面向复杂任务的多源搜索Agent设计:从任务规划到Markdown-PDF报告自动生成
Agent驱动的高质量报告自动生成从Markdown到PDF的工程化实现摘要让LLM生成一份结构完整、格式规范的PDF报告远比想象中复杂——内容质量、文件路径安全、格式兼容性、资源清理每个环节都是坑。本文以 DeepAgents 项目中的报告生成模块为例详细介绍从Markdown结构化输出到PDF转换的完整工程链路涵盖tool工具定义、resolve_path路径沙箱、Word COM 转换引擎、提示词内容约束以及资源清理等关键实现。一、为什么报告生成是个工程问题让LLM写一段文字不难但生成一份能用的报告涉及多个工程挑战内容层面LLM 有编造倾向在信息不充分时会生成似是而非的内容。必须通过提示词约束保证先搜集信息再生成文档的执行顺序禁止在信息不全时调用文件生成工具。路径层面LLM 经常产生幻觉路径如/workspace/report.md或/mnt/data/output.md。如果直接使用这些路径文件会写到错误的位置甚至覆盖系统文件。格式层面Markdown 到 PDF 的转换需要保留表格、代码块、标题层级等结构。纯文本转 PDF 库如 reportlab需要手动处理排版而利用 Word COM 引擎可以复用成熟的排版引擎。资源层面Word COM 是进程外 COM 组件每次调用后必须正确清理否则 Word 进程会堆积在后台导致内存泄漏。二、报告生成架构资源清理阶段格式转换阶段路径安全阶段内容生成阶段输入阶段LLM 幻觉路径重定向到output/session_{id}/.md 文件.temp.htmlfinally 块最终 .pdf 文件Agent 搜集的信息网络/数据库/知识库用户指令报告主题、格式要求提示词约束内容≥1000字、禁止占位符generate_markdown 工具Markdown 结构化输出resolve_path路径清洗与重定向get_session_context会话工作目录Markdown → HTMLmarkdown库 CSS样式Word COM 引擎Dispatch(Word.Application)FileFormat17另存为 PDF关闭 Word 进程删除临时 .temp.htmlpythoncom.CoUninitializeoutput/session_{id}/report.pdf报告生成分四个阶段内容生成→路径安全→格式转换→资源清理每个阶段解决一类特定的工程问题。三、核心实现3.1 提示词约束从源头控制内容质量报告内容的第一道防线在prompts.yml中。主Agent的 System Prompt 对文件生成做了严格约束# prompt/prompts.yml文件生成相关规则-文件生成-可以使用 generate_markdown、convert_md_to_pdf 工具-生成Markdown文档时直接调用工具即可-生成PDF文档时先生成Markdown再转换为PDF-生成内容要求-无论什么复杂程度的任务都需要生成一个todo-list进行规划-内容要求丰富且全面不少于1000字-严禁使用等待子任务完成之类的占位符内容生成文件-只有当你真正拿到了完整的信息文本后才能调用 generate_markdown-关键执行顺序-绝不允许在获取信息之前调用文件生成工具-分两步执行第一步只调用搜索工具第二步调用生成工具这些规则解决了三个关键问题内容完整性要求不少于1000字禁止占位符确保报告不是内容待补充的空壳执行顺序强制先搜索后生成避免 LLM 在信息不全时编造数据格式链路明确 Markdown → PDF 的两步流程不允许跳过值得注意的是提示词中还包含了通过发送消息汇报文档生成进度及结果的时候不允许发送文档的路径的约束。这是出于安全考虑——文档路径可能暴露服务器目录结构前端只应知道文件已生成无需知道具体路径。同时todo-list 进行规划的要求让 LLM 在生成报告前先输出一个结构化的章节规划确保报告有清晰的逻辑框架而非随意堆砌。3.2 Markdown 生成路径安全的tool实现generate_markdown是报告生成链路的起点。它接受内容、文件名和路径参数但路径并不直接使用——必须经过resolve_path清洗# tools/markdown_tools.pytooldefgenerate_markdown(content:Annotated[str,要写入Markdown文档的文本内容],filename:Annotated[str,Markdown文档的文件名],path:Annotated[str,文件保存的绝对路径]):根据提供的文本内容生成对应的Markdown(.md)文件ifnotfilename.endswith(.md):filename.md# 从 ContextVar 获取当前会话的工作目录session_dirget_session_context()# 路径清洗结合 path 和 filename交给 resolve_path 处理ifpathandpath!.:full_input_pathstr(Path(path)/filename)else:full_input_pathfilename full_path_strresolve_path(full_input_path,session_dir)file_pathPath(full_path_str)# 确保目录存在并写入文件file_path.parent.mkdir(parentsTrue,exist_okTrue)file_path.write_text(content,encodingutf-8)returnfMarkdown文件 {file_path} 已成功生成。关键设计点get_session_context()通过 ContextVar 获取当前会话的工作目录如output/session_abc123/然后resolve_path将 LLM 传过来的任何路径清洗并重定向到这个目录下。无论LLM传的是/workspace/report.md、output/report.md还是session_abc123/report.md最终都会落到正确的会话目录中。3.3 路径沙箱resolve_path的清洗规则resolve_path是路径安全的守门员针对 LLM 常见的路径幻觉实现了多种场景的处理输入场景清洗结果/workspace/report.mdoutput/session_123/report.md/mnt/data/sub/report.mdoutput/session_123/sub/report.mdoutput/report.mdoutput/session_123/report.mdsession_123/session_123/report.md嵌套output/session_123/report.mdsub/report.md普通相对路径output/session_123/sub/report.md核心逻辑可以概括为三个剥离剥离虚拟路径前缀/workspace、/mnt/data、/home/user、剥离重复的 session 嵌套目录、剥离 output 前缀。最终统一拼接到session_dir下。3.4 PDF 转换Word COM 引擎的完整实现Markdown 转 PDF 是整个链路中最复杂的环节。项目选择 Windows Word COM 引擎而非纯 Python 库原因是 Word 对表格、代码块、中文排版的支持远优于 reportlab 等轻量方案# utils/word_converter.pydefconvert_md_to_pdf_via_word(md_abs_path:Path,pdf_abs_path:Path)-str:temp_html_pathmd_abs_path.with_suffix(.temp.html)try:# 第一步Markdown → HTML含 CSS 样式withopen(md_abs_path,r,encodingutf-8)asf:md_contentf.read()html_bodymarkdown.markdown(md_content,extensions[tables,fenced_code])html_contentf htmlheadmeta charsetUTF-8 style body {{ font-family: Microsoft YaHei, SimHei, sans-serif; }} table {{ border-collapse: collapse; width: 100%; }} th, td {{ border: 1px solid black; padding: 8px; }} pre {{ background-color: #f5f5f5; padding: 10px; }} /style/head body{html_body}/body/html withopen(temp_html_path,w,encodingutf-8)asf:f.write(html_content)# 第二步Word COM 打开 HTML → 另存为 PDFpythoncom.CoInitialize()word_appwin32com.client.Dispatch(Word.Application)word_app.VisibleFalseword_app.DisplayAlertsFalsedocword_app.Documents.Open(str(temp_html_path.resolve()))doc.SaveAs(str(pdf_abs_path.resolve()),FileFormat17)# wdFormatPDFdoc.Close(SaveChanges0)returnf成功转换:{pdf_abs_path}(Word引擎)finally:# 第三步资源清理finally 块保证一定执行ifword_app:word_app.Quit()iftemp_html_path.exists():temp_html_path.unlink()pythoncom.CoUninitialize()转换流程是标准的管道Markdown → HTML含 CSS→ Word COM 打开 → FileFormat17 另存为 PDF。三个关键细节CSS 保留结构表格的border-collapse、代码块的monospace字体、中文字体配置都在 HTML 阶段的 CSS 中定义确保 PDF 输出格式正确FileFormat17Word 的wdFormatPDF常量无需额外安装 PDF 打印机finally 资源清理Word 进程退出、临时 .temp.html 删除、COM 反初始化三个清理步骤缺一不可。漏掉pythoncom.CoUninitialize()会导致 COM 对象泄漏后续调用报错3.5 报告工具的注册与调用链路两个报告工具通过main_agent.py注册到主Agent中# agent/main_agent.pymain_agentcreate_deep_agent(modelmodel,subagentssubagents_list,tools[generate_markdown,convert_md_to_pdf,read_file_content],system_promptmain_agent_config[system_prompt])主Agent的tools参数只注册了文件生成相关的三个工具搜索工具全部通过子Agent委派。这种设计在语义上清晰体现了主Agent负责生成子Agent负责搜索的分工。convert_md_to_pdf工具的调用逻辑在提示词中已经明确——“先生成Markdown再通过pdf工具进行转换得到最终的pdf文档”LLM 不会直接调用 PDF 工具而是先调generate_markdown再调convert_md_to_pdf。四、工程实践要点4.1 Word COM 的进程管理Word COM 是进程外组件每次Dispatch都会创建一个新的 WINWORD.EXE 进程。如果finally块中漏掉了word_app.Quit()Word 进程会堆积在后台长时间运行后消耗大量内存。pythoncom.CoInitialize/CoUninitialize的配对调用同样重要——COM 的单元模型Apartment Model要求每个线程初始化一次不反初始化会导致线程泄漏。4.2 临时文件的自清理Markdown 转 PDF 过程中产生的.temp.html文件必须在转换完成后删除。代码中通过temp_html_path.exists()检查加unlink()删除这是一个看似简单但容易被忽略的细节。如果转换中途异常退出临时文件会残留长期积累占用磁盘空间。4.3 内容长度与格式约束prompts.yml中的不少于1000字看似随意实则是经过实测的经验值——少于1000字的报告在格式上会比较单薄表格和图表难以展开。同时todo-list要求让 LLM 在生成报告前先规划结构减少写到一半忘记章节的概率。五、总结本文从内容生成、路径安全、格式转换、资源清理四个维度完整梳理了 Agent 驱动的报告自动生成链路。核心设计原则路径安全 功能便利LLM 的路径幻觉是随机性的必须通过resolve_path强制兜底不能依赖 LLM 的自觉复用成熟引擎PDF 转换选择 Word COM 而非纯 Python 库因为 Word 的排版引擎经过了二十多年的打磨对中文、表格、代码块的支持远优于轻量方案资源清理不可省略COM 组件和临时文件的管理必须放在finally块中任何异常路径都不能跳过清理步骤如果你正在搭建一个需要自动产出 PDF 报告的 Agent 系统建议优先解决路径安全和内容质量控制两个问题——它们决定了报告能不能用、敢不敢用而 PDF 转换引擎的选择反而是最灵活的环节。这套链路虽然依赖 Windows 环境Word COM但在企业级场景中Windows Server 是常见部署环境Word 引擎的兼容性和输出质量带来的收益远大于跨平台损失。如果需要在 Linux 环境下运行可以考虑将 Word COM 替换为 LibreOffice 的--headless模式替换成本较低——只需修改word_converter.py中的转换引擎即可。技术栈Python 3.10 / pywin32 / markdown / Word COM / FastAPI / ContextVar适用场景数据分析报告自动生成、企业文档批量导出、合规报告

