企业级 LLM 实战:在受限环境中基于 Copilot API 构建 ReAct MCP Agent

企业级 LLM 实战:在受限环境中基于 Copilot API 构建 ReAct MCP Agent

在银行等金融 IT 环境中,LLM 应用落地往往面临着严苛的限制。最典型的一道坎是:我们只能使用公司内部提供的 LLM API(如 Copilot API),而这些 API 往往是不完整的。

本文将复盘一次真实的架构演进:当我们的基础模型不支持标准的 Function Calling (bind_tools) 时,如何通过 ReAct 模式Model Context Protocol (MCP),手动构建一个强大的、支持工具调用的智能 Agent。

1. 交互全景图 (Architecture Overview)

在深入代码细节之前,让我们先通过一张时序图来俯瞰整个系统的请求流转过程。

MCP Server (GitHub)Copilot API (BaseChatModel)GithubReactAgent (Expert)MainAgent (Router)PostgreSQLChatServiceChatRouter (FastAPI)前端用户MCP Server (GitHub)Copilot API (BaseChatModel)GithubReactAgent (Expert)MainAgent (Router)PostgreSQLChatServiceChatRouter (FastAPI)前端用户第一阶段:请求接收与上下文加载第二阶段:主 Agent 路由思考第三阶段:子 Agent 执行与工具调用第四阶段:响应持久化POST /chat ("列出我的 repo")初始化 (Inject GithubAgent)stream_chat_response(request, MainAgent)INSERT User Message ("列出我的 repo")SELECT Chat History (limit=20)History Records去重 (Remove Duplicate User Input)astream("列出我的 repo", history)System Prompt (Tools Def) + History + InputChunk: ```json { "action": "delegate_to_github" } ```拦截 JSON,生成友好提示Yield: "[System: Asking GitHub Agent...]"astream("列出我的 repo")System Prompt (GitHub Tools) + InputChunk: ```json { "action": "get_repo_list" } ```拦截 JSONYield: "[Thinking: Calling get_repo_list...]"call_tool("get_repo_list", args)Tool Result (JSON List)Observation: Tool ResultFinal Answer ("这里是您的仓库列表...")Yield Final AnswerYield Final AnswerStream Response Token by TokenINSERT Assistant Message (Full Response)

2. 困境:当 bind_tools 失效

2.1 背景

我们基于公司提供的 Copilot API 封装了一个 LangChain BaseChatModel。基础的对话功能(ainvoke, astream)一切正常。

2.2 遭遇滑铁卢

当我们试图引入工具调用能力(Agentic Workflow)时,按照标准文档调用 llm.bind_tools(tools),却收到了冷冰冰的错误:
NotImplementedError

原因在于:Copilot API(或其内部封装)并没有完全遵循 OpenAI 的 Function Calling 规范,或者我们的封装层无法透传这些参数。

这意味着我们失去了一键构建 Agent 的能力。我们必须寻找另一条路。

3. 破局:回归 ReAct 模式与核心组件设计

既然模型“不懂”原生工具调用,我们就教它用“人话”来调用工具。这正是 ReAct (Reasoning + Acting) 模式的精髓。

为了实现这一目标,我们设计了以下核心组件:

3.1 McpToolConverter: 协议适配器

职责:将 MCP 协议定义的工具(JSON Schema)转换为 LangChain 的 StructuredTool。这确保了我们的代码能够“读懂”MCP Server 提供的任何工具。

# src/tools/mcp_tool_converter.pyclassMcpToolConverter:@staticmethoddefconvert(tool: McpTool)-> StructuredTool:# 动态创建 Pydantic Model,这是 LangChain 验证参数的基础 fields ={}for name, prop in tool.inputSchema["properties"].items():# ... 解析类型和描述 ... fields[name]=(p_type, Field(description=desc)) args_model = create_model(f"{tool.name}Schema",**fields)return StructuredTool.from_function(..., args_schema=args_model)

3.2 ToolCallableAgent: 抽象基类

职责:负责基础设施。它连接 MCP Server,获取工具列表,并负责生成能够“教”会 LLM 使用这些工具的 System Prompt。

关键实现:手动构建工具 Prompt
既然不能用 bind_tools,我们就把工具定义写进 System Prompt 里。

