Full-stack financial AI agent with market data, RAG knowledge retrieval, streaming LLM answers, structured charts, and grounding controls.
一个结合实时行情 API、RAG 知识检索和大语言模型的金融问答系统。支持资产价格查询、涨跌原因分析、金融知识问答,并在架构层面系统性控制回答准确性。
| 能力 | 说明 |
|---|---|
| 全栈可运行 | Next.js 前端 + FastAPI 后端 + Docker Compose,一套仓库跑完整金融问答体验 |
| 多源行情级联 | yfinance、Finnhub、Alpha Vantage、Stooq 逐级降级,降低单一数据源失败影响 |
| RAG + Web 兜底 | 本地金融知识库支持 BM25/向量混合检索,检索失败时可切到 Web 搜索 |
| 结构化金融分析 | 价格、趋势、KPI、对比指标、图表数据和免责声明分离输出 |
| 准确性控制 | 数据校验、数值交叉验证、事实核查和保守措辞,减少幻觉式回答 |
| 流式 Agent 体验 | SSE 实时推送 token、图表、meta 和思考链事件 |
git clone https://github.com/Jiang020609/fin-ai-agent.git
cd fin-ai-agent
cp .env.example .env
# 编辑 .env,填入你自己的 OpenAI 或 OpenRouter Key
docker compose up --build启动后访问 http://localhost:3000。不配置 Finnhub、Alpha Vantage 或搜索 API 也可以运行,系统会使用免费行情源和本地知识库降级能力。
用户浏览器
│
▼
┌──────────────────────────────────────────────────┐
│ Frontend (Next.js + TypeScript) │
│ │
│ InputBar → useChatHistory(状态机) → api.ts(SSE) │
│ │
│ 渲染组件: │
│ MessageBubble KPICards FinancialChart │
│ ThoughtChain TrendBadge ComparisonTable │
│ SourceList EvidenceAnalysis DataFreshness │
└──────────────────────┬───────────────────────────┘
│ SSE: thought/meta/chart/token/done
▼
┌──────────────────────────────────────────────────┐
│ Backend (FastAPI) │
│ │
│ Phase 1 ─ Query Understanding [agent.py] │
│ Session消解 → 查询改写 → Ticker提取 → 意图分类│
│ (正则快速分类 → LLM Function Call fallback) │
│ │
│ Phase 2 ─ Plan Generation [orchestrator.py] │
│ 意图 → PLAN_TEMPLATE → 有序步骤列表 │
│ │
│ Phase 3 ─ Step Execution [steps.py] │
│ 22个@register_step函数, 共享StepContext │
│ fetch → validate → prompt → LLM → assemble │
│ │
│ 底层服务: │
│ ┌────────────┐ ┌─────────────┐ ┌──────────────┐│
│ │ market.py │ │ rag.py │ │ grounding.py ││
│ │ 4源级联 │ │ BM25+Vector │ │ 三层准确性 ││
│ │ +缓存降级 │ │ +LLM Rerank │ │ 控制 ││
│ └─────┬──────┘ └──────┬──────┘ └──────────────┘│
└────────┼───────────────┼────────────────────────-┘
▼ ▼
┌──────────────────────────────────────────────────┐
│ External Services │
│ │
│ Yahoo Finance / Finnhub / Alpha Vantage / Stooq │
│ OpenAI / OpenRouter (LLM + Embedding) │
│ Tavily / SerpAPI (Web Search) │
└──────────────────────────────────────────────────┘
Mermaid 版本(支持渲染的环境可展开)
graph TB
subgraph Frontend["Frontend (Next.js + TypeScript)"]
UI["ChatPanel / InputBar"]
Charts["FinancialChart + KPICards + TrendBadge + DataFreshness"]
Sources["SourceList + DisclaimerBar"]
Thought["ThoughtChain"]
State["useChatHistory (状态机)"]
UI --> State
State -->|SSE 流式| API_Client["api.ts (SSE Parser)"]
end
subgraph Backend["Backend (FastAPI)"]
Router["chat.py (API 路由)"]
Agent["agent.py (Query Understanding)"]
Orchestrator["orchestrator.py (Plan + Execute)"]
Steps["steps.py (22 步骤函数)"]
subgraph Intent["意图分类 (双层)"]
Regex["正则快速分类"]
LLM_Cls["LLM Fallback"]
end
subgraph Tools["工具服务"]
Market["market.py\n4源级联 + 缓存"]
RAG["rag.py\nChromaDB + BM25 混合检索"]
WebSearch["web_search.py\nTavily / SerpAPI"]
Trend["trend.py\n规则化趋势分类"]
end
subgraph Grounding["准确性控制层"]
Validate["数据校验\n(NaN/Inf/负值拦截)"]
FactCheck["事实核查\n(LLM 自检 + 重试)"]
NumCheck["数字交叉验证\n(回答 vs 原始数据)"]
MissingNotice["缺失字段标注"]
end
LLM["llm.py\nOpenAI API 封装"]
end
subgraph External["外部服务"]
Yahoo["Yahoo Finance"]
Finnhub["Finnhub API"]
AlphaV["Alpha Vantage"]
Stooq["Stooq CSV"]
OpenAI["OpenAI / OpenRouter"]
SearchAPI["Tavily / SerpAPI"]
end
API_Client -->|POST /api/chat/stream| Router
Router --> Agent
Agent --> Intent
Agent --> Orchestrator
Orchestrator --> Steps
Steps --> Tools
Steps --> Grounding
Steps --> LLM
Market --> Yahoo
Market --> Finnhub
Market --> AlphaV
Market --> Stooq
LLM --> OpenAI
WebSearch --> SearchAPI
Orchestrator -->|structured_response| Router
Router -->|SSE events| API_Client
API_Client --> Charts
API_Client --> Sources
API_Client --> Thought
- 获取实时/近期价格数据(4 层数据源级联,任一成功即返回)
- 计算 7 日、30 日涨跌幅和区间高低点
- 规则化趋势分类:基于涨跌幅 + 振幅 + 线性斜率,不依赖 LLM 判断
- 返回结构化
data_summary+trend_summary,客观数据与分析严格分离
- 抽取资产和日期引用(支持"1月15日""上周""最近"等表达)
- 获取对应窗口的行情数据
- 搜索相关新闻/事件证据(Tavily / SerpAPI)
- LLM 基于行情 + 证据做归因分析,使用谨慎措辞
- 明确区分:观测数据 / 可能原因 / 证据来源 / 风险说明
- 三级检索降级:ChromaDB 向量 + BM25 混合检索 → 纯 BM25 关键词检索 → Web 搜索兜底
- Embedding 不可用时(如 OpenRouter 不支持 embedding API)自动降级为纯 BM25 模式,确保知识库始终可查
- 文档分块(500 字符/块,80 字符重叠,按 Markdown 层级分割)
- 混合检索:BM25 关键词 + 向量语义 → RRF(Reciprocal Rank Fusion)融合 → LLM Rerank
- 10 篇知识文档(41KB+),覆盖估值指标、财报分析、技术分析、宏观经济、风险管理、衍生品、公司治理、行业分析、中国市场
- 事实核查:LLM 自检回答中的数值是否与参考资料一致,不通过则重试
- 自动提取问题中的多个 Ticker(如"苹果和微软谁更好")
- 计算量化对比指标:7 日/30 日收益率、波动率、夏普比率、最大回撤
- 自动判定各维度优胜者
- LLM 基于量化数据生成结构化对比分析
- 保留最近 6 条消息作为上下文
- Session 状态管理(30 分钟 TTL):记忆当前资产和意图
- Ticker 指代回溯(如先问"阿里巴巴股价",再问"它为什么跌"能自动关联)
- 查询改写:模糊问题自动改写为明确查询(LLM 辅助)
- SSE 实时推送:thought → token → chart → meta → done
- 思维链可视化:展示完整的 Agent 决策过程
- 前端状态机管理:idle → sending → thinking → idle/error
| 层级 | 技术 | 选型理由 |
|---|---|---|
| 前端框架 | Next.js 14 + TypeScript | App Router、SSR/CSR 灵活、类型安全 |
| UI 样式 | Tailwind CSS | 原子化 CSS、快速开发、深色主题 |
| 图表 | Recharts | React 原生、轻量、AreaChart 适合金融走势 |
| 状态管理 | useReducer 状态机 | 严格状态转换、防止并发不一致 |
| 后端框架 | FastAPI (Python) | 异步高性能、自动 OpenAPI 文档、SSE 原生支持 |
| 行情数据 | yfinance + Finnhub + Alpha Vantage + Stooq | 4 源级联、免费、覆盖面广 |
| 向量数据库 | ChromaDB | 本地嵌入、零基础设施依赖 |
| Embedding | text-embedding-3-small | 高质量、低成本、支持 OpenRouter 代理 |
| LLM | GPT-4o-mini / DeepSeek | 性价比高、支持 OpenRouter 统一接入 |
| Web 搜索 | Tavily / SerpAPI | RAG 兜底 + 事件分析证据来源 |
| 部署 | Docker Compose | 前后端一键编排、含健康检查 |
系统采用自研的 Query Orchestrator 框架,将每次请求拆为三个阶段:
| 阶段 | 模块 | 职责 |
|---|---|---|
| Phase 1: Query Understanding | agent.py |
Session 消解 → 查询改写 → Ticker 提取 → 双层意图分类 |
| Phase 2: Plan Generation | orchestrator.py |
意图 → PLAN_TEMPLATE 步骤序列(build_plan()) |
| Phase 3: Step Execution | steps.py |
22 个 @register_step 步骤函数,共享 StepContext,支持 sync/streaming 双模式 |
设计动机:将隐式的代码流程(函数调用顺序 = 执行逻辑)转为显式的步骤序列模板,使流程可配置、可测试、可复用。streaming 执行引擎自动将
assemble_*步骤重排到generate_answer之前,确保 meta/chart 事件先于 token 流发送。
系统采用 5 类意图路由,通过 Query Orchestrator 统一编排为步骤序列:
用户问题
│
├─ Session 消解 + 查询改写
│
├─ Ticker 识别(映射表 + 历史回溯)
│
├─ 意图分类(正则优先 → LLM Function Call fallback)
│
├─ orchestrator.build_plan(intent, assets)
│ 意图 → PLAN_TEMPLATE → 步骤序列
│
├─→ market_data(6 步)
│ fetch_price → validate_data → build_market_prompt →
│ generate_answer → validate_response_numbers → assemble_market_response
│
├─→ market_reasoning(8 步)
│ extract_date → fetch_price → validate_data → search_news →
│ classify_evidence → build_reasoning_prompt → generate_answer →
│ assemble_reasoning_response
│
├─→ knowledge_rag(6 步)
│ rag_search → web_search_fallback → build_knowledge_prompt →
│ generate_answer → fact_check → assemble_knowledge_response
│
├─→ compare(5 步)
│ fetch_prices_multi → compute_comparison → build_compare_prompt →
│ generate_answer → assemble_compare_response
│
└─→ general(3 步)
build_general_prompt → generate_answer → assemble_general_response
第一层:正则快速分类(零成本,零延迟)
| 优先级 | 类别 | 匹配关键词 |
|---|---|---|
| 1 | compare |
对比、比较、vs、哪个更好(需 2+ ticker) |
| 2 | market_reasoning |
为什么、为何、原因、大涨.*原因、why、reason |
| 3 | market_data |
股价、走势、涨跌、多少钱、price、stock |
| 4 | knowledge_rag |
什么是、解释、定义、what is、explain |
compare 优先级最高但需要 2+ ticker;reasoning 优先于 market_data。
第二层:LLM Fallback 分类
正则未命中时调用 LLM(temperature=0, max_tokens=10),返回结果由 _normalize_intent() 归一化到 4 类,兼容旧标签。
所有路由返回统一的 structured_response,由后端程序组装(非 LLM 生成),保证准确性:
{
"response_type": "market_data",
"data_summary": {
"price": 178.25,
"change_7d": 3.52,
"high_7d": 182.0,
"low_7d": 170.1,
"data_source": "yahoo",
"timestamp": "2025-01-15T10:30:00"
},
"trend_summary": {
"label": "uptrend",
"label_cn": "上涨",
"confidence": "high",
"rationale": "7日涨幅 +3.52%,价格斜率向上"
},
"sources": [{"title": "行情数据", "source": "yahoo"}],
"disclaimer": "以上数据来自第三方行情接口...不构成投资建议。"
}采用强制三段结构:
📊 客观数据 — 仅列出 API 返回的真实数字,标注来源和时间
📈 趋势总结 — 明确给出上涨/下跌/震荡判断及依据
💡 补充说明 — 缺失数据明确告知,不编造
核心约束:所有数字必须来自提供的 market_data,不得自行生成价格数据
采用强制四段结构 + 证据约束:
📉 市场表现观察 — 实际涨跌数据(来自 API)
🔍 可能原因分析 — 基于新闻/事件归因,谨慎措辞
📑 证据来源 — 列出引用的新闻标题和出处
⚠️ 风险说明 — 不确定性声明 + 不构成投资建议
核心约束:不臆造、区分事实与分析、证据不足时明确说明
采用四段渐进结构:
📌 核心定义/结论 — 直接回答
📖 展开解释 — 分点阐述
💡 示例(如适用)— 具体例子辅助理解
📑 来源说明 — 标注信息来源
核心约束:优先使用参考资料,无检索结果时标注"基于模型通用知识"
独立的核查 Agent,输出 JSON {passed, issues},核查 LLM 回答中的数值是否与参考资料一致。不通过则注入反馈重试一次。
系统实现了三层准确性防线,整合在独立的 grounding.py 模块中:
┌─────────────────────────────────────────────────────────────┐
│ 第一层:数据源隔离 │
│ │
│ market_data → 数据只来自行情 API,LLM 只解释不编数据 │
│ market_reasoning→ 行情 API + Web 搜索证据,LLM 做归因 │
│ knowledge_rag → RAG 向量检索 + Web 搜索,LLM 做整合 │
│ general → LLM 通用知识(不涉及实时数据) │
├─────────────────────────────────────────────────────────────┤
│ 第二层:检索过滤 │
│ │
│ • RAG 相似度阈值 ≥ 0.3(低于丢弃) │
│ • 行情数据 NaN/Inf/负值拦截(market.py 数据清洗) │
│ • 价格缓存 TTL 60s / 历史缓存 1h │
│ • 降级缓存显式标注 stale + age │
│ • 空结果拦截:API 无数据时直接返回提示,不调用 LLM │
├─────────────────────────────────────────────────────────────┤
│ 第三层:输出约束 │
│ │
│ • Prompt 约束:"所有数字必须来自 market_data" │
│ • 事实核查:LLM 自检回答 vs RAG 来源(不通过则重试) │
│ • 数字交叉验证:回答中的数字 vs market_data(无需额外LLM) │
│ • 缺失字段显式标注:注入"无法获取"提示到 Prompt │
│ • structured_response 的 data_summary 由程序组装 │
│ • 原因分析强制使用谨慎措辞:"可能原因""或与…有关" │
└─────────────────────────────────────────────────────────────┘
趋势判断使用纯规则模块,不依赖 LLM:
| 指标 | 来源 | 作用 |
|---|---|---|
| 涨跌幅 | API 计算 | 主判据:> +2% 上涨,< -2% 下跌 |
| 振幅 | (high-low)/low | 窄幅涨跌 + 高振幅 → 震荡 |
| 斜率 | 最小二乘法 | ±2% 内用斜率区分微趋势 |
输出带 confidence 分级(high / medium / low),前端据此展示不同样式。
| 数据类型 | 来源 | 刷新策略 | 备注 |
|---|---|---|---|
| 实时股价 | yfinance → Finnhub → Alpha Vantage → Stooq | 缓存 60s | 4 源级联,任一成功即返回 |
| 历史行情 | yfinance → Stooq | 缓存 1h | 7 日 / 30 日窗口 |
| 金融知识 | 本地 .md/.txt/.pdf → ChromaDB | 启动时加载 | 10 篇文档,41KB+,涵盖估值/财报/技术分析/宏观/风险/衍生品/行业/中国市场 |
| 新闻证据 | Tavily / SerpAPI | 实时搜索 | 用于原因分析链路 |
| LLM | OpenAI / OpenRouter | 按需调用 | 支持 GPT-4o-mini / DeepSeek |
本项目不随仓库分发第三方行情或新闻数据;运行时会根据用户配置调用对应服务。使用者需自行遵守 Yahoo Finance、Finnhub、Alpha Vantage、Stooq、Tavily、SerpAPI、OpenAI / OpenRouter 等第三方服务的条款。行情与新闻数据可能延迟或不完整,结果仅供学习和演示,不构成投资建议。
fin-ai-agent/
├── backend/
│ ├── app/
│ │ ├── main.py # FastAPI 入口 + 生命周期
│ │ ├── routers/
│ │ │ └── chat.py # API 路由(chat + stream)
│ │ ├── services/
│ │ │ ├── agent.py # Query Understanding:意图分类 + Session 消解
│ │ │ ├── orchestrator.py # Plan Generation + Execution Engine
│ │ │ ├── steps.py # 22 个步骤函数(@register_step 注册)
│ │ │ ├── market.py # 行情服务:4源级联 + 缓存 + 重试 + 清洗
│ │ │ ├── rag.py # RAG 检索:ChromaDB + BM25 混合 + LLM Rerank
│ │ │ ├── llm.py # LLM 封装:流式/非流式 + 意图分类
│ │ │ ├── compare.py # 多资产对比:Sharpe/MaxDrawdown 计算
│ │ │ ├── web_search.py # Web 搜索:Tavily / SerpAPI
│ │ │ ├── news_classifier.py # 证据分类:关键词驱动的归因分类
│ │ │ ├── grounding.py # 准确性控制层:事实核查 + 数据校验
│ │ │ ├── trend.py # 趋势分析:规则化分类(涨跌幅+振幅+斜率)
│ │ │ ├── session.py # Session 状态管理(30分钟 TTL)
│ │ │ └── metrics.py # 监控指标收集器
│ │ ├── prompts/
│ │ │ └── templates.py # Prompt 模板(行情/原因分析/RAG/核查/分类)
│ │ ├── models/
│ │ │ └── schemas.py # Pydantic 模型 + StructuredResponse
│ │ └── utils/
│ │ ├── ticker_map.py # 公司名/别名 → Ticker 映射(40+)
│ │ ├── date_extract.py # 日期提取 + 搜索 query 构造
│ │ └── sanitize.py # 输入安全:长度限制 + 注入检测
│ ├── data/ # 金融知识库文档(.md / .txt)
│ └── requirements.txt
├── frontend/
│ ├── src/
│ │ ├── app/page.tsx # 主页面:Header + Messages + InputBar
│ │ ├── components/
│ │ │ ├── MessageBubble.tsx # 消息气泡:Markdown + 结构化卡片
│ │ │ ├── FinancialChart.tsx # 走势图:Recharts AreaChart
│ │ │ ├── KPICards.tsx # KPI 卡片:价格/涨跌/PE/市值
│ │ │ ├── DataFreshness.tsx # 数据更新时间标注
│ │ │ ├── TrendBadge.tsx # 趋势标签:方向 + 置信度 + 依据
│ │ │ ├── ThoughtChain.tsx # 思维链:可折叠推理步骤
│ │ │ ├── SourceList.tsx # 来源列表:页码徽章 + 相关度指示器
│ │ │ ├── DisclaimerBar.tsx # 风险声明条
│ │ │ ├── ExampleQuestions.tsx # 示例问题卡片
│ │ │ └── InputBar.tsx # 输入框
│ │ ├── lib/
│ │ │ ├── api.ts # API 客户端 + SSE 解析 + 超时保护
│ │ │ ├── useChatHistory.ts # 状态机 Hook + localStorage 持久化
│ │ │ ├── useServerStatus.ts # 服务健康检查(15s 轮询)
│ │ │ └── useColorScheme.ts # 涨跌配色切换(中国/美国)
│ │ └── types/
│ │ └── chat.ts # TypeScript 类型(含 StructuredResponse)
│ └── package.json
├── .env.example # 环境变量模板
├── docker-compose.yml # Docker 编排
└── README.md
- Python 3.10+
- Node.js 18+
- OpenAI API Key(或 OpenRouter Key)
git clone <repo-url>
cd fin-ai-agent
cp .env.example .env
# 编辑 .env,填入 API Key.env 关键配置:
# LLM(二选一)
# 方案 A:OpenAI 直连
OPENAI_API_KEY=<your-openai-api-key>
OPENAI_BASE_URL=https://api.openai.com/v1
OPENAI_MODEL=gpt-4o-mini
# 方案 B:OpenRouter(可用 DeepSeek 等模型)
OPENAI_API_KEY=<your-openrouter-api-key>
OPENAI_BASE_URL=https://openrouter.ai/api/v1
OPENAI_MODEL=deepseek/deepseek-chat-v3-0324
EMBEDDING_MODEL=openai/text-embedding-3-small
# 行情数据(可选,不填仅用 yfinance)
FINNHUB_API_KEY=
ALPHA_VANTAGE_API_KEY=
# Web 搜索(可选,用于原因分析和 RAG 兜底)
SEARCH_API_KEY=
SEARCH_PROVIDER=tavilycd backend
python3 -m venv ../.venv
source ../.venv/bin/activate
pip install -r requirements.txt
uvicorn app.main:app --reload --port 8000首次启动会自动构建 ChromaDB 向量索引(需要
data/目录有文档)。
cd frontend
npm install
npm run dev
# 打开 http://localhost:3000docker compose up --build
# 前端:http://localhost:3000
# 后端:http://localhost:8000
# 健康检查:http://localhost:8000/health- Query Orchestrator 架构:意图 → 步骤序列模板 → 统一执行引擎(sync + streaming 双模式)
- 22 个注册步骤函数:通过
@register_step装饰器,可独立测试、动态组合 - SSE 流式输出(打字机效果 + 思维链实时更新)
- 4 源级联行情(yfinance → Finnhub → Alpha Vantage → Stooq)
- 多轮对话(6 条上下文 + Ticker 指代回溯 + Session 状态管理)
- 查询改写:模糊/指代性问题自动改写为明确查询
- 三层准确性控制(数据源隔离 + 检索过滤 + 输出约束)
- 结构化回答(data_summary / trend_summary 程序组装,非 LLM 生成)
- 多资产对比分析 + 夏普比率 + 最大回撤
- RAG 混合检索(BM25 + 向量 + RRF 融合 + LLM Rerank)
- 知识库 10 篇文档(41KB+):估值指标、财报分析、技术分析、宏观经济、风险管理、衍生品、公司治理、行业分析、中国市场
- 引用溯源:RAG 来源附带 页码 和 相关度评分
- 新闻证据分类:关键词驱动的自动归因(财报/政策/宏观/行业/公司)
- Docker 一键部署
- 实时财报接入:对接 SEC EDGAR / 交易所公告 API,自动解析最新财报
- 缓存升级:生产环境替换为 Redis,支持分布式和 TTL 精细管理
- 监控增强:对接 Prometheus + Grafana,基于审计日志的延迟/错误率看板
- 日期窗口定位:对特定日期请求,从历史行情中精确提取该日前后数据
- 多语言:前端 i18n + Prompt 自适应语言
- 更多资产类型:加密货币、外汇、商品期货
欢迎提交 issue 和 pull request。提交前请阅读 CONTRIBUTING.md,并确认没有提交 API Key、.env、缓存、日志、私有数据或生成的向量数据库。
后续方向见 ROADMAP.md。当前最适合参与的方向是截图/GIF demo、英文文档、更多数据源适配和生产级缓存。
如果发现安全问题,请不要直接公开到 issue,详见 SECURITY.md。
本项目基于 ISC License 开源,详见 LICENSE。