相关新闻

数据指标计算完全指南:回归与分类评估核心指标详解

数据指标计算完全指南:回归与分类评估核心指标详解

一句话总结:MAE/MSE 衡量回归误差,信息熵/准确率/混淆矩阵衡量分类效果,业务场景决定指标选择。 环境要求 Python 3.14scikit-learn 1.9pandas 3.0numpy 2.4jupyter 1.1notebook 7.5nbconvert 7.17 一、指标速查表 任务类型核心指标公式特…

2026/10/2 3:46:22 阅读更多 →
深度揭秘佛山市锵美装饰有限公司网站建设案例如何助力传统家装企业实现数字化转型与品牌升级

深度揭秘佛山市锵美装饰有限公司网站建设案例如何助力传统家装企业实现数字化转型与品牌升级

在这个“酒香也怕巷子深”的互联网时代,传统装修行业正面临着前所未有的变革浪潮。以前,大家找装修公司靠的是邻居推荐、小区传单或者路过门店时的直观感受;而现在,信息的获取渠道早已转移到手机端和电脑上。当用户想要在佛山寻找一家靠谱的装修公司时,他们做的第一件事往…

2026/10/4 23:43:23 阅读更多 →
从CI/CD告警到防御:揭秘npm依赖混淆攻击与axios供应链安全实践

