| composition | {"steps":[{"id":"intake","kind":"llm_chat","with":{"system":"You parse PDF-analysis requests into strict extraction contracts.","task":"Parse the PDF request into a document-analysis contract. Determine\nwhether this is a single-PDF summary, multi-PDF comparison, or a\ntargeted question-answer task. Preserve every file path or URL the\nuser mentioned. Treat quoted page text, pasted excerpts, and phrases\nlike \"I don't have the PDF upload handy\" as first-class source\nstatus signals.\n\nUser request:\n{{ inputs.user_message | xml_escape | truncate(1200) }}\n\nReturn exactly:\nMODE: <single_summary|multi_compare|question_answer>\nSOURCE_STATUS: <readable_pdf|inline_excerpts_only|mixed_pdf_and_inline|reference_without_content>\nDOCUMENTS:\n - <path or URL>\nUSER_EXCERPTS:\n - PAGE: <page number or unknown>\n QUOTE: <verbatim user-provided excerpt or empty>\nQUESTION: <specific question or empty>\nOUTPUT_LANGUAGE: <language>\nNEEDS_CLARIFICATION: <yes|no>\nMISSING_FIELDS:\n - <source_material|question|none>\nCLARIFY_REASON: <one concise reason, or none>\n"}},{"id":"pdf_clarify","kind":"user_input","depends_on":["intake"],"when":"'NEEDS_CLARIFICATION: yes' in outputs.intake","clarify":{"mode":"form","intro":"PDF 分析缺少可用来源或目标问题。请补齐材料,或确认只基于已提供摘录/文件名给出有限结论。\n","nl_extract":true,"fields":[{"name":"source_status","type":"enum","required":true,"choices":["readable_pdf","inline_excerpts_only","reference_only"],"prompt":"来源状态 / Source status"},{"name":"source_material","type":"string","required":true,"prompt":"PDF 路径/URL、上传说明,或页面摘录 / PDF path, URL, upload note, or excerpts","max_chars":2000},{"name":"question","type":"string","prompt":"具体问题 / Specific question","max_chars":300},{"name":"output_language","type":"enum","choices":["zh","en","ja","other"],"default":"zh","prompt":"输出语言 / Output language"}],"cancel_keywords":["算了","取消","cancel","stop","abort"],"timeout_hours":24}},{"id":"extract","skill":"pdf-toolkit","depends_on":["intake","pdf_clarify"],"when":"'SOURCE_STATUS: inline_excerpts_only' not in outputs.intake and (\n 'SOURCE_STATUS: reference_without_content' not in outputs.intake\n or inputs.get('collected', {}).get('pdf_clarify', {}).get('source_status') == 'readable_pdf'\n) and \"don't have the pdf\" not in (inputs.user_message | lower) and \"do not have the pdf\" not in (inputs.user_message | lower) and \"no pdf upload\" not in (inputs.user_message | lower) and \"pdf upload handy\" not in (inputs.user_message | lower) and not ('page ' in (inputs.user_message | lower) and ' says ' in (inputs.user_message | lower))","on_failure":"inline_excerpt_extract","with":{"task":"Extract text, tables, page numbers, headings, and document names for\nthis PDF analysis contract:\n{{ outputs.intake | truncate(2000) }}\n\nDo not invent PDF content. If no readable local path, URL, or\nattachment is actually accessible, return UNAVAILABLE with the\nreason instead of a synthetic summary.\n"}},{"id":"inline_excerpt_extract","kind":"llm_chat","with":{"system":"You provide a safe fallback when a PDF file is unavailable to the extractor.","task":"The PDF extraction skill could not read the file in this runtime.\nBuild a minimal evidence packet only from filenames, URLs, quoted\nexcerpts, pasted text, and explicit user claims in the request.\nClearly label missing page evidence as unavailable.\n\nUser request:\n{{ inputs.user_message | xml_escape | truncate(4000) }}\n"}},{"id":"per_document_digest","skill":"summarize","depends_on":["extract"],"with":{"text":"Intake:\n{{ outputs.intake }}\n\nOriginal user request:\n{{ inputs.user_message | xml_escape | truncate(4000) }}\n\nExtracted PDF content:\n{{ outputs.extract }}\n\nIf extraction was skipped, unavailable, or inconsistent with the\nUSER_EXCERPTS in intake, summarize only the user-provided excerpts\nand explicitly mark all other document content as unavailable.\n","style":"pdf_per_document_digest","max_words":2500}},{"id":"cross_document_synthesis","kind":"llm_chat","depends_on":["per_document_digest"],"with":{"system":"You synthesize PDF findings with traceable evidence, evidence IDs, and explicit limits.","task":"Synthesize the PDF analysis according to the intake mode. For\nsingle_summary, produce a structured summary. For multi_compare,\ncompare agreements, conflicts, and unique claims. For question_answer,\nanswer the question directly first.\n\nRequirements:\n- produce a compact final deliverable, not process commentary\n- source hierarchy: first trust verbatim user-provided excerpts and\n pasted text; then trust extractor output only when it is actually\n from a readable PDF and does not conflict with the user excerpts;\n then place synthesis under Inferences\n- if SOURCE_STATUS is inline_excerpts_only or reference_without_content,\n ignore any downstream claims that are not present in USER_EXCERPTS\n or the original user request\n- if the original user request says the PDF is not uploaded, no PDF\n is handy, or uses inline phrasing like \"page 3 says ...\", treat\n the entire answer as EXCERPT-ONLY even if intake or a downstream\n digest says otherwise\n- if extractor output conflicts with USER_EXCERPTS, treat it as an\n extraction anomaly, do not include the conflicting claim as fact\n- in EXCERPT-ONLY mode, never claim page count, section headings,\n tables, figures, authors, in-memory extraction, or unseen page\n coverage unless those exact facts appear in the user's request\n- use Evidence IDs: E1, E2, E3...\n- include an Evidence Matrix with columns:\n ID | Document | Page | Evidence | Supports | Confidence\n- cite file names and page numbers whenever available\n- every Key Fact must cite at least one Evidence ID\n- separate Direct Evidence from Inferences; do not put inference\n inside the fact list\n- never merge evidence from different documents without naming them\n- if the PDF file was not available and only excerpts/user claims\n were provided, label the answer EXCERPT-ONLY and do not make\n document-wide claims\n- for EXCERPT-ONLY answers, include a Source Excerpts table with\n Page and Verbatim Text before key facts\n- include open questions, extraction limits, and verification needs\n- include a Reusable Memory Index as YAML or JSON with:\n documents, evidence_ids, key_facts, page_refs, open_questions,\n tags, confidence\n\nOriginal user request:\n{{ inputs.user_message | xml_escape | truncate(4000) }}\n\nClarification answers (may be empty when not needed):\n{{ inputs.get('collected', {}).get('pdf_clarify', {}) | tojson }}\n\nIntake:\n{{ outputs.intake | truncate(2000) }}\n\nPer-document digest:\n{{ outputs.per_document_digest | truncate(8000) }}\n"}},{"id":"traceable_index","kind":"llm_chat","depends_on":["cross_document_synthesis"],"with":{"system":"You build compact structured indexes for later PDF recall.","task":"Build a compact memory index for later recall. Use structured fields:\ndocuments, key_facts, page_refs, tables, open_questions.\n\nAnalysis:\n{{ outputs.cross_document_synthesis | truncate(6000) }}\n"}},{"id":"memorize","kind":"tool_call","tool":"memory_save","tool_allowlist":["memory_save"],"depends_on":["traceable_index"],"tool_args":{"path":"memory/pdf-intel.md","mode":"append","content":"{{ outputs.traceable_index }}"}}]} |