LangChain Agent 实战接入

前言

上一篇我们论证了 AI 产品为什么需要内置本地 Agent !

现在我们来聚焦"怎么做" ------ 从架构设计到代码接入,输出我们的落地方案。

一个核心场景:如何在应用的 AI 问答模块,接入一个基于 LangChain / LangGraph 的本地 Agent Runtime?


一、整体架构:五层分离

五层职责:

层级 技术选择 职责
UI 交互层 React + 流式 Hook 对话界面、审批卡片、结果展示
基础 Agent 层 LangChain createAgent() 通用问答与工具调用循环
工作流编排层 LangGraph StateGraph 复杂任务、检查点、中断恢复
工具接入层 自研 Tool Adapter 屏蔽数据源、本地命令、第三方差异
宿主层 Electron Main + Local Service 密钥安全、进程管理、随包发布
  • 前端不要直接去调模型,用服务封装一个本地 Agent Runtime
  • API Key 不能暴露给前端,工具执行需要统一收口,后续加能力不用推翻前端
  • LangChain 用来快速起步,LangGraph 用来做复杂编排,工具层基于业务扩展去封装

二、接入步骤

Step 1:建立本地 Agent 服务

新建 ai-agent-server workspace,基于 Hono 框架提供 HTTP 服务:

typeScript 复制代码
// apps/ai-agent-server/src/server.ts
import { Hono } from 'hono';
import { serve } from '@hono/node-server';

export const createAiAgentServer = async (config) => {
  const app = new Hono();

  // 健康检查
  app.get('/health', (c) => c.json({ status: 'ready', model: config.model }));

  // 问答接口(Step 2 实现)
  registerChatRoutes(app, config);

  const server = serve({ fetch: app.fetch, hostname: config.host, port: config.port });
  return { origin: `http://<equation>{config.host}:</equation>{server.address().port}`, close: () => ... };
};

关键点

  • 服务跑在 127.0.0.1:39321,只监听本地
  • API Key 只存在服务端环境变量,不进前端 bundle

Step 2:接入 LangChain createAgent

最小 Agent 接入------先做普通问答,工具后续再加:

typeScript 复制代码
// apps/ai-agent-server/src/agent/createHomeAiQaAgent.ts
import { createAgent } from 'langchain';
import { ChatOpenAI } from '@langchain/openai';

export const createHomeAiQaAgent = (config) => {
  const model = new ChatOpenAI({
    apiKey: config.apiKey,
    model: config.model,
    temperature: 0.2,
    configuration: { baseURL: config.baseUrl },
  });

  return createAgent({
    model,
    tools: [],  // 先不加工具,二期再扩展
    systemPrompt: '你是本地 AI 助手...',
    name: 'home_qa_agent',
  });
};

SSE 流式接口:

