内容
# MCP 银行智能层(实验室 1 + 实验室 2)
该项目将一大堆金融文件转化为 AI 生成的**投资简报**。它分为两个阶段:
- **实验室 1 — RAG + 多智能体(AutoGen):** 一个 `ResearchAgent` 从 ChromaDB 矢量存储中检索数据并撰写简报,一个 `CriticAgent` 审查简报,直到它符合质量检查清单。
- **实验室 2 — MCP 银行智能层:** 我们将实验室 1 的复杂检索逻辑暴露为一个干净的 **MCP 服务器**,提供可重用的“银行工具”。然后,LangChain + Ollama 智能体作为 **MCP 客户端**,在运行时发现并调用这些工具来构建简报 — 并重用实验室 1 的 `CriticAgent` 进行最终审查。
实验室 2 **重用实验室 1 的数据**(`chroma_db/`)。您不需要重新运行 `ingest.py`。
---
## 1. 什么是 MCP,为什么它很重要?
**MCP(模型上下文协议)** 是 Anthropic 的一个开放标准,它定义了 **AI 应用程序如何与外部工具和数据交互**。可以将其视为一个通用适配器 — 就像 AI 工具的 USB-C 接口。
没有 MCP,每个智能体都硬编码自己的数据访问。在实验室 1 中,智能体直接访问 ChromaDB。如果一个新团队想要一个贷款顾问或欺诈分析师,他们必须复制和修改整个代码库。
有了 MCP,数据/工具逻辑位于一个 **标准服务器接口** 后面:
| 概念 | 它是什么 | 在本实验室中 |
| --- | --- | --- |
| **MCP 服务器** | 通过标准协议公开 **工具**(类型化函数) | `server.py` — 4 个银行工具 + 1 个奖励工具 |
| **MCP 客户端** | 在运行时发现并调用这些工具 | `client.py` — LangChain/Ollama 智能体 |
| **传输** | 客户端和服务器之间的通道 | `stdio`(客户端作为子进程启动服务器) |
| **工具** | 一个类型化、文档化的函数,LLM 可以调用 | `search_financials`, `get_company_profile`, ... |
**为什么这很强大:** 智能体代码永远不会命名数据库。明天,您可以通过编辑 `server.py` 将 ChromaDB 替换为实时的彭博资讯订阅源 — `client.py` 中的智能体不会更改。工具在任何 MCP 兼容的客户端(Claude Desktop、Cursor、您自己的智能体等)之间变得 **模块化和可共享**。
### 关键思想:谁思考 vs. 谁获取
- **服务器工具故意设计得“ dumb”**。它们仅检索和处理数据并返回 JSON。它们 **永远不会调用 LLM**。
- **客户端智能体进行思考**。LLM 查看可用的工具,决定调用哪些工具(这 **不是** 硬编码的),读取 JSON 结果并撰写简报。
---
## 2. 架构
### 实验室 1 流水线(RAG + AutoGen)
```
data/financial/*.txt
│ ingest.py (运行一次)
▼
┌──────────────┐ retriever.py ┌──────────────┐ APPROVED? ┌──────────┐
│ ChromaDB │ ───────────────────────►│ ResearchAgent│ ◄───────────► │ Critic │
│ financial_docs│ (语义检索) │ (草稿) │ 反馈 │ Agent │
└──────────────┘ └──────────────┘ └──────────┘
│
▼
output/brief_{company}.txt
```
### 实验室 2 流水线(MCP)
```
┌──────────────────────── client.py (MCP 客户端) ───────────────────────┐
│ │
--company Apple ─┤ LangChain ReAct 智能体(Ollama:llama3.1) │
│ │ “应该调用哪个工具?” (LLM 决定,不是硬编码) │
│ ▼ │
│ langchain-mcp-adapters ──── stdio ───► server.py (MCP 服务器) │
│ ▲ │ @mcp.tool()函数 │
│ │ JSON 工具结果 ▼ │
│ └─────────────────────────────── 读取 chroma_db/financial_docs │
│ │ (重用 retriever.py) │
│ ▼ │
│ 草稿简报 ──► 实验室 1 CriticAgent (一轮审查) ──► 修订 │
│ │
└──────────────────────────┬───────────────────────────────────────────┘
▼
output/mcp_brief_{company}.txt
```
---
## 3. MCP 工具(`server.py`)
所有工具都使用 `@mcp.tool()` 定义,具有类型化参数,返回 **JSON 可序列化字典**,并在输入错误时返回 `{"error": ...}` 而不是崩溃。
| 工具 | 签名 | 它做什么 |
| --- | --- | --- |
| `search_financials` | `(query: str, company: str, max_results: int)` | 公司金融文档的语义搜索。 |
| `get_company_profile` | `(company: str)` | 聚合元数据:文档计数、可用年份、文档类型。 |
| `compare_companies` | `(companies: list[str])` | 两家公司的并排财务摘要。 |
| `generate_risk_flags` | `(company: str)` | 扫描风险相关文档并返回标记的风险关键词。 |
| `get_market_news` *(奖励)* | `(company: str)` | 通过 DuckDuckGo 获取最多 3 条最近的新闻标题(网络,不是 LLM)。 |
缺少公司 → `{"error": "company not found", "company": "..."}`。
> 注意:`stdio` 使用 stdout 作为协议通道,因此 `server.py` 仅记录到 **stderr** — 它从不打印到 stdout。
---
## 4. 项目结构
```
MCP/
├── data/financial/*.txt # 实验室 1 源文档
├── ingest.py # 实验室 1 — 构建 chroma_db(运行一次,不要重新运行)
├── chroma_db/ # 实验室 1 矢量存储(被实验室 2 重用)
├── retriever.py # 实验室 1 — 语义检索(被 server.py 重用)
├── agents.py # 实验室 1 — ResearchAgent + CriticAgent (CriticAgent 被重用)
├── main.py # 实验室 1 — AutoGen 流水线入口点
├── server.py # 实验室 2 — 新增:MCP 服务器(4 个工具 + 奖励工具)
├── client.py # 实验室 2 — 新增:LangChain MCP 客户端 + CriticAgent
├── output/ # 简报写入此处
├── requirements.txt
└── README.md # 本文件
```
---
## 5. 设置
**先决条件**
1. Conda 环境 `agentic` 已激活:`conda activate agentic`。
2. [Ollama](https://ollama.com) 在本地运行并拉取模型:
```bash
ollama pull llama3.1:latest
ollama pull nomic-embed-text:latest
```
3. 实验室 1 已经 ingest(`chroma_db/` 存在)。如果没有,请运行 `python ingest.py` **一次**。
**安装实验室 2 依赖项**
```bash
pip install mcp langchain langchain-community langchain-mcp-adapters langchain-ollama langgraph python-dotenv
# 或简单地:
pip install -r requirements.txt
```
---
## 6. 如何运行
**实验室 1(AutoGen):**
```bash
python main.py --company Apple --year 2024
```
**实验室 2(MCP):**
```bash
python client.py --company Apple
```
`client.py` 自动将 `server.py` 作为子进程启动 — 您不需要自己启动服务器。
**快速服务器烟雾测试** (可选 — 它将等待 stdin 上的客户端):
```bash
python server.py # Ctrl+C 停止
```
输出写入 `output/mcp_brief_{company}.txt`。
可用公司:**Apple, Amazon, Microsoft, Nvidia, Tesla** (年份 2023–2024)。
---
## 7. 如何满足约束条件
- **服务器和客户端是单独的文件** — `server.py` 和 `client.py`。
- **工具从不调用 LLM** — 它们只查询/聚合 ChromaDB 并返回 JSON。
- **工具路由不是硬编码的** — ReAct 智能体中的 LLM 决定调用哪些 MCP 工具。
- **重用现有的 `financial_docs` 集合** — 不会创建新的 ChromaDB;`compare_companies` 甚至重用实验室 1 的 `retriever.py`。
- **CriticAgent 在保存之前运行** — `client.py` 为每个简报调用实验室 1 的 `CriticAgent` 进行一轮审查。
- **错误返回 JSON** — 无效输入产生 `{"error": ...}`,而非未处理的异常。
---
## 8. 故障排除
**`ModuleNotFoundError: No module named 'autogen'`**
实验室 1 代码使用 **经典** AutoGen API。PyPI 名称 `pyautogen` 现重定向到 Microsoft 的新 `autogen-agentchat` 包,**不** 包含 `autogen` 模块。请安装经典 API 分叉:
```bash
pip install ag2
python -c "import autogen; from autogen import AssistantAgent; print('classic AutoGen OK')"
```
**简报充满了数据中没有的数字(幻觉)**
这意味着 LLM **没有** 实际调用 MCP 工具 — 它在文本中描述了调用并编造了答案。`client.py` 现在打印 **LLM 实际调用的 MCP 工具:...**,如果列表为空,则会发出警告。
确认 Ollama 工具调用是否有效:
```bash
python diagnose_tools.py
```
- 如果 `TOOL_CALL` 为空,则您的 Ollama 服务器和/或 `langchain-ollama` 太旧,无法发出结构化的工具调用。请升级两者:
```bash
pip install -U langchain-ollama langchain-core langgraph
# 更新 Ollama 服务器本身(Linux):
curl -fsSL https://ollama.com/install.sh | sh
ollama --version # 需要具有工具调用支持的最新版本
```
- 工具调用需要一个具有工具功能的模型。`llama3.1` 支持它;如果仍然不稳定,请尝试 `ollama pull qwen2.5:7b-instruct` 并在 `client.py` 中设置 `MODEL`。
**`create_react_agent ... LangGraphDeprecatedSinceV10`**
无害警告 — 它仍然有效。(它将在未来主要版本中移动到 `from langchain.agents import create_agent`。)
---
## 9. 奖励 / 扩展
- ✅ 使用 DuckDuckGo 实现 `get_market_news()`。
- 💡 通过 **SSE** 传输公开服务器而不是 stdio。
- 💡 添加一个 `portfolio_summary` 工具,依次调用 `compare_companies` 和 `generate_risk_flags` 以获取公司列表。