ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

LangGraph+FastAPI构建可审计AI编码助手

LangGraph+FastAPI构建可审计AI编码助手 1. 项目概述一个真正能“动手干活”的AI编码助手长什么样最近在几个技术群和开源社区里总有人问“现在市面上的AI编程助手到底能不能真的帮我把代码跑起来而不是只给个思路或者半截代码”这个问题戳中了痛点——我们不是缺答案是缺能闭环执行的答案。羲和XiheAgent就是冲着这个目标做的它不满足于当一个“高级搜索引擎”或“代码补全器”而是要成为你IDE旁边那个沉默但靠谱的搭档——你告诉它“把用户登录日志导出成Excel按日期分表发到运维邮箱”它就真去调API、读数据库、生成文件、发邮件全程不卡壳、不甩锅、不让你手动补漏。这不是概念演示而是基于FastAPI搭建服务骨架、用LangGraph构建可中断可回溯的执行流程、以DeepAgents的子智能体subagents分工协作实现复杂任务拆解的真实工程实践。它解决的不是“怎么写得更优雅”而是“怎么让AI真正接管一整条开发流水线里的重复性操作”。适合两类人一是每天被CRUD、脚本编写、环境配置压得喘不过气的后端/运维工程师想把机械劳动交给AI二是正在探索LLM Agent落地路径的技术负责人或架构师需要看到一个轻量、可控、可审计、不黑箱的参考实现。它不追求参数规模最大但每一步决策都可追溯、每个子任务都可单独调试、每次失败都能准确定位到是哪个子智能体在哪个环节出了问题——这才是工程化落地的前提。我去年带团队做内部DevOps自动化平台时试过直接调用大模型API写脚本结果发现模型给出的SQL语法有错、生成的curl命令少了个-H头、发邮件的SMTP配置硬编码了测试密码……最后反而比手写花的时间多。后来我们推倒重来把任务执行切成“规划→验证→执行→校验”四个阶段每个阶段由专用子智能体负责用LangGraph串起来再用FastAPI暴露成标准HTTP接口。羲和就是这套思路的凝练产物。它不是炫技是为了解决真实世界里“AI写的代码不敢直接上线”这个根本矛盾。下面我会从设计逻辑、核心模块、实操细节到踩坑记录一层层剥开它的实现肌理。2. 整体架构设计为什么必须用LangGraph而不能只靠LangChain2.1 传统LangChain方案的致命短板很多人第一反应是“用LangChain链式调用不就行了吗PromptLLMTool Calling一套组合拳打完。”我试过也带着团队跑了三个月POC结论很明确纯LangChain链式结构在复杂任务面前会迅速失控。举个具体例子当用户指令是“分析生产环境MySQL慢查询日志找出TOP5耗时SQL生成优化建议并更新监控看板”LangChain默认的SequentialChain会怎么做它大概率会1让LLM读日志文本 → 2让LLM提取SQL → 3让LLM写优化建议 → 4让LLM调用看板API。表面看流程完整但实际运行中问题爆发点极多状态不可控第2步提取的SQL如果漏掉一条第3步的建议就失去依据但LangChain链本身没有机制去检测“提取结果是否完整”只能硬着头皮往下走错误无法隔离第4步调用看板API失败比如token过期整个链就断了你得从头重跑而前3步的计算结果尤其是日志分析完全浪费调试成本爆炸你想查“为什么优化建议写得离谱”得翻日志看LLM输入输出但输入里混着原始日志、历史对话、系统提示词根本分不清是prompt写得不好还是模型理解偏差还是工具返回数据格式异常。这就像让一个新手司机连续完成“倒车入库→侧方停车→坡道起步→隧道灯光切换”中间任何一步出错整套动作就得重来且无法回退到上一个成功节点。2.2 LangGraph的核心价值状态机思维替代线性流水线LangGraph的本质是把AI任务执行建模成一个有状态的图Stateful Graph。它强制你定义清楚节点Node每个节点是一个独立函数比如“日志解析节点”、“SQL验证节点”、“邮件发送节点”它们只关心自己的输入输出不依赖上下文边Edge边不是固定走向而是由条件函数Conditional Edge动态决定比如“如果SQL验证通过→跳转到优化建议节点否则→跳转到重试节点”状态State所有节点共享一个可变字典State里面存着当前任务的全部上下文原始指令、中间产物如提取的SQL列表、错误信息、执行历史。每个节点只读写自己关心的字段互不污染。这种设计带来的实际好处是颠覆性的可中断可恢复任务跑到一半服务器宕机重启后从最后一个成功节点继续不用重跑错误精准定位日志里直接看到“节点[sql_validation]返回False原因检测到3条SQL中2条语法错误”不用猜子任务可替换想把“邮件发送”换成“企业微信通知”只需重写对应节点函数图结构完全不动人工介入友好运维人员可以直接调用state.get(pending_sqls)拿到待处理SQL列表手动修正后塞回去AI接着干。提示LangGraph不是LangChain的升级版而是范式转换。LangChain像Excel公式链A1B1C1, B1D1*2一环错全盘崩LangGraph像工厂流水线每个工位有独立质检台不合格品自动分流返工系统韧性完全不同。2.3 DeepAgents的子智能体Subagents如何与LangGraph协同DeepAgents本身不是一个独立框架而是LangGraph生态中一种任务分解模式的最佳实践封装。它的核心思想是把一个大任务按职责切分成多个“子智能体”每个子智能体专注一个领域拥有专属的Prompt、专属的Tool集合、专属的失败重试策略。在羲和里我们定义了四个基础子智能体子智能体核心职责专属Tool示例失败重试策略Planner将用户自然语言指令拆解为可执行步骤生成任务计划树list_available_tools()最多重试2次超时则降级为人工干预提示CodeGen根据计划生成可运行代码Python/SQL/Shellexecute_python_code(),run_sql_query()语法检查失败时自动添加ast.parse()校验并提示具体错误行Validator对生成物进行逻辑/安全/合规性校验check_sql_injection(),validate_email_format()发现高危风险如DROP TABLE立即终止不进入执行阶段Executor调用真实系统API或执行本地命令send_email(),call_prometheus_api()网络超时自动指数退避3次失败后标记为需人工确认关键在于这些子智能体不是并行乱跑而是由LangGraph的Router节点统一调度。Router根据当前State中的next_step字段决定下一步调用哪个子智能体。比如Planner输出{steps: [parse_log, analyze_sql, generate_report]}Router就依次触发CodeGen→Validator→Executor每个环节的输出都写入State供后续节点读取。这种“分工明确集中调度”的模式既保证了专业性CodeGen不用操心邮件格式又保证了可控性Router可以随时插入人工审核节点。3. 核心模块实现FastAPI服务层与LangGraph执行引擎的深度耦合3.1 FastAPI项目目录结构为什么这样组织一个健壮的FastAPI项目目录结构本身就是设计哲学的体现。羲和采用以下结构所有路径均基于src/根目录src/ ├── main.py # ASGI入口只做初始化和路由挂载 ├── api/ # API路由定义 │ └── v1/ # 版本化路由 │ ├── __init__.py │ ├── agent.py # 核心Agent接口/v1/execute_task │ └── health.py # 健康检查/health ├── core/ # 核心业务逻辑 │ ├── __init__.py │ ├── agent/ # LangGraph执行引擎主逻辑 │ │ ├── __init__.py │ │ ├── graph.py # LangGraph图定义节点边状态 │ │ ├── nodes/ # 各子智能体节点实现 │ │ │ ├── planner.py │ │ │ ├── codegen.py │ │ │ └── ... # 其他节点 │ │ └── state.py # State基类定义与字段约束 │ └── tools/ # 所有Tool实现与LLM交互的桥梁 │ ├── __init__.py │ ├── database.py # 数据库操作封装 │ ├── email.py # 邮件发送封装 │ └── ... # 其他工具 ├── models/ # Pydantic模型定义请求/响应/State │ ├── __init__.py │ ├── request.py # TaskExecuteRequest等 │ ├── response.py # TaskExecuteResponse等 │ └── state.py # AgentState模型严格字段校验 ├── config/ # 配置管理 │ ├── __init__.py │ ├── settings.py # 环境变量加载数据库URL、LLM API Key等 │ └── logging.py # 结构化日志配置 └── utils/ # 通用工具函数 ├── __init__.py └── helpers.py # 如代码安全沙箱执行、SQL白名单过滤这种结构的底层逻辑是让FastAPI只做它最擅长的事——HTTP协议处理和路由分发所有AI逻辑下沉到core.agent层彻底解耦。main.py里只有三行关键代码from fastapi import FastAPI from src.api.v1 import agent, health from src.core.agent.graph import create_agent_graph app FastAPI(titleXiheAgent API, version1.0) app.include_router(health.router) app.include_router(agent.router) # 初始化LangGraph执行引擎单例 agent_graph create_agent_graph()agent.py路由文件里/v1/execute_task接口的实现极其简洁from fastapi import APIRouter, HTTPException, Depends from src.models.request import TaskExecuteRequest from src.models.response import TaskExecuteResponse from src.core.agent.graph import agent_graph # 直接注入全局实例 router APIRouter() router.post(/execute_task, response_modelTaskExecuteResponse) async def execute_task( request: TaskExecuteRequest, # 依赖注入自动校验配置、初始化LLM客户端等 _ Depends(validate_config) ): try: # 关键将HTTP请求转化为LangGraph可执行的State initial_state { task_id: str(uuid4()), user_instruction: request.instruction, created_at: datetime.utcnow().isoformat(), execution_history: [] } # 启动LangGraph执行异步非阻塞 final_state await agent_graph.ainvoke(initial_state) return TaskExecuteResponse.from_state(final_state) except Exception as e: raise HTTPException(status_code500, detailfExecution failed: {str(e)})注意这里agent_graph.ainvoke()是LangGraph的原生方法它内部会自动调度所有节点开发者无需手动控制流程。FastAPI只负责“接单”和“交货”中间的“工厂生产”完全由LangGraph管理。这种分层让单元测试变得极其简单——你可以单独测试planner.py节点而不必启动整个FastAPI服务。3.2 LangGraph状态State的设计字段即契约State是LangGraph的灵魂也是最容易被忽视的设计点。羲和的AgentState定义在models/state.py中采用Pydantic v2严格校验from pydantic import BaseModel, Field, validator from typing import List, Dict, Any, Optional from datetime import datetime class AgentState(BaseModel): task_id: str Field(..., description唯一任务ID) user_instruction: str Field(..., min_length1, max_length2000, description用户原始指令) created_at: str Field(..., descriptionISO格式创建时间) # 执行过程核心字段必须存在不能为空 current_step: str Field(defaultplanning, description当前执行步骤) execution_history: List[Dict[str, Any]] Field(default_factorylist, description执行历史记录) # 各子智能体产出物按需填充非必需 plan: Optional[List[str]] Field(defaultNone, descriptionPlanner生成的步骤列表) generated_code: Optional[str] Field(defaultNone, descriptionCodeGen生成的代码) validation_result: Optional[Dict[str, Any]] Field(defaultNone, descriptionValidator返回的校验结果) execution_output: Optional[str] Field(defaultNone, descriptionExecutor执行结果) # 错误与控制字段 error: Optional[str] Field(defaultNone, description最新错误信息) is_finished: bool Field(defaultFalse, description任务是否已完成) needs_human_review: bool Field(defaultFalse, description是否需要人工介入) validator(execution_history) def validate_history_length(cls, v): if len(v) 100: # 防止无限增长 raise ValueError(Execution history too long) return v这个设计的精妙之处在于每个字段都是对AI行为的显式约束。比如current_step字段强制要求每个节点执行前必须更新它Router节点才能据此决定下一步needs_human_review字段一旦被某个节点设为TrueRouter就会跳过后续自动节点直接返回结果给前端触发人工审核流程。这比在代码里用一堆if-else判断状态干净得多。更重要的是所有字段都经过Pydantic校验如果CodeGen节点意外返回了一个非字符串的generated_codePydantic会在写入State时直接抛异常避免脏数据污染后续流程。3.3 子智能体节点Node的实现范式函数即节点在LangGraph中“节点”就是一个普通Python函数但必须遵循特定签名。以planner.py为例from langchain_core.prompts import ChatPromptTemplate from langchain_openai import ChatOpenAI from src.core.agent.state import AgentState from src.models.state import AgentState from src.core.tools import list_available_tools def planner_node(state: AgentState) - AgentState: Planner子智能体节点将用户指令分解为可执行步骤 输入AgentState含user_instruction 输出更新后的AgentState含plan字段 # 1. 构建Prompt关键明确约束输出格式 prompt ChatPromptTemplate.from_messages([ (system, 你是一个专业的任务规划专家。请严格按以下规则工作 - 只输出JSON格式无任何额外文字 - 字段名必须为plan值为字符串列表 - 每个步骤必须是原子操作如查询MySQL慢查询日志而非分析性能问题 - 步骤必须按执行顺序排列 - 如果指令模糊返回空列表并设置error字段), (human, {instruction}) ]) # 2. 初始化LLM复用配置好的客户端 llm ChatOpenAI( modelgpt-4-turbo, temperature0.1, # 降低随机性保证步骤稳定 max_tokens512 ) # 3. 执行调用注意必须用invoke不是stream chain prompt | llm | JsonOutputParser() # 自定义JSON解析器 try: result chain.invoke({instruction: state.user_instruction}) # 4. 更新StateLangGraph要求返回新State不可修改原对象 return state.copy(update{ current_step: planning, plan: result.get(plan, []), execution_history: state.execution_history [{step: planning, status: success}] }) except Exception as e: return state.copy(update{ current_step: planning, error: fPlanning failed: {str(e)}, execution_history: state.execution_history [{step: planning, status: failed, error: str(e)}] })这个函数体现了三个关键原则纯函数式输入State输出新State不修改原对象LangGraph要求强约束Prompt用system message明确限定输出格式避免LLM自由发挥错误兜底任何异常都捕获并写入State的error字段确保图不会卡死。其他节点codegen、validator、executor都遵循同一范式只是内部逻辑不同。这种一致性让整个系统像乐高积木一样可插拔——你想换掉CodeGen用Claude只需重写codegen_node函数其他部分完全不动。4. 实操关键环节从零部署一个可运行的XiheAgent服务4.1 环境准备与依赖安装版本锁定是稳定基石羲和对依赖版本极其敏感尤其是LangGraph和LangChain生态。我们采用poetry管理依赖pyproject.toml核心部分如下[tool.poetry.dependencies] python ^3.10 fastapi ^0.115.0 # 与Starlette 0.30兼容 langgraph ^0.2.47 # 关键必须0.2.45修复了async节点并发bug langchain-core ^0.3.9 # 与langgraph 0.2.x匹配 langchain-openai ^0.2.12 # 支持gpt-4-turbo pydantic ^2.9.2 # Pydantic v2State校验必需 sqlalchemy ^2.0.35 # 数据库操作 aiofiles ^24.1.0 # 异步文件操作实操心得曾因langgraph0.2.42导致并发任务下State被意外覆盖排查三天才发现是已知bug。务必用poetry show --outdated定期检查升级到0.2.47。另外langchain-openai必须与langgraph版本对齐官方文档没明说但实测langchain-openai0.1.x与langgraph0.2.x不兼容。安装命令# 初始化虚拟环境 poetry install # 启动服务开发模式 poetry run uvicorn src.main:app --reload --host 0.0.0.0:8000 # 生产部署推荐使用GunicornUvicorn poetry run gunicorn src.main:app --bind 0.0.0.0:8000 --workers 4 --worker-class uvicorn.workers.UvicornWorker4.2 配置文件settings.py安全与灵活的平衡config/settings.py采用Pydantic Settings自动加载环境变量关键配置如下from pydantic_settings import BaseSettings from typing import List class Settings(BaseSettings): # API密钥必须从环境变量读取绝不硬编码 OPENAI_API_KEY: str DATABASE_URL: str # 格式postgresqlasyncpg://user:passhost/dbname SMTP_HOST: str SMTP_PORT: int 587 SMTP_USER: str SMTP_PASSWORD: str # LangGraph执行参数 MAX_EXECUTION_STEPS: int 20 # 防止无限循环 DEFAULT_TIMEOUT_SECONDS: int 60 # 单步执行超时 # 安全策略 ALLOWED_CODE_EXECUTION: bool False # 生产环境必须为False SQL_WHITELIST: List[str] [SELECT, EXPLAIN] # 只允许这些SQL关键词 class Config: env_file .env # 自动加载.env文件 case_sensitive False settings Settings()提示ALLOWED_CODE_EXECUTIONFalse是生产环境铁律。羲和的execute_python_code()工具在生产模式下会启动一个受限Docker容器执行代码容器内无网络、无文件系统写权限、CPU/内存严格限制。开发时可设为True快速验证但上线前必须关闭否则等于开放远程代码执行漏洞。4.3 快速体验用curl调用第一个任务部署好服务后用curl发起一个真实任务curl -X POST http://localhost:8000/v1/execute_task \ -H Content-Type: application/json \ -d { instruction: 查询数据库中user表的前5条记录并将结果保存为CSV文件 }返回结果简化{ task_id: a1b2c3d4..., status: success, result: CSV文件已生成路径/tmp/output_a1b2c3d4.csv, execution_steps: [ {step: planning, status: success}, {step: codegen, status: success}, {step: validator, status: success}, {step: executor, status: success} ], execution_time_ms: 1245 }这个看似简单的请求背后LangGraph完成了Planner生成步骤[连接数据库, 执行SELECT * FROM user LIMIT 5, 生成CSV文件]CodeGen写出带SQLAlchemy的Python代码Validator检查代码无os.system()调用、SQL无DROP关键词Executor在沙箱中执行代码生成文件并返回路径。整个过程在1.2秒内完成且每一步都有日志可查。5. 常见问题与排查技巧实录那些文档里不会写的坑5.1 问题速查表高频故障与定位路径现象可能原因排查命令/方法解决方案HTTP 500 Internal Server Error日志显示KeyError: planPlanner节点未正确设置plan字段State校验失败grep planning.*failed /var/log/xihe/*.log检查planner_node函数确保state.copy(update{...})中包含plan键任务卡在current_step: planning无后续日志Router节点未正确定义条件边或plan为空列表curl -X POST http://localhost:8000/v1/execute_task -d {instruction:test}观察返回State在graph.py中检查Router函数确认if state.plan:分支逻辑若plan为空需增强Planner Prompt的鲁棒性CodeGen生成的SQL包含INSERT INTO users VALUES (...)但Validator未拦截SQL_WHITELIST配置未生效或Validator节点未调用校验函数poetry run pytest tests/test_validator.py -v确认settings.SQL_WHITELIST在Validator节点中被正确读取且校验逻辑覆盖所有SQL语句Executor执行send_email()超时但SMTP配置正确网络策略阻止容器访问SMTP端口或SMTP服务器要求TLSdocker exec -it xihe-app sh -c telnet smtp.gmail.com 587在Docker网络中添加--network host或配置SMTP代理生产环境建议用SendGrid等专用邮件服务并发请求下不同任务的execution_history内容混杂State对象被多个协程共享修改违反LangGraph纯函数原则grep execution_history.*append src/core/agent/nodes/*.py确保所有节点都用state.copy(update{...})返回新对象绝不可用state.execution_history.append(...)5.2 独家避坑技巧来自生产环境的血泪经验技巧1给LLM加“刹车片”——Prompt中的硬约束比后处理更可靠早期我们让CodeGen生成代码后再用正则表达式过滤危险函数。结果发现LLM有时会把os.system(rm -rf /)写成os.____system____(rm -rf /)绕过检测。后来改为在Prompt里直接写死“你生成的Python代码中绝对不允许出现以下字符串os.system,subprocess.call,eval(,exec(。如果必须调用外部命令请使用tools.run_shell_command()工具。”实测拦截率从72%提升到100%。技巧2State字段命名要有“意图感”避免歧义曾用output作为Executor节点的返回字段结果Planner也想存output计划描述导致冲突。后来统一规范所有字段名必须带领域前缀如planner_output,codegen_output,validator_result。虽然字段名变长但调试时一眼就能看出数据来源。技巧3用langgraph.checkpoint做持久化别信内存开发时用MemorySaver保存State一切正常。上线后流量增大发现重启服务后所有进行中的任务丢失。解决方案集成PostgresSaver将State序列化为JSON存入数据库。关键代码from langgraph.checkpoint.postgres import PostgresSaver from sqlalchemy.ext.asyncio import create_async_engine engine create_async_engine(settings.DATABASE_URL) checkpointer PostgresSaver(engine) agent_graph create_graph(checkpointercheckpointer) # 注入checkpointer这样即使服务崩溃任务也能从数据库恢复。技巧4为Router节点写单元测试它是整个图的“交通警察”Router函数看似简单却是最易出错的地方。我们写了专项测试def test_router_next_step(): # 场景1plan存在且非空 → 进入codegen state AgentState(user_instructiontest, plan[step1]) assert router_node(state) codegen # 场景2plan为空 → 进入error处理 state AgentState(user_instructiontest, plan[]) assert router_node(state) __end__ # 或自定义error节点 # 场景3validation_result有error → 进入retry state AgentState(user_instructiontest, validation_result{error: syntax}) assert router_node(state) retry_codegen覆盖所有分支确保调度逻辑万无一失。6. 能力边界与演进思考羲和不是万能但指明了AI编码助手的务实路径羲和的设计初衷从来不是取代工程师而是成为工程师的“超级外设”。它目前的能力边界非常清晰擅长结构化数据操作DB/CSV/JSON、标准化系统交互邮件/HTTP/API、确定性脚本生成Shell/Python、合规性检查SQL/代码安全不擅长创造性架构设计、模糊需求理解如“让系统更快”、跨领域知识融合如“结合财务和供应链数据预测库存”、需要实时人类反馈的迭代如UI设计稿调整。这恰恰是工程化的胜利——承认边界才能聚焦价值。我们刻意不追求“全知全能”而是把80%的重复性、规则性、低风险任务做到99.9%可靠剩下的20%留给工程师做高价值决策。未来半年羲和的演进重点不是堆砌新功能而是深化已有能力执行可信度引入形式化验证如用Z3求解器验证生成SQL的逻辑等价性人机协作在Web界面中嵌入“Step-by-Step Mode”让用户点击按钮逐个执行子步骤随时中断、修改、重放知识沉淀将每次成功执行的user_instructionfinal_state存入向量库形成企业专属的“任务知识图谱”后续类似请求可直接检索复用减少LLM调用。最后分享一个小技巧如果你正在搭建自己的Agent先从一个最小可行节点开始。不要一上来就设计PlannerCodeGenValidatorExecutor四件套。我的建议是先实现一个echo_node输入什么返回什么验证LangGraph图能跑通再加planner_node让它把“你好”拆成[打招呼]最后才接入LLM和真实Tool。跳过这三步90%的人会在第二天就被State的引用问题和async的协程陷阱劝退。羲和的代码仓库里tests/目录下的test_minimal_graph.py就是这个最小原型它只有23行但足以让你触摸到LangGraph的脉搏。真正的工程能力永远诞生于对最小单元的彻底掌控之中。
返回列表