typeScript 复制代码
// apps/ai-agent-server/src/routes/chat.ts
export const registerChatRoutes = (app, config) => {
  const agent = createHomeAiQaAgent(config);

  app.post('/api/agent', async (c) => {
    const body = await c.req.json();
    const stream = await agent.stream(
      { messages: body.messages },
      { encoding: 'text/event-stream', streamMode: ['values', 'updates', 'messages'] },
    );
    return new Response(stream, {
      headers: { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache' },
    });
  });
};

Step 3:Electron 托管进程

让 Agent 服务随应用自动启动。开发态用 pnpm dev,打包态用 fork:

typeScript 复制代码
// electron/src/main/processManager/aiAgentProcess/index.ts
export const startAiAgentProcess = () => {
  if (app.isPackaged) {
    // 打包态:fork 打包后的 js 文件
    aiAgentProcess = childProcess.fork(path.join(__dirname, 'aiAgentServer.js'), []);
  } else {
    // 开发态:pnpm 启动 dev server
    aiAgentProcess = childProcess.spawn('pnpm', ['--filter', 'ai-agent-server', 'dev'], { env: { ...process.env } });
  }
};

前端通过 LinkService 获取运行时状态:

typescript 复制代码
// 前端调用
const runtime = await window.LinkService.request('v1/aiAgent/getRuntime');
// runtime = { ready: true, status: 'ready', baseUrl: 'http://127.0.0.1:39321' }

Step 4:前端面板接入

右侧副面板使用 LangGraph SDK 的 useStream Hook:

typeScript 复制代码
// apps/src/features/home/components/AiQaPanel/index.tsx
import { FetchStreamTransport, useStream } from '@langchain/langgraph-sdk/react';

const AiQaPanel = ({ runtime }) => {
  const transport = useMemo(() => new FetchStreamTransport({
    apiUrl: `${runtime.baseUrl}/api/agent`,
  }), [runtime.baseUrl]);

  const stream = useStream({ transport });

  return (
    <section>
      <header><h2>AI 问答</h2></header>
      {/* 消息列表 */}
      <div>{stream.messages.map(renderMessage)}</div>
      {/* 输入框 */}
      <input value={input} onChange={(e) => setInput(e.target.value)} />
      <button onClick={() => stream.submit({ messages: [{ type: 'human', content: input }] })}>发送</button>
    </section>
  );
};

Step 5:权限审批机制

Agent 一旦能执行本地命令,就必须有权限审批。推荐三层模型

风险等级 典型操作 默认策略
low 读笔记、读线程上下文 自动执行
medium 新建文档、导出 PDF 首次确认,可记住
high Shell 命令、发消息 必须逐次审批
critical 删除文件、批量外发 默认拒绝

用 LangGraph 的 interrupt() 实现审批中断:

arduino 复制代码
// Agent 调用高风险工具时,interrupt 等待用户审批
// 用户点击"允许"后,resume 继续执行

前端渲染审批卡片:

typeScript 复制代码
<ApprovalCard
  title="读取文件"
  reason="需要读取首页实现文件"
  riskLevel="low"
  onApprove={() => AiAgentApis.approveRequest(requestId)}
  onDeny={() => AiAgentApis.denyRequest(requestId)}
/>

不是所有工具都该暴露给模型,Tool Registry 是能力全集,Agent 每次可见的是权限过滤后的子集。


三、记忆管理:短期和长期必须分开

这是 Agent 和普通 Chat 最大的差异之一。

短期记忆(Thread Memory)

基于 thread_id 的状态持久化,保存当前会话的:

  • 消息历史
  • 任务计划
  • 已调用工具结果
  • 中断点

用 LangGraph 的 checkpointer 实现,开发阶段用 MemorySaver,生产用 PostgresSaver

长期记忆(User Memory)

跨会话持久保存:

  • 用户偏好(输出风格、常用配置)
  • 授权偏好
  • 常用操作模板

关键判断:不是所有内容都该记住。只保存三类:用户显式要求记住的、高频重复出现对后续有帮助的、权限与偏好类配置。

短期记忆保留过程,长期记忆保留结论。


四、任务编排:简单 vs 复杂

简单任务(普通问答、单工具调用)→ 直接用 Agent ReAct 循环。

复杂任务 (多步骤、有审批、多阶段产物)→ 用 LangGraph StateGraph 显式编排。 推荐的任务编排层次:

层次 作用
Intent Classifier 识别意图:问答 / 查询 / 生成 / 执行 / 工作流
Planner 拆解目标为可执行步骤
Supervisor 调度子代理、审批、重试
Executor 执行工具或子图
Verifier 检查结果是否完整

简单问题让 Agent 自由思考,复杂任务让 Graph 显式编排。


五、错误处理与并发

错误分类

错误类型 示例 处理
model_error 超时、429 自动重试 / 模型降级
tool_error 参数错误 让 Agent 自修复
permission_error 审批被拒 中断提示用户
sandbox_error 路径非法 终止并诊断

重试原则

  • 幂等是重试前提:涉及副作用的动作必须带幂等键
  • 非幂等动作绝不重试:已发送的消息、已执行的外发动作
  • LLM 429 用指数退避 + 模型降级

六、总结

LangChain Agent 接入的核心,不是"接个模型"这么简单。从业务层面看,需要:

  1. 分离架构:UI、Agent、编排、工具、宿主各司其职
  2. 分步接入:启动服务 → 创建Agent → 进程托管 → UI 面板 → 权限审批
  3. 权限、记忆、沙箱

但更核心的是:Agent 只是一套执行、编排任务的框架,如何让大模型更好的工作。离不开我们的工程化:

  • prompt 调优
  • RAG 向量工程
  • 模型的微调优
  • 业务数据集评测,人工复核指标

这些,将是更深远的课题!

相关推荐
kyriewen10 小时前
Godot 全面封杀 Vibe Coding,作为每天用 AI 写代码的人,我反而觉得这是件好事
前端·ai编程·claude
手写码匠11 小时前
手写 AI 上下文压缩系统:从零实现 Prompt 压缩与选择性上下文管理
人工智能·深度学习·算法·aigc
plainGeekDev12 小时前
Claude Code 的非交互模式:一条命令当 grep 用
ai编程·claude
ZzT12 小时前
如何低成本让 Agent 自进化
openai·ai编程
谭光志12 小时前
工具塞满上下文窗口怎么办?深度拆解 AI Agent Tool Search 按需加载实现原理
前端·后端·ai编程
foggyprojects12 小时前
从0开始,一句话启动AI DataAgent
后端·数据分析·ai编程
闯荡13 小时前
summary 上下文总结机制
agent
程序员老刘13 小时前
老刘给大家道个歉
flutter·ai编程
掉鱼的猫13 小时前
Agent Harness 实战指南:构建生产级 AI Agent 的"马具"框架
java·llm·aigc
带刺的坐椅13 小时前
Agent Harness 实战指南:构建生产级 AI Agent 的"马具"框架
java·ai·llm·agent·solon-ai