1. 这不是“又一个LangChain教程”而是一套可落地的JSON驱动Agent工作流你搜“LangChain agent”时页面上堆满“三步搭建智能体”“五分钟入门Agent开发”的标题——但真正跑起来才发现配置一塌糊涂、状态难追踪、调试像在黑盒里摸开关。我去年帮三家中小团队落地AI助手项目80%的卡点根本不在LLM调用本身而在如何让Agent的行为可定义、可验证、可协作。直到我把整个Agent的骨架从Python代码里抽出来用纯JSON描述模型选择、工具列表、执行顺序、错误重试策略、输入输出schema……所有逻辑不再散落在几十行代码里而是集中在一个结构清晰、人眼可读、Git可版本管理的JSON文件中。这个思路不是凭空想的它直接来自Ollama本地部署时最朴素的需求我不想每次改个提示词都要重启服务更不想让非开发同事连修改一个工具参数都得找我改代码。JSON在这里不是数据格式而是Agent的配置协议层——就像Docker用JSON写DockerfileKubernetes用YAML写Deployment一样我们用JSON定义Agent的“行为契约”。它天然适配Ollama的本地化特性无需API密钥、模型路径直写、LangChain的模块化设计Tool、LLM、Memory全可JSON化注册更重要的是它让Agent从“写死的代码”变成“可配置的服务”。下面要讲的不是怎么安装Ollama或抄一段LangChain示例而是如何用JSON把Agent的每个决策节点钉死在配置里让调试从“猜”变成“查”让协作从“改代码”变成“改配置”。2. 为什么必须用JSON驱动Agent三个被忽略的硬伤与解法2.1 硬伤一LangChain Agent的“黑盒执行”让调试成本翻倍LangChain默认的AgentExecutor执行过程像一台老式收音机你调大音量调整temperature但不知道是调谐电容出了问题还是天线接触不良。它的run()方法内部封装了Plan-Act-Observation循环所有中间状态比如选了哪个Tool、传了什么参数、返回了什么原始结果默认不暴露。我曾遇到一个客户项目Agent在调用天气API后总返回空值排查了3小时才发现是LangChain自动注入的tool_input字段名和API文档要求的city_name不一致——这个映射关系藏在Tool类的args_schema里而args_schema又依赖Pydantic模型定义。如果用Python硬编码改一个字段名就得改模型、改Tool实例、重启服务但如果用JSON定义Tool字段映射就明明白白写在input_mapping字段里{ tool_name: get_weather, description: 获取指定城市的实时天气, api_url: http://localhost:8000/weather, method: GET, input_mapping: { city: city_name, unit: temp_unit }, output_path: $.data.current.temp_c }提示input_mapping不是LangChain原生支持的字段而是我们在JSON Schema里自定义的协议。它强制把“代码逻辑”变成“配置规则”任何前端工程师都能看懂并修改。2.2 硬伤二Ollama本地模型切换的“路径依赖”让环境难以复现Ollama的ollama run llama3命令背后是模型文件存放在~/.ollama/models/下的SHA256哈希目录里。当你在开发机上ollama pull llama3再把代码部署到测试服务器如果服务器没拉过这个模型或者拉的版本不同比如llama3:latest指向llama3:8b而非llama3:70bAgent直接报错model not found。传统做法是写Shell脚本预装模型但这又引入了运维复杂度。我们的解法是把Ollama模型声明为JSON中的第一等公民。在Agent配置JSON里明确写出模型名称、版本、甚至校验哈希{ ollama_config: { model: llama3:8b, host: http://localhost:11434, timeout: 120, model_hash: sha256:abc123def456... } }实操时Agent启动前先调用Ollama API/api/show检查模型是否存在且哈希匹配不匹配则自动执行ollama pull。这个逻辑封装在OllamaModelLoader类里但触发条件完全由JSON控制——没有JSON里的model_hash就不做校验写了model_hash就强制校验。这比“文档里写一句‘请确保模型已下载’”可靠一万倍。2.3 硬伤三多Agent协作时的“状态孤岛”导致流程断裂一个典型客服Agent需要串联知识库检索、订单查询、工单创建三个步骤。LangChain的SequentialChain能串起来但每个步骤的输出格式不统一知识库返回Markdown文本订单API返回JSON对象工单系统要求XML。传统做法是写一堆output_parser函数但这些函数散落在各处新人接手时根本不知道“为什么这里要.replace(\n, )”。我们的方案是用JSON Schema定义每个步骤的输入/输出契约。比如订单查询步骤的JSON配置{ step_id: query_order, tool: order_api, input_schema: { type: object, properties: { order_id: {type: string, pattern: ^ORD-[0-9]{6}$} }, required: [order_id] }, output_schema: { type: object, properties: { status: {type: string, enum: [pending, shipped, delivered]}, items: { type: array, items: { type: object, properties: { sku: {type: string}, quantity: {type: integer, minimum: 1} } } } } } }Agent执行时自动用jsonschema.validate()校验输入输出。如果订单API返回了{status: shipped, items: []}而Schema要求items非空立刻中断并抛出ValidationError而不是把空数组传给下一步导致工单创建失败。这个校验动作本身是代码但校验规则完全由JSON定义——改规则不用动一行Python只改JSON。3. JSON配置的核心结构设计从“能用”到“好维护”的四层拆解3.1 第一层Agent元信息——定义身份与边界这是JSON的根对象回答“这是谁为谁服务能做什么”三个问题。它不涉及具体逻辑但决定了整个Agent的生命周期管理方式{ agent_id: customer_support_v2, version: 2.3.1, description: 处理电商用户咨询支持订单查询、退货申请、物流跟踪, owner: support-teamcompany.com, tags: [ecommerce, customer-service, ollama], lifecycle: { auto_reload: true, config_watch_path: ./configs/customer_support.json, max_retries: 3, retry_delay_seconds: 2 } }agent_id是唯一标识用于日志追踪和监控告警比如Prometheus指标agent_execution_total{agent_idcustomer_support_v2}version必须遵循语义化版本规范因为Agent配置会随业务迭代旧版本配置可能还在生产环境运行lifecycle.auto_reload开启后Agent会监听config_watch_path文件变化热重载配置——这解决了“改完JSON还得手动重启服务”的痛点。实现原理是用watchdog库监听文件修改事件触发reload_config()方法该方法会重新解析JSON、重建Tool列表、重置Memory全程毫秒级完成注意auto_reload不能盲目开启。如果Agent正在处理一个长耗时请求如生成一份10页PDF报告热重载可能导致内存泄漏。我们的实践是只对lifecycle以外的字段热重载lifecycle本身变更必须重启服务并在JSON Schema里标记x-reload-required: true。3.2 第二层LLM与Ollama集成——把本地模型变成可插拔组件这一层彻底解耦LangChain的ChatOllama类与具体模型。配置项直接映射Ollama API参数避免二次封装{ llm: { type: ollama, model: qwen2:7b, base_url: http://192.168.1.100:11434, temperature: 0.3, top_k: 40, top_p: 0.9, num_predict: 2048, repeat_penalty: 1.1, stop: [|eot_id|, \n\n] } }关键细节base_url支持内网IP适配Ollama部署在NAS或专用服务器的场景。很多教程只写http://localhost:11434但生产环境Ollama常部署在独立机器上num_predict控制最大输出token数必须显式设置。Ollama默认不限制但LangChain的streaming模式下过长输出会导致HTTP连接超时stop数组定义停止符对应Qwen2模型的|eot_id|结束标记。不设这个模型可能无限生成直到达到num_predict上限我们封装了一个OllamaLLMFactory类它接收这个JSON片段动态生成ChatOllama实例class OllamaLLMFactory: def create(self, config: dict) - ChatOllama: return ChatOllama( modelconfig[model], base_urlconfig[base_url], temperatureconfig.get(temperature, 0.8), # ...其他参数 )这样换模型只需改JSON里的model字段无需碰Python代码。3.3 第三层Tool工具集——用JSON描述“能做什么”这是Agent能力的原子单元。每个Tool的JSON配置必须包含可执行性、可观测性、可测试性三要素{ tools: [ { name: search_knowledge_base, description: 在客服知识库中搜索与用户问题匹配的解决方案, type: http, spec: { url: http://kb-api.internal/search, method: POST, headers: {Authorization: Bearer {{env.KB_TOKEN}}}, body_template: { query: {{input.question}}, top_k: 3 } }, output_parser: { type: json_path, path: $.results[*].content }, test_cases: [ { input: {question: 订单多久能发货}, expected_output: [我们通常在24小时内发货] } ] } ] }spec.headers.Authorization里的{{env.KB_TOKEN}}是环境变量插值语法由Agent加载时自动替换。这比把Token硬编码在JSON里安全得多output_parser.type: json_path表示用jsonpath-ng库解析响应path字段就是JSONPath表达式。如果API返回结构变化只改path即可不用重写Parser类test_cases是核心创新点。我们开发了一个ToolTester工具能自动运行这些用例并生成覆盖率报告。比如search_knowledge_base有5个test_case实际只通过3个报告会标红显示哪两个失败并给出HTTP响应详情。这把Tool测试从“手动curl”变成了“配置即测试”3.4 第四层Execution Flow——用JSON定义“怎么做”这才是Agent的真正大脑。我们放弃LangChain原生的AgentExecutor自己实现了一个基于JSON的流程引擎{ execution_flow: { steps: [ { id: parse_intent, tool: intent_classifier, input: {text: {{input.text}}}, output_key: intent }, { id: route_to_tool, type: switch, condition: {{steps.parse_intent.output.intent}}, cases: { order_query: {next: query_order}, return_request: {next: initiate_return}, tracking: {next: get_tracking_info} } } ], error_handling: { on_failure: fallback_to_human, retry_policy: { max_attempts: 2, backoff_factor: 1.5 } } } }steps是有序数组定义执行顺序。每个step有id用于后续引用、tool关联tools数组中的name、input支持Jinja2模板语法{{input.text}}取用户原始输入type: switch是流程分支节点condition字段用Jinja2表达式计算路由条件cases定义不同值对应的下一步。这比LangChain的RouterChain更直观且条件逻辑可读性强error_handling统一定义失败策略。on_failure: fallback_to_human表示任何step失败都跳转到人工客服环节这个环节本身也是一个Tool其JSON配置在tools数组里这个Flow引擎的核心是JSONFlowExecutor类它遍历steps数组对每个step渲染input模板得到实际参数调用对应Tool执行将输出存入steps[step_id].output供后续step引用检查output_parser并提取结构化结果整个过程无状态所有中间数据都存在内存字典里便于调试时打印完整执行轨迹。4. 实操从零搭建一个JSON驱动的客服Agent含避坑清单4.1 环境准备Ollama LangChain 必要依赖不要用pip install langchain这种宽泛安装我们精确锁定版本以避免兼容问题# 创建虚拟环境 python -m venv agent-env source agent-env/bin/activate # Linux/Mac # agent-env\Scripts\activate # Windows # 安装核心依赖注意版本号 pip install ollama0.1.32 pip install langchain0.1.16 pip install langchain-community0.0.32 pip install jsonschema4.21.1 pip install jinja23.1.4 pip install watchdog3.0.0 pip install jsonpath-ng1.6.0实测心得LangChain 0.1.x系列对Ollama支持最稳定。0.2.x引入了Runnable新范式但JSON配置驱动的旧模式需要大量适配。我们坚持用0.1.16因为它对Tool、LLM的抽象最干净JSON映射逻辑最简单。Ollama安装后务必验证基础功能# 拉取测试模型 ollama pull qwen2:7b # 测试API是否正常 curl http://localhost:11434/api/tags # 应返回包含qwen2:7b的JSON如果curl超时常见原因是Ollama服务未启动或端口被占用。Windows用户常遇到ollama serve后台服务没开需手动运行ollama serve命令。4.2 创建JSON配置文件customer_support.json把前面四层结构组合成一个完整文件。注意真实项目中我们会把tools单独拆成tools/目录下的多个JSON文件用$ref引用但为演示简洁这里写在一起{ agent_id: customer_support_v2, version: 2.3.1, description: 电商客服Agent支持订单查询、退货申请、物流跟踪, owner: ai-teamcompany.com, lifecycle: { auto_reload: true, config_watch_path: ./configs/customer_support.json, max_retries: 3, retry_delay_seconds: 2 }, llm: { type: ollama, model: qwen2:7b, base_url: http://localhost:11434, temperature: 0.3, top_k: 40, top_p: 0.9, num_predict: 2048, repeat_penalty: 1.1, stop: [|eot_id|, \n\n] }, tools: [ { name: intent_classifier, description: 识别用户咨询意图订单查询、退货申请、物流跟踪、其他, type: local, spec: { module: intent_classifier.py, function: classify } }, { name: query_order, description: 根据订单号查询订单状态和商品明细, type: http, spec: { url: http://order-api.internal/v1/orders/{{input.order_id}}, method: GET, headers: {X-API-Key: {{env.ORDER_API_KEY}}} }, output_parser: { type: json_path, path: $ } } ], execution_flow: { steps: [ { id: parse_intent, tool: intent_classifier, input: {text: {{input.text}}}, output_key: intent } ], error_handling: { on_failure: fallback_to_human, retry_policy: { max_attempts: 2, backoff_factor: 1.5 } } } }4.3 编写核心执行器agent_executor.py这是将JSON配置转化为实际Agent的胶水代码。重点看load_config()和execute()方法import json import os from typing import Dict, Any from langchain_community.chat_models import ChatOllama from langchain.tools import Tool from jsonschema import validate, ValidationError import jinja2 class JSONAgentExecutor: def __init__(self, config_path: str): self.config_path config_path self.config self.load_config() self.llm self._create_llm() self.tools self._create_tools() self.flow_engine self._create_flow_engine() def load_config(self) - Dict[str, Any]: 加载并验证JSON配置 with open(self.config_path, r, encodingutf-8) as f: config json.load(f) # 加载JSON Schema进行验证此处省略Schema定义实际项目中应有完整Schema # validate(instanceconfig, schemaAGENT_SCHEMA) return config def _create_llm(self) - ChatOllama: 根据JSON配置创建Ollama LLM实例 llm_config self.config[llm] return ChatOllama( modelllm_config[model], base_urlllm_config[base_url], temperaturellm_config.get(temperature, 0.8), top_kllm_config.get(top_k, 40), top_pllm_config.get(top_p, 0.9), num_predictllm_config.get(num_predict, 2048), repeat_penaltyllm_config.get(repeat_penalty, 1.0), stopllm_config.get(stop, []) ) def _create_tools(self) - list[Tool]: 将JSON tools数组转换为LangChain Tool对象 tools [] for tool_config in self.config[tools]: if tool_config[type] http: tool self._create_http_tool(tool_config) elif tool_config[type] local: tool self._create_local_tool(tool_config) tools.append(tool) return tools def _create_http_tool(self, config: dict) - Tool: 创建HTTP Tool # 此处简化实际应封装HTTP调用逻辑 def _run(input_dict: dict) - str: # 使用requests调用API处理headers插值等 pass return Tool( nameconfig[name], func_run, descriptionconfig[description] ) def execute(self, user_input: str) - Dict[str, Any]: 执行Agent流程 # 1. 初始化执行上下文 context {input: {text: user_input}} # 2. 遍历execution_flow.steps for step in self.config[execution_flow][steps]: # 渲染input模板 input_template jinja2.Template(json.dumps(step[input])) rendered_input json.loads(input_template.render(context)) # 3. 查找并调用对应Tool tool next((t for t in self.tools if t.name step[tool]), None) if not tool: raise ValueError(fTool {step[tool]} not found) # 4. 执行Tool try: result tool.invoke(rendered_input) # 5. 解析输出并存入context context.setdefault(steps, {})[step[id]] {output: result} except Exception as e: # 处理错误 self._handle_step_error(step, e, context) return context def _handle_step_error(self, step: dict, error: Exception, context: dict): 错误处理逻辑 error_config self.config[execution_flow][error_handling] if error_config[on_failure] fallback_to_human: context[fallback_reason] str(error) context[final_output] 已转接人工客服请稍候。4.4 启动Agent并测试创建app.py作为入口from agent_executor import JSONAgentExecutor if __name__ __main__: # 初始化Agent agent JSONAgentExecutor(./configs/customer_support.json) # 测试输入 test_input 我的订单ORD-123456发货了吗 # 执行 result agent.execute(test_input) print(执行结果:, json.dumps(result, indent2, ensure_asciiFalse))运行python app.py首次运行会看到Ollama模型加载日志几秒后输出执行结果。关键观察点result[steps][parse_intent][output]应该是order_query如果query_order工具配置正确result[steps][query_order]里会有订单详情常见问题排查问题Ollama connection refused原因Ollama服务未运行或base_url地址错误解决运行ollama serve确认curl http://localhost:11434/api/tags返回正常问题TemplateSyntaxError: unexpected char u{原因Jinja2模板语法错误{{input.text}}写成了{input.text}解决检查JSON中所有{{ }}语法确保双大括号完整问题KeyError: intent原因intent_classifier工具返回的字典没有intent键解决检查intent_classifier.py的classify()函数确保返回{intent: order_query}4.5 配置热重载让Agent“活”起来启用lifecycle.auto_reload后修改JSON配置无需重启。实现原理是watchdog监听文件from watchdog.observers import Observer from watchdog.events import FileSystemEventHandler class ConfigReloader(FileSystemEventHandler): def __init__(self, executor: JSONAgentExecutor): self.executor executor def on_modified(self, event): if event.src_path self.executor.config_path: print(f检测到配置变更正在重载 {event.src_path}...) self.executor.config self.executor.load_config() self.executor.llm self.executor._create_llm() self.executor.tools self.executor._create_tools() print(配置重载完成) # 在app.py中添加 if __name__ __main__: agent JSONAgentExecutor(./configs/customer_support.json) # 启动热重载监听 if agent.config[lifecycle].get(auto_reload, False): event_handler ConfigReloader(agent) observer Observer() observer.schedule(event_handler, path./configs/, recursiveFalse) observer.start() # ...后续执行逻辑现在你直接用编辑器修改customer_support.json里的temperature值保存后终端会立刻打印“配置重载完成”下次agent.execute()就用新参数了。5. 生产级增强JSON配置的进阶技巧与避坑指南5.1 环境变量注入安全地管理敏感配置JSON里绝不能出现API Key、数据库密码。我们用{{env.VAR_NAME}}语法在加载时替换{ tools: [ { name: order_api, spec: { url: https://api.example.com/orders, headers: { Authorization: Bearer {{env.ORDER_API_KEY}}, X-Client-ID: {{env.CLIENT_ID}} } } } ] }加载逻辑import os def render_env_vars(config_str: str) - str: 渲染JSON字符串中的环境变量 for key, value in os.environ.items(): config_str config_str.replace(f{{{{env.{key}}}}}, value) return config_str # 在load_config()中使用 with open(self.config_path, r, encodingutf-8) as f: config_str f.read() config_str render_env_vars(config_str) config json.loads(config_str)注意事项环境变量名必须大写且只允许字母、数字、下划线。ORDER_API_KEY合法order.api.key非法。这是为了与Linux环境变量规范一致避免跨平台问题。5.2 JSON Schema校验让配置错误在启动时暴露没有Schema的JSON配置就像没有类型检查的JavaScript——运行时才报错。我们为Agent配置定义了严格Schema{ $schema: https://json-schema.org/draft/2020-12/schema, type: object, properties: { agent_id: {type: string, minLength: 1}, llm: { type: object, properties: { type: {const: ollama}, model: {type: string, minLength: 1}, base_url: {type: string, format: uri} }, required: [type, model, base_url] }, tools: { type: array, items: { type: object, properties: { name: {type: string}, description: {type: string}, type: {enum: [http, local]} }, required: [name, description, type] } } }, required: [agent_id, llm, tools] }加载配置时强制校验from jsonschema import validate, ValidationError def load_config_with_validation(config_path: str, schema_path: str) - dict: with open(config_path, r) as f: config json.load(f) with open(schema_path, r) as f: schema json.load(f) try: validate(instanceconfig, schemaschema) except ValidationError as e: raise ValueError(f配置校验失败: {e.message} at {..join([str(i) for i in e.absolute_path])}) return config这样如果JSON里漏写了llm.base_url启动时立刻报错配置校验失败: None is not of type string at llm.base_url而不是等到调用API时才说Connection refused。5.3 配置版本管理用Git管理Agent演进把configs/目录纳入Git每次变更都提交git add configs/customer_support.json git commit -m feat(customer_support): 支持物流跟踪增加tracking_api工具 git tag v2.3.1好处回滚git checkout v2.2.0 -- configs/customer_support.json一键回退审计git log -- configs/customer_support.json查看谁在什么时候改了什么发布CI/CD流程自动打包configs/目录发布到不同环境dev/staging/prod我们约定configs/下每个Agent一个子目录子目录里包含main.json主配置、tools/工具配置、schemas/JSON Schema。这样结构清晰Git Diff也易读。5.4 性能优化JSON解析的冷启动瓶颈与解法首次加载大JSON1MB可能耗时200ms影响首请求延迟。解法是预编译import json import marshal # 预编译JSON为Python字节码 def compile_config(config_path: str, cache_path: str): with open(config_path, r) as f: config json.load(f) # 用marshal序列化比json快3倍但只支持Python内置类型 with open(cache_path, wb) as f: marshal.dump(config, f) # 加载时优先读缓存 def load_config_optimized(config_path: str) - dict: cache_path config_path .marshal if os.path.exists(cache_path) and os.path.getmtime(cache_path) os.path.getmtime(config_path): with open(cache_path, rb) as f: return marshal.load(f) else: compile_config(config_path, cache_path) return load_config_optimized(config_path)实测1.2MB的配置文件json.load()耗时180msmarshal.load()仅需60ms。缺点是marshal文件不可读但配置文件本就该由程序生成不是给人手写的。5.5 监控与可观测性让JSON配置“说话”在Agent执行时自动记录JSON配置的元数据到日志import logging import time logger logging.getLogger(__name__) def execute_with_logging(self, user_input: str) - dict: start_time time.time() # 记录配置摘要 config_summary { agent_id: self.config[agent_id], version: self.config[version], llm_model: self.config[llm][model], tool_count: len(self.config[tools]) } logger.info(fAgent执行开始 | {json.dumps(config_summary)} | 输入: {user_input[:50]}...) try: result self._execute_core(user_input) duration time.time() - start_time logger.info(fAgent执行成功 | 耗时: {duration:.2f}s | 输出长度: {len(str(result))}) return result except Exception as e: duration time.time() - start_time logger.error(fAgent执行失败 | 耗时: {duration:.2f}s | 错误: {str(e)}, exc_infoTrue) raise这样ELK或Datadog里就能按agent_id、version聚合分析性能比如发现customer_support_v2在v2.3.0版本后平均耗时上升20%立刻知道是新加入的tracking_api工具拖慢了整体。6. 常见问题速查表JSON Agent开发者的实战笔记问题现象根本原因解决方案我的实操备注Agent execution terminated due to error.这是LangChain的通用错误未捕获具体异常在execute()方法外层加try-except打印sys.exc_info()获取完整堆栈别信这个错误信息它毫无价值。一定要用logging.exception()打全量日志ollama download too slow国内直连Ollama官方镜像源速度慢配置Ollama使用清华源export OLLAMA_HOSThttps://mirrors.tuna.tsinghua.edu.cn/ollama不要在JSON里写镜像源这是Ollama客户端配置不是Agent配置langchain agent not working with ollamaLangChain版本与Ollama API不兼容锁定ollama0.1.32和langchain0.1.16新版Ollama0.1.40的/api/chat响应格式变了LangChain 0.1.16不支持JSON parse error: Expecting property name enclosed in double quotesJSON文件用了中文引号“”或单引号用VS Code打开安装JSON Tools插件用Format Document自动修复记住JSON只认英文双引号{key: value}{key: value}是Python dict不是JSONTool not foundJSON中tool字段名与tools数组里name不一致用jq .tools[].name customer_support.json检查所有tool name大小写敏感name: QueryOrder和tool: queryorder不匹配Environment variable not substituted{{env.KEY}}语法写错或环境变量未设置运行echo $ORDER_API_KEY确认变量存在检查JSON中是否多写了空格{{ env.KEY }}