从CI/CD告警到防御:揭秘npm依赖混淆攻击与axios供应链安全实践

1. 从一次深夜告警说起:当CI/CD流水线开始“吃”自己的日志凌晨两点,手机屏幕在黑暗中亮起,不是消息推送,而是监控系统的告警。一条CI/CD流水线的构建失败了,错误日志简短得令人不安:Error: Cannot find mo…

2026/10/5 8:33:13 阅读更多 →

最新新闻

DeepSeek工具调用与多模态扩展实战指南

DeepSeek工具调用与多模态扩展实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 4:32:53 阅读更多 →
乳酸化修饰如何驱动肿瘤免疫逃逸?机制与实验验证全解析

乳酸化修饰如何驱动肿瘤免疫逃逸?机制与实验验证全解析

这些年泡在肿瘤微环境和表观遗传相关的研究里,我明显感觉到一个趋势:代谢物不再只是能量代谢的配角,而是直接钻进表观遗传机器里改写基因表达。尤其是“乳酸化修饰”这个概念,这几年几乎成了免疫代谢交叉领域最热的关键词之一。最…

2026/10/9 4:32:53 阅读更多 →
FloEFD流域识别失败?3大高频坑与系统排查法

FloEFD流域识别失败?3大高频坑与系统排查法

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 4:32:53 阅读更多 →
VSCode 1.68.1 ia32便携版指南:老系统绿色开发环境搭建与避坑

