ترجمة آلية
تمت ترجمة هذه الصفحة تلقائيًا بواسطة الذكاء الاصطناعي وقد تحتوي على أخطاء. إذا كان هناك أي شيء غير واضح، يُرجى الرجوع إلى المصدر الإنجليزي.
خط أنابيب التصدير¶
1. خط أنابيب التصدير بالتفصيل¶
خط أنابيب التصدير أحادي الخيط الكامل (cmd_export، export_cmd.py:42-86؛ مسار الدُفعة cmd_batch
يعيد استخدام نفس خط الأنابيب، batch_cmd.py:117-204). الوظائف الرئيسية وأرقام الأسطر بين قوسين:
flowchart TD
START(["thread URL / UUID"]) --> IDX
subgraph S1["① indexing (pre-step of index / batch)"]
IDX["GraphQLClient.list_threads<br/>(graphql.py:51)<br/>LibraryThreadsRelayQuery first page 25 items<br/>LibraryRecentThreadsPaginationQuery pagination<br/>stop when endCursor is empty (graphql.py:81-85)"]
IDX --> IDXO[("index/library_<account>.json<br/>cmd_index (index_cmd.py:17)")]
end
subgraph S2["② incremental planning (batch only)"]
PLAN["plan_incremental pure function<br/>(hooks/incremental.py:36)<br/>sorted by lastUpdated desc, four-state classification:<br/>new / updated / done / expired"]
PLAN --> ES{"neither full nor force?"}
ES -->|"yes"| TRIM["trim the longest trailing done/expired run<br/>(early stop, incremental.py:83-86)"]
ES -->|"no"| KEEP["return item by item (full-scan fallback)"]
end
subgraph S3["③ fetching (adapter.get_thread)"]
GT["PerplexityAdapter.get_thread<br/>(adapter.py:58)"]
GT --> PLAIN["ThreadFetcher.get_thread<br/>plain response (rest.py:59)<br/>GET /rest/thread/uuid<br/>entries + background_entries<br/>cursor pagination ≤20 pages, 3s between pages (rest.py:43-57)"]
DM{"detect_mode<br/>(normalize.py:66)<br/>mode detection after parse_turn, see §4"}
DM -->|"computer / deep-research / council / study"| BLK["ThreadFetcher.get_thread_blocks<br/>schematized response (rest.py:67)<br/>8 SCHEMATIZED_USE_CASES (rest.py:26)<br/>sleep blocks_delay=4s before fetching (adapter.py:28,88)"]
DM -->|"search (all signals present)"| NOBLK["skip blocks<br/>(search is simple query+answer)"]
DM -->|"all-signals-missing fallback (adapter.py:84-87)<br/>mode=search and no entry has display_model"| BLK
BLK --> ANOM["scan_wf_anomalies<br/>log.warning on any non-COMPLETED workflow<br/>(parsers.py:646; call site adapter.py:131-134)"]
end
subgraph S4["④ in-memory parsing and assembly (before persistence)"]
PT["parse_turn × N (parsers.py:173)<br/>steps / three-channel citation collection<br/>(entry.sources + FINAL.web_results<br/>+ WORKFLOW_ITEM_SOURCES)<br/>report_info / locked_reason into metadata"]
EA["extract_answer (parsers.py:80)<br/>FINAL.answer JSON → plan.goals fallback"]
BASE["Base Conversation assembled in memory<br/>raw responses retained on conv._plain / conv._blocks"]
ATT["attach_workflow_blocks (parsers.py:231)<br/>wf_block attached to turns by entry uuid<br/>wf_status into turn.metadata"]
STUB["attach_stub_workflows (parsers.py:470)<br/>stub-turn 10s time-window matching"]
UNC["collect_unconsumed_background<br/>(parsers.py:491) attribution waterfall ③"]
READY["Conversation ready for export<br/>(adapter.py:91-157)"]
BASE -->|"other modes"| READY
BASE -->|"computer/council only<br/>(adapter.py:150-157)"| ATT
ATT --> STUB
STUB -.-> UNC
UNC --> READY
end
subgraph S5["⑤ asset preparation (before writer)"]
ADL["adapter.get_assets (adapter.py:196)<br/>collect_downloadable_assets (parsers.py:696)<br/>same-name multi-version numbered v1..vN by created_at"]
CDN["CloudFront signed-URL direct download<br/>AssetDownloader.download_all (assets.py:80)<br/>resolve_ext three-source extension resolution (assets.py:122)"]
end
subgraph S6["⑥ persistence (fs_writer.write_thread)"]
WJ[("thread.json (fs_writer.py:224-253)<br/>+ interruptions registration collect_interruptions (parsers.py:535)<br/>+ answer_variants registration collect_answer_variants (parsers.py:589)")]
RAW1[("raw_entries.json<br/>retained plain response (fs_writer.py:257-261)")]
RAW2[("raw_blocks.json<br/>retained schematized response (fs_writer.py:262-266)<br/>absent when blocks were not fetched")]
WM[("conversation.md compact version<br/>render_conversation (render.py:641)")]
WT[("turns/turn_NNNN.md full version<br/>render_turn (render.py:596)")]
WS[("sources.json + sources.md<br/>(fs_writer.py:270-278)")]
WR[("report.md (fs_writer.py:308-316)<br/>adapter.get_report fallback when needed")]
MF[("assets/assets_manifest.json<br/>versioned manifest (fs_writer.py:320-330)")]
end
IDX --> S2
S2 -->|"per-thread new/updated"| S3
PLAIN --> PT
PT --> EA
EA --> DM
ANOM --> BASE
NOBLK --> BASE
READY --> ADL
ADL --> CDN
CDN --> WJ
WJ --> RAW1
WJ -. "when blocks fetched" .-> RAW2
RAW1 --> WS
RAW1 -. "offline re-runnable<br/>(re-render, see offline-operations.md §12)" .-> PT
WS --> WM
WM --> WT
WT --> WR
WR --> MF
MF --> DONE(["state.mark_ok<br/>BatchState checkpoint (state.py:123)"])
التصميمات الرئيسية:
- الاستجابات الخام محفوظة لإعادة التشغيل دون اتصال:
adapter.get_threadيحلل ويجمع المحادثة في الذاكرة قبل تشغيلFilesystemWriter.write_thread(adapter.py:58-157). الكتابة الناجحة تخزنthread.jsonأولاً ثم الاستجابات النصية/الكتلية المتاحة كـraw_*.json(fs_writer.py:224-266)؛ يمكن إعادة تشغيل التحليل/العرض لاحقًا من تلك الملفات دون اتصال بالشبكة. - النص يُجلب دائمًا؛ الكتل حسب اكتشاف الوضع: البحث يتخطى الكتل (يوفر طلبًا واحدًا)؛ عندما تفشل جميع إشارات الكشف، البديل يجلب أيضًا (adapter.py:74-85 تعليق وقرار: من الأفضل الجلب الزائد عن ترك
raw_blocks.jsonيختفي بصمت بعد تغيير حقل المنصة). - نقطة تحليل واحدة: جميع استخراجات الحقول مركزة في
parsers.py(_gوصول آمن متعدد المستويات parsers.py:23،_loadsتحمل الأخطاء parsers.py:33،to_intمفتاح فرز متساهل parsers.py:44) — تجديدات الموقع تحتاج فقط إلى تغيير مكان واحد. - الكاتب للقراءة فقط: تركيب
wf_block/stub_wfs/unconsumed_bgsيحدث فيadapter.get_thread(adapter.py:150-157)؛FilesystemWriterلا يُركب، بل يستهلك فقط (fs_writer.py:217 تعليق).
2. شجرة قرار اكتشاف الوضع (detect_mode)¶
منطق القرار الكامل لـ normalize.detect_mode (normalize.py:66-128). خمسة أوضاع:
computer / council / deep-research / study / search (البحث هو الافتراضي).
الإشارة الأعلى أولوية هي حقل المنصة الموثوق entry.search_mode (SEARCH_MODE_MAP،
normalize.py:50-59): تكوين النموذج الرسمي تم اختباره (GET /rest/models/config/v2)
default_models.search=pplx_pro (واجهة المستخدم "الأفضل")، default_models.research=pplx_alpha
(واجهة المستخدم "بحث عميق")؛ قيم search_mode تتطابق واحد لواحد مع أوضاع المحادثة (التحقق من الأرشيف: 100+
موضوع SEARCH-entry + pplx_alpha هي 100% search_mode=RESEARCH؛ 100+ موضوع pplx_pro النقي هي جميعها
SEARCH). فقط عندما يكون search_mode غائبًا تمامًا، يتراجع إلى سلسلة اسم الخطوة + display_model الأصلية.
flowchart TD
IN["Input: metadata + idx_thread + url<br/>+ turns + entries"] --> SMQ{"search_mode hit on any entry?<br/>(any over all entries, normalize.py:106-115)<br/>ASI→computer / AGENTIC_RESEARCH→council<br/>STUDY→study / RESEARCH→deep-research<br/>SEARCH/STUDIO→search"}
SMQ -->|"hit"| MULTI{"multi-value conflict within thread?<br/>(mode switching, normalize.py:116-119)"}
MULTI -->|"multiple values"| SPEC["take highest by specificity:<br/>computer > council > study<br/>> deep-research > search<br/>(_MODE_SPECIFICITY, normalize.py:63)<br/>+ log.warning"]
MULTI -->|"single value"| SMCF{"conflict with downstream signals (step names/<br/>display_model)?<br/>(normalize.py:120-123)"}
SPEC --> SMCF
SMCF -->|"conflict"| SMWARN["log.warning recorded<br/>search_mode wins"]
SMCF -->|"consistent or no downstream signal"| SMWIN["return the search_mode-mapped mode"]
SMWARN --> SMWIN
SMQ -->|"all absent"| Q1{"primary signal A: URL contains /computer/tasks/<br/>or metadata.mode == '4'<br/>or index mode ∈ ASI/COMPUTER?"}
Q1 -->|"yes"| SM1["step_mode = computer"]
Q1 -->|"no"| Q2{"primary signal B: a COUNCIL_RESEARCH step exists?"}
Q2 -->|"yes"| SM2["step_mode = council"]
Q2 -->|"no"| Q3{"primary signal C: a RESEARCH_ANSWER step exists?<br/>(content-based, not relying on Chinese labels)"}
Q3 -->|"yes"| SM3["step_mode = deep-research"]
Q3 -->|"no"| SM0["step_mode = (empty)"]
SM1 --> DMS
SM2 --> DMS
SM3 --> DMS
SM0 --> DMS
subgraph DMS["fallback-chain redundant signal: display_model (DISPLAY_MODEL_MODE, normalize.py:32-37)"]
DMQ{"display_model hit on any entry?<br/>(any over all entries, not just the first —<br/>the first entry of a mixed thread may not represent the whole)"}
MAP["mapping table:<br/>pplx_agentic_research → council<br/>pplx_asi_opus → computer<br/>pplx_asi_opus_thinking → computer<br/>pplx_study → study"]
end
DMQ -->|"hit"| CF{"conflicts with step_mode?"}
CF -->|"conflict"| WARN["log.warning records the conflict<br/>display_model wins"]
CF -->|"consistent or step_mode empty"| DMWIN["return the display_model-mapped mode"]
WARN --> DMWIN
DMQ -->|"no hit"| FB{"step_mode non-empty?"}
FB -->|"yes"| SMWIN2["return step_mode"]
FB -->|"no"| SEARCH["return search (default)"]
انضباط الكشف (أساس مُختبر):
pplx_alphaليس متعمدًا في جدول التعيين (normalize.py:15-31 تعليق): إنه النموذج المخصص لـ RESEARCH (default_models.research؛ واجهة المستخدم ثابتة، لا محدد) — إنه الهدف الذي يجب على هذا المصنف اكتشافه، وليس إشارة كشف. الإحصاء السابق "121 موضوع بحث استخدم pplx_alpha" كان في الواقع عينات من سوء تقدير هذا المصنف نفسه (بدون إشارة search_mode، جلسات RESEARCH التي تفتقر إلى خطوة RESEARCH_ANSWER تم اعتبارها بحثًا) — أعيد تقييمها بواسطة search_mode وتم ترحيل 124 موضوعًا.- search_mode يفوز عند التعارض مع الإشارات اللاحقة: إنه سجل المنصة الموثوق لوضع المحادثة؛ أسماء الخطوات هي أكثر حقول المنصة عرضة للتغيير، display_model مجرد تعداد طبقة نموذج. التعارضات دائمًا تسجل تحذيرًا (normalize.py:120-123).
- تبديل الوضع داخل الموضوع يأخذ الأعلى حسب الخصوصية: computer > council > study > deep-research > search (normalize.py:63, 116-119)، مع تسجيل تحذير.
- غياب جميع الإشارات لا يستنتج البحث: التراجع إلى جلب الكتل على مستوى خط الأنابيب (adapter.py:84-87)، مما يضمن عدم اختفاء البيانات المنظمة بصمت بسبب انحراف الحقل.