MindStack:多Agent智能金融研报平台
项目概述
MindStack是基于LangGraph构建的多Agent金融研报系统,采用双层图架构:6节点顶层主图(Browser→Planner→Human→Researcher→Writer→Publisher)串联从股票识别、大纲生成到报告发布的完整流程,每章节内嵌套3节点审改子图(Researcher→Reviewer→Reviser)形成审改闭环,7个Agent共享TypedDictState通信。设计了金融数据模块集成FMP(美股)+雪球(A股/港股)双源API,三级兜底识别股票代码,统一暴露约30项财务指标与19个行业同行映射。项目通过错误注入对照评测定位Reviewer单次LLM调用四维校验的注意力稀释问题,重构为4路独立并行调用+关键维度程序化预检;经held-outticker验证,数据准确性recall25.0%→97.5%,系统还支持大纲阶段的人工审核回退机制。
技术栈:Python+FastAPI(后端服务)、LangGraph(Agent编排)、FinancialModelingPrepAPI(美股数据)、雪球/pysnowball(A股/港股数据)、Next.js14(前端,基于GPT-Researcher原生前端二次开发)、WebSocket(实时通信)。
以下按模块说明设计与实现,并在涉及量化结果处同步说明其适用边界。
一、总体架构:LangGraphStateGraph编排
工作流通过LangGraph的StateGraph定义为一条带条件边的节点链路:browser(初始研究+金融数据注入)→planner(生成报告大纲)→human(人工反馈,可选)→researcher(并行深度研究)→writer(撰写)→publisher(汇总导出)。human节点之后设有条件边:根据反馈内容判断是路由回planner重新规划,还是前进到researcher,并设置最大重规划次数上限以避免循环。金融数据的获取(个股概览、财务报表、同业数据)通过asyncio.gather并发执行,单一数据源超时或异常时捕获降级为通用研究模式,不影响整条流水线。
二、股票代码识别与双源数据路由
用户输入可能是公司名称("分析贵州茅台")或代码("600519"/"AAPL"),系统需先将其归一化为标准ticker,采用三级判断:正则匹配A股/港股/美股代码格式;未命中时查表约20余家高频中文公司名到代码的硬编码映射;仍未命中时用一次结构化输出的LLM调用判断查询意图并抽取ticker,仅在前两级失败时触发。
A股/港股与美股在数据可得性上差异较大,未采用统一接口封装,而是显式路由:数字代码(5-6位)走雪球(pysnowball封装),字母代码走FinancialModelingPrep(FMP)。其中FMP预计算的PE/PB/ROE等比率字段在实测中存在缺失或口径不一致的情况,因此改为从利润表、资产负债表等原始报表数据自行计算,以换取数值口径的可控性。
三、四维金融合规校验(Reviewer):问题定义与迭代过程
这是项目中投入验证工作最多的模块,也是本次说明的重点。
问题背景:金融报告场景对数值准确性的容错度极低,一个被错误引用的PE或净利率可能直接影响报告的可信度,这与通用RAG/写作场景中"表述大致准确即可"的容错标准不同。
初始实现与观察到的问题:早期版本用单次LLM调用同时判断数据准确性、逻辑完整性、合规性、格式规范四个维度。运行内部评测后观察到两个问题:其一,四个维度共享同一次调用的注意力预算,模型对1%量级的数值偏差(如PE45.74写成46.2)命中率低;其二,任一维度处理超时会导致其余三个维度的结果一并丢失。
改进方案:将四个维度拆分为四个独立的、通过asyncio.gather并行执行的检查,单一维度异常不影响其余维度。其中"数据准确性"维度进一步拆分为两层:程序化预检:用正则定位草稿中PE/PB/ROE/净利率等指标数值,与参考数据做浮点比较,相对偏差超过0.5%才标记(阈值用于过滤四舍五入造成的噪声)。LLM语义层兜底,捕捉程序化规则覆盖不到的问题,如单位错误、将同业公司数据误植到目标公司。"格式规范"维度采用类似的程序化预检(标题结构、引用链接存在性)加LLM兜底的组合;"合规性"维度拆分为独立的短prompt,去除数据比对负担后专注于识别"保证收益""无风险"等表述问题。
四、Human-in-the-Loop机制实现
human节点在计划生成后暂停,等待用户对研究大纲提出修改意见。该节点采用 LangGraph 原生的 interrupt() + checkpointer 恢复模式:节点内部调用 interrupt(payload) 暂停图执行并通过 checkpoint 持久化状态,前端反馈到达后由 Command(resume=feedback) 恢复执行。InMemorySaver checkpointer 已接入 StateGraph.compile(checkpointer=...),配合 thread_id 标识会话线程。
改造后的调用链路为:run_research_task 跑到 human 节点触发 interrupt,返回 interrupted 标记并将会话缓存到 _pending_research 注册表;前端发送 human_feedback 消息后,resume_research_task 从缓存取出图实例,以 Command(resume=...) 恢复执行并生成最终报告。反馈触发计划修订时图会再次 interrupt,支持多轮修订循环。