# src/agents/tool_callable_agent.pyclassToolCallableAgent(BaseAgent):asyncdefinitialize(self):# 1. 连接 MCP Server# 2. 获取工具列表# 3. 生成 Prompt 描述 self.tool_definitions = self._format_tool_definitions(self.tools)def_format_tool_definitions(self, tools: List[McpTool])->str: prompt_lines =["You have access to the following tools:\n"]for tool in tools: schema = json.dumps(tool.inputSchema, indent=2) prompt_lines.append(f"Name: {tool.name}\nDescription: {tool.description}\nArguments: {schema}") prompt_lines.append(""" To use a tool, please output a JSON blob wrapped in markdown code block like this: ...json { "action": "tool_name", "action_input": { ... } } ... """)return"\n".join(prompt_lines)

3.3 GithubReactAgent: 领域专家

职责:专注于 GitHub 相关任务。它继承自 ToolCallableAgent,实现了核心的 ReAct Loop

关键实现:手动解析与执行循环
它不依赖 AgentExecutor,而是自己控制循环逻辑。

# src/agents/github_react_agent.pyclassGithubReactAgent(ToolCallableAgent):def_parse_tool_call(self, text:str)->dict|None:# 正则提取 JSON json_match = re.search(r"```json\s*(\{.*?\})\s*```", text, re.DOTALL)return json.loads(json_match.group(1))if json_match elseNoneasyncdef_agent_loop(self, messages: List)-> AsyncIterator[BaseMessageChunk]:"""ReAct Loop: Think -> Parse -> Act -> Observe -> Think"""while turn < MAX_TURNS:# 1. Thinkasyncfor chunk in self.llm_service.llm.astream(messages):yield chunk # 实时流式输出思考过程# 2. Parse & Actif tool_call := self._parse_tool_call(full_response):# 3. Observe tool_result =await self._execute_tool_ephemeral(tool_call['action'], tool_call['action_input']) messages.append(HumanMessage(content=f"Tool Output: {tool_result}"))

3.4 MainAgent: 智能路由器

职责:作为系统的单一入口,负责意图识别和任务分发。

关键实现:动态路由与幻觉抑制
它不直接执行业务逻辑,而是通过 delegate_to_github 这样的“元工具”将任务派发给 GithubReactAgent。我们在调试中发现它容易产生幻觉,因此对其进行了特别强化。

# src/agents/main_agent.pyclassMainAgent(BaseAgent):def__init__(self, llm_service, github_agent): self.tool_mapping ={"delegate_to_github":{"agent": github_agent,"name":"GitHub Agent"}}def_build_system_prompt(self)->str:# 强指令防止幻觉return"""You are a helpful assistant and a router. CRITICAL INSTRUCTIONS: 1. You MUST ONLY use the tools listed above. 2. Do NOT invent or hallucinate new tools. 3. If the user request involves GitHub ..., MUST use `delegate_to_github`. """asyncdef_astream_impl(self,input, chat_history):# ... (流式输出与 JSON 拦截逻辑) ...# 如果检测到 JSON Tool Call,拦截并替换为友好提示if tool_call:yield AIMessageChunk(content=f"\n[System: I will ask the {agent_name} to help...]\n")asyncfor chunk in agent.astream(query):yield chunk 

4. 进阶挑战:调试与修复

解决了“能用”的问题后,我们又遇到了“好用”的问题。

4.1 场景:分步提问引发的血案

用户先问:“列出我的 repo”,Agent 问:“你是谁?”,用户答:“nvd11”。
在这个过程中,我们遇到了两个严重问题:

  1. 重复提问:Agent 似乎忘记了它问过什么,或者把用户的回答重复处理了。
  2. 幻觉:Agent 在调用工具前,自己编造了一堆假的 repo 列表。

4.2 调试与修复

通过 LangSmith Trace,我们发现问题的根源在于我们手动实现的 Loop 和 Prompt 还不够严谨。

修复一:历史记录去重
我们的 ChatService 采用了“先存后读”的策略,导致最新的 User Input 在 chat_history 中出现了一次,作为 input 参数又出现了一次。模型看到两次 “nvd11”,逻辑就乱了。

  • Fix: 在读取历史记录后,如果最后一条与当前输入相同,手动移除它。

