انتقل إلى المحتوى

ترجمة آلية

تمت ترجمة هذه الصفحة تلقائيًا بواسطة الذكاء الاصطناعي وقد تحتوي على أخطاء. إذا كان هناك أي شيء غير واضح، يُرجى الرجوع إلى المصدر الإنجليزي.

المصدر الإنجليزي · الإبلاغ عن مشكلة في الترجمة

خط أنابيب التصدير


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_&lt;account&gt;.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)"])

التصميمات الرئيسية:

  1. الاستجابات الخام محفوظة لإعادة التشغيل دون اتصال: adapter.get_thread يحلل ويجمع المحادثة في الذاكرة قبل تشغيل FilesystemWriter.write_thread (adapter.py:58-157). الكتابة الناجحة تخزن thread.json أولاً ثم الاستجابات النصية/الكتلية المتاحة كـ raw_*.json (fs_writer.py:224-266)؛ يمكن إعادة تشغيل التحليل/العرض لاحقًا من تلك الملفات دون اتصال بالشبكة.
  2. النص يُجلب دائمًا؛ الكتل حسب اكتشاف الوضع: البحث يتخطى الكتل (يوفر طلبًا واحدًا)؛ عندما تفشل جميع إشارات الكشف، البديل يجلب أيضًا (adapter.py:74-85 تعليق وقرار: من الأفضل الجلب الزائد عن ترك raw_blocks.json يختفي بصمت بعد تغيير حقل المنصة).
  3. نقطة تحليل واحدة: جميع استخراجات الحقول مركزة في parsers.py (_g وصول آمن متعدد المستويات parsers.py:23، _loads تحمل الأخطاء parsers.py:33، to_int مفتاح فرز متساهل parsers.py:44) — تجديدات الموقع تحتاج فقط إلى تغيير مكان واحد.
  4. الكاتب للقراءة فقط: تركيب 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 &gt; council &gt; study<br/>&gt; deep-research &gt; 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)، مما يضمن عدم اختفاء البيانات المنظمة بصمت بسبب انحراف الحقل.