VSCode 1.68.1 ia32便携版指南:老系统绿色开发环境搭建与避坑

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 4:32:53 阅读更多 →
110kV距离保护整定全解析:三段式配合、分支系数与二次回路

110kV距离保护整定全解析:三段式配合、分支系数与二次回路

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 4:32:53 阅读更多 →
无线网络安全实验全流程:从抓包到WPA2握手审计

无线网络安全实验全流程:从抓包到WPA2握手审计

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 4:31:52 阅读更多 →

日新闻

Java时间API实战:LocalDate、Date与ZonedDateTime的转换与避坑指南

Java时间API实战:LocalDate、Date与ZonedDateTime的转换与避坑指南

Java时间API这个话题,隔三差五就会在群里被翻出来讨论一次。上周还有个同事线上处理一个订单超时问题,排查到最后发现是ZonedDateTime序列化后时区丢了,用户在下单当天晚上看到的时间整整差了8个小时。这类问题几乎每个做Java开发的人都遇到过…

2026/10/9 0:00:49 阅读更多 →
EasyTier实践:从NAT穿透到子网代理的异地组网部署与排错

EasyTier实践:从NAT穿透到子网代理的异地组网部署与排错

前几个月我手头有好几台机器需要互相访问:办公室台式机、家里 NAS、还有一台云主机。如果只是偶尔传个文件倒还好,问题是工作场景经常要在几处环境之间来回切换,每次都先登录跳板机再层层代理,实在折腾。我先后试过端口映射、自建…

2026/10/9 0:00:49 阅读更多 →
AI Agent工程实战:从七要素到七个决策点的系统设计指南

AI Agent工程实战:从七要素到七个决策点的系统设计指南

AI Agent 这个词在过去一年里被反复提及,但真正动手搭过一套能跑起来的 Agent 系统的人都知道,从"知道它是什么"到"让它稳定干活"之间隔着一整套工程决策。我前后参与过几个 Agent 项目的落地,从最初用现成框架拼装&…

2026/10/9 0:01:50 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:32 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:40 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 10:10:36 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 21:13:17 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 13:34:55 阅读更多 →