修复二:幻觉抑制 (Thinking Suppression)
模型太“热心”了,在输出 JSON 工具调用指令的同时,顺便把“结果”也编出来了。

  • Fix 1 (Prompt): 在 MainAgent System Prompt 中加入 CRITICAL INSTRUCTIONS,严厉禁止 “invent or hallucinate new tools”。
  • Fix 2 (Code): 在流式输出 (astream) 中引入拦截机制。一旦检测到 ```json 开始,就停止向用户输出后续文本。只在工具执行完毕后,由系统生成一条友好的 [System: Calling GitHub...] 提示。

5. 总结

在受限的企业级环境中,我们不能总是依赖最先进、最便捷的 API(如 OpenAI Function Calling)。但这并不意味着我们束手无策。

通过 ReAct 模式,我们用最原始的 Prompt Engineering 和正则解析,手动重建了 Agent 的思考回路。结合 MCP 协议,我们成功将这一能力扩展到了无限的外部工具。

这不仅是一个技术 workaround,更是一种对 LLM 原理深刻理解后的架构创新。它证明了:只要模型具备基本的指令遵循能力(Instruction Following),我们就能构建出强大的 Agent 系统。

Read more

把 Whisper、Moonshine、SenseVoice 统统装进手机:sherpa-onnx 离线语音部署框架,GitHub 10.9K Star

把 Whisper、Moonshine、SenseVoice 统统装进手机:sherpa-onnx 离线语音部署框架,GitHub 10.9K Star

导读: 语音 AI 模型更新很快——Whisper、Moonshine、SenseVoice、FireRedASR、Paraformer,几乎每个月都有新模型发布。但对开发者来说,选好模型只是第一步,真正的工程挑战在后面:怎么把它跑在手机上?嵌入式设备上?浏览器里?怎么接入 NPU 加速?怎么在没有网络的环境下运行? sherpa-onnx 是 next-gen Kaldi 团队开源的语音推理部署框架(GitHub 10.9k stars,Apache 2.0 协议),它的定位很明确:将多种语音模型统一转成 ONNX 格式,部署到各类平台上,支持离线运行。覆盖 12 项语音功能、12 种编程语言、从服务器到嵌入式的多平台支持,最新版 v1.12.29 于

我用Openclaw + Claude搭了一套自动写作系统,每天省3小时

我用Openclaw + Claude搭了一套自动写作系统,每天省3小时

这是我目前最重要的一套AI工作流。从信息获取到发布,几乎不用手动完成。 一、为什么我要搭建这套系统? 信息过载的困境 如果你也在持续关注AI,应该会有同样的感受: 信息太多了。 每天打开 X、公众号、GitHub、技术社区,都会冒出大量新内容。 AI模型更新、工具更新、Agent框架、自动化方案…… 想跟上这些信息,本身就已经是一项工作。 手动写作的低效循环 更别说: * 整理信息 * 找选题 * 写文章 * 配图 * 发布到各个平台 如果全部手动完成,写作就会变成一件非常消耗精力的事。 我一度也在这种状态里: 想持续输出,但写作本身占用了太多时间。 一个关键问题 后来我开始思考一个问题: 如果写作这件事可以被"系统化",会发生什么? 于是,我不再把AI当成写作工具。 而是开始搭一套完整的 AI写作工作流。 二、思路转变:从优化写作到优化流程 大多数人的AI写作方式 大多数人使用AI写作,是这样:

AIGC时代——语义化AI驱动器:提示词的未来图景与技术深潜

AIGC时代——语义化AI驱动器:提示词的未来图景与技术深潜

文章目录 * 一、技术范式重构:从指令集到语义认知网络 * 1.1 多模态语义解析器的进化路径 * 1.2 提示词工程的认知分层 * 二、交互革命:从提示词到意图理解 * 2.1 自然语言交互的认知进化 * 2.2 专业领域的认知增强 * 三、未来技术图谱:2025-2030演进路线 * 3.1 2025年关键突破 * 3.2 2027年技术里程碑 * 3.3 2030年技术愿景 * 四、伦理与治理:构建可信语义化AI * 4.1 动态伦理约束框架 * 4.2 提示词审计系统 * 五、开发者能力升级路线图 * 5.1 核心技能矩阵 * 5.2 典型学习路径 * 结语 * 《驱动AI:

主流 AI 插件 之一的 Copilot 介绍

主流 AI 插件 之一的 Copilot 介绍

Copilot 是微软推出的一款人工智能助手,旨在通过自然语言交互帮助您提升工作效率和创造力,覆盖多平台(网页端、桌面端、移动端、Edge 浏览器等),提供智能问答、内容生成、代码辅助等功能。其核心定位为“日常 AI 伴侣”,旨在通过自然语言交互提升工作与生活效率。         ⚠️ 注意:自 2024 年起,Copilot 已从独立插件全面整合进 GitHub Enterprise 与 Microsoft 365 开发者计划,部分高级功能(如多文件协同编辑、Agent 模式)需订阅 Copilot Pro 或企业版。 一、Copilot 官网与介绍 1.1 Microsoft Copilot • 定位:微软旗下AI助手,适用于工作与生活,支持多场景应用。 • 功能:文本生成、