Multi Agent、Harness、Tools、Skills、MCP:Agent工程五层架构实战

📅 发布时间:2026/9/8 4:11:51
Multi Agent、Harness、Tools、Skills、MCP:Agent工程五层架构实战 先说结论Multi Agent、Skills、Harness、Tools、MCP 这几个词最近在 Agent 工程领域出现频率非常高。很多开发者第一次接触时容易把它们当成互不相干的新框架结果查了一圈资料发现哈里斯是运行时Skills 是经验包Tools 是能力边界MCP 又是协议整个学习路径被切成碎片。其实从工程视角看这五个词描述的是一个完整 Agent 系统的不同层次Harness 是让 Agent 循环跑起来的运行时Tools 是 Agent 能调用的外部能力Skills 是可复用、可检索的经验包MCP 是工具接入的标准协议Multi Agent 与 Deep Agent 则是组合这些基础能力后形成的两种协作形态。这篇实战记录以“AI 职业规划”作为落地场景用户输入“我想从后端开发转行做 AI 应用开发”系统需要完成用户画像整理、岗位行情分析、技能差距分析、学习路径编排和模拟面试准备。要支撑这个流程单靠一个对话式 Agent 不够需要多个角色分工协作也需要把常用的方法论沉淀成 Skills把外部数据能力通过 Tools 和 MCP 接入。读完你可以得到一套最小可运行的 Agent 工程骨架并理解每层为什么这样设计。1. 先理解 Agent、Harness、Tools、Skills、MCP 五者的分工这五个概念经常被混着提但它们解决的问题完全不同。先用一张表建立整体认知再逐个展开。概念通俗理解解决什么问题在项目中的形态Agent能执行任务而不是只聊天的程序自主决策、调用能力、完成目标规划器、分析器、面试官等角色实例HarnessAgent 跑起来的运行时骨架管理主循环、上下文、日志和权限AgentLoop 类型与循环代码Tools外挂能力让 Agent 有能力产生真实副作用岗位数据查询、简历解析、代码执行函数Skills可沉淀、可复用的经验包复用方法论避免把所有逻辑写进提示词SKILL.md 文件集合MCP工具接入的统一协议标准化外部工具的发现和调用MCP Server 与 MCP Client1.1 Agent从“聊天”到“执行任务”Agent 不是普通的聊天程序。普通的 LLM 调用是“你问一句模型答一句”模型没有自主性也不会主动使用外部资源。Agent 则是在 LLM 之上叠加了任务理解、规划、工具调用和结果反馈的能力。可以把它理解为一个 Agent 大模型 运行循环 上下文记忆 一组可调用工具。这里最容易误解的地方是“Agent 一定很聪明”。实际上 Agent 的智能上限由模型决定但它的可用性上限由工程决定。一个模型很强但循环写得很烂的 Agent可能还不如一个模型一般但终止条件、错误处理、工具 schema 都设计良好的 Agent。所以做 Agent 工程重点不在“让模型自己想”而在“把模型的行为约束在可控轨道上”。1.2 HarnessAgent 的运行时骨架Harness 中文常被叫作“运行时”或“主循环”。社区里提到的 codex harness、deepseek harness本质上都是同一个东西把大模型的 Agentic Loop 包装成可执行的代码环境。Harness 的具体职责包括维护输入输出消息列表把系统提示词、用户输入、Skill 内容组装成模型上下文把已注册的工具转换成模型能识别的 function schema循环执行“模型输出 - 工具调用 - 结果回填 - 再次请求模型”检查步数上限、超时、权限、成本和异常分支。也就是说Harness 是 Agent 的“发动机舱”。引擎是 LLM但油门、刹车、仪表盘、转向逻辑都在 Harness 里。自己写 Harness 是快速理解 Agent 原理最好的方式生产环境则可以选用成熟框架。1.3 ToolsAgent 的能力边界没有工具时模型只能生成文字。模型可以用训练时学到的知识回答“什么是 RAG”但没法查询你数据库里的真实订单也没法把生成结果写入文件。Tools 就是注册给模型的函数模型按照调用约定传入参数执行结果再回到 Harness作为消息的一部分交给模型继续推理。Tools 通常包含四个要素名称、描述、输入参数 schema、执行函数。其中描述和 schema 必须足够清晰。模型不认识你的 Python 函数它只认识你传给它的 JSON 格式说明。写不清楚描述模型就不会在合适的时机调用这个工具。1.4 Skills可沉淀、可复用、可检索的经验包Skills 是给 Agent 准备的“岗位手册”。它是一段结构化指令描述某类任务具体怎么做先采集什么信息再按什么步骤分析最后输出什么格式。经典形态是一个 SKILL.md 文件头部用 YAML 写 name 和 description正文写详细流程。Skills 和系统提示词的区别在于系统提示词是常驻上下文所有对话都要背负它的 token 成本Skills 是按需加载的只有当用户目标命中某个领域的描述时才注入。这样既能控制上下文长度又能把方法论从代码里抽出来单独维护和版本管理。社区中已经出现大量打包好的 Skills比如前端开发 Skills、办公文档处理 Skills都是把高频任务的方法论固化成文件。1.5 MCP统一工具接入协议MCPModel Context Protocol是一个开放协议解决的是“每个 Agent 都找外挂工具但每种工具都要单独适配”的问题。如果把 Tools 看成 USB 设备MCP 就是定义插口标准的那套协议。MCP 架构由三层组成MCP Host 是承载 Agent 的宿主程序MCP Client 负责与 Server 通信MCP Server 暴露能力。Server 可以暴露三类原语Tools可以被模型调用的函数通常有副作用Resources可读取的数据资源比如文件内容、数据库结果Prompts可复用的提示词模板。传输方式常见两种stdio 适合本地进程间通信Streamable HTTP 适合远程服务。本文示例使用 stdio 方式因为它最容易在本地跑通。2. Multi Agent 与 Deep Agent 的设计取舍Multi Agent 和 Deep Agent 经常被放在一起说但它们解决的是两个不同维度的问题。Multi Agent 强调“多个角色分工协作”Deep Agent 强调“一个复杂目标拆到足够深再执行”。两者可以叠加使用。2.1 Multi Agent 解决的是角色分工Multi Agent 系统的核心不是“放多个 Agent 进去”而是“多个 Agent 各司其职、通过消息或任务队列协作”。每个 Agent 有独立的系统提示词、独立的 Skills 和独立的工具权限这样职责边界清晰提示词可控也可以单独测试。典型好处系统提示词不会越长越乱每个 Agent 只关心自己的领域工具权限可以按角色隔离比如查询 Agent 只读数据执行 Agent 才能写入某个角色升级或换模型时不影响其他角色。代价是系统复杂度明显上升。角色之间需要定义消息格式需要安排调度逻辑还需要处理多个 Agent 之间的重复调用和成本膨胀。实际项目里如果单 Agent 加几个工具就能完成任务就不要硬拆成 Multi Agent。2.2 Deep Agent 解决的是问题深度Deep Agent 关注的是“拆解深度”。面对“帮我规划转行 AI 应用开发”这种复杂目标它会把目标递归拆解成子任务树先了解用户背景再查岗位市场然后做技能差距分析接着生成学习路径最后设计实战项目。每个子任务还能继续拆直到某个子任务可以被一个 Harness 加少量工具和 Skill 完成。可以这样区分Multi Agent 回答“谁来负责这个任务”Deep Agent 回答“这个任务还要拆多细”。实际系统通常是 Deep Agent 负责拆解与编排Multi Agent 负责按拆解结果执行。2.3 在 AI 职业规划场景中的组合方式为了不过度设计本文把协作流程固定成主规划器 四类执行角色主规划器收到用户目标后按 Deep Agent 思路拆成固定子任务用户画像 Agent 整理用户当前技能栈和时间投入行情分析 Agent 查询目标岗位的技能要求和薪资区间学习路径 Agent 对比差距生成按月学习计划面试准备 Agent 基于目标岗位生成模拟面试题目。这五个角色各用一个 Harness 实例共享同一套 Tools 注册中心Skills 在各自角色需要时按描述加载。这样既演示了 Multi Agent 的协作也演示了 Deep Agent 的拆解过程。3. 项目需求拆解与架构设计任何项目都先写需求再写代码。这里把 AI 职业规划系统的需求固定到可以编码的程度避免做成一个无限扩大的平台。3.1 目标场景与用户流程用户输入一段自然语言例如“我想从后端开发转行做 AI 应用开发目前会 Java、Spring Boot每周能投入 10 小时。”系统输出一份结构化报告包含五部分用户画像、岗位行情、技能差距、3 个月学习路径、模拟面试题目与复习清单。用户的每次输入都走同一条流程不要求用户自己拆任务。3.2 角色 Agent 划分角色核心职责需要的 Tools需要的 Skillsplanner拆解目标、编排任务、汇总结果无任务拆解 Skillprofile分析用户背景输出技能清单简历解析用户画像 Skillanalyst查询岗位趋势、技能要求、薪资岗位数据查询 MCP行业分析 Skillroadmap对比差距生成学习路径技能字典学习路径 Skillinterview生成模拟面试题与复习清单无面试准备 Skill3.3 整体架构与数据流系统分为四层编排层planner 负责接收用户输入拆解为 Task 列表依次派发Harness 层每个执行角色持有自己的 AgentLoop 实例工具层Tools 注册表统一管理函数MCP Client 负责从外部 MCP Server 拉取工具Skill 层Skill 文件按库管理执行角色按需加载。数据流是单向的。用户输入进入 plannerplanner 输出任务队列各执行角色依次处理并把结果写入任务 output最后 planner 汇总成报告。这个流程刻意采用串行方式减少并发调度带来的不确定性适合作为第一版。4. 环境准备与项目骨架为了保证示例可运行先固定技术栈和目录结构。这里以 Python 为主因为 MCP 官方 SDK 和大多数 Agent 示例都用 Python。4.1 运行环境与依赖组件建议版本用途Python3.10 及以上运行 Agent、Tools、MCP Servermcp使用当前稳定版MCP Client 与 Server SDKpydantic2.x校验工具参数与任务消息openai最新稳定版调用兼容 OpenAI 接口的大模型pyyaml最新稳定版读取配置文件这里不锁定精确版本号落地前需要根据实际环境和模型服务商确认版本兼容性。下面的示例代码用于说明思路类名和参数需要按实际 SDK 调整。注意学习环境可以直接在本地虚拟环境里跑通生产环境必须把模型密钥、服务地址、数据源连接串全部外置到环境变量或配置中心。4.2 项目目录结构career_agents/ ├── config/ │ └── settings.yaml ├── harness/ │ ├── loop.py │ └── messages.py ├── agents/ │ ├── planner.py │ ├── profile.py │ ├── analyst.py │ ├── roadmap.py │ └── interviewer.py ├── skills/ │ ├── profile-analyzer/SKILL.md │ ├── job-trend/SKILL.md │ ├── learning-path/SKILL.md │ └── interview-prep/SKILL.md ├── tools/ │ ├── registry.py │ └── career_data.py ├── mcp/ │ ├── career_server.py │ └── client_loader.py └── main.py目录分层遵循“能力和经验分离”的原则。Skills 是可编辑的方法论文档Tools 是可测试的函数Harness 是通用的循环代码agents 是业务角色main.py 只负责组装。4.3 配置说明# config/settings.yaml llm: provider: openai-compatible base_url: https://your-model-endpoint.example.com/v1 model: your-model-name temperature: 0.2 harness: max_steps: 8 log_level: debug mcp: servers: career: command: python args: [mcp/career_server.py] skills_dir: skills配置项的含义比较直接但有两个点要注意。temperature 调低可以让工具调用更稳定减少模型随意发挥的可能。max_steps 是 Harness 的步数上限职业规划这种多阶段任务建议 8 到 12 步太小会导致任务没完成就被截断太大容易放大成本。5. 实现 Harness先让单个 Agent 跑起来Multi Agent 再复杂最终也要落到“单个 Harness 能跑通”的前提上。这一节实现一个最小 Harness不依赖任何框架。5.1 Harness 的核心职责一个可用 Harness 至少要完成四件事维护完整消息历史模型才能记住前面调用过什么把已注册工具转成模型识别的 function schema循环调用模型直到模型不再请求工具对工具执行结果做序列化作为 tool 消息回填。同时必须设置终止条件。常见终止条件包括模型返回纯文本答案、步数达到上限、工具执行失败次数过多、用户主动中断。缺少终止条件的 Harness 在真实场景中一定会出现失控调用。5.2 最小 Harness 代码# harness/messages.py from dataclasses import dataclass, field dataclass class Message: role: str # system / user / assistant / tool content: str | None None tool_calls: list[dict] | None None tool_call_id: str | None None dataclass class LLMResponse: content: str tool_calls: list[dict] | None None# harness/loop.py import inspect import json from dataclasses import dataclass, field from typing import Any, Callable from harness.messages import LLMResponse, Message dataclass class AgentLoop: llm: Any # 需要实现 chat(messages, schemas) - LLMResponse system_prompt: str max_steps: int 8 tools: dict[str, Callable] field(default_factorydict) messages: list[Message] field(default_factorylist) def __post_init__(self): if self.system_prompt: self.messages.append(Message(system, self.system_prompt)) def register_tool(self, fn: Callable) - None: self.tools[fn.__name__] fn def schemas(self) - list[dict]: result [] for name, fn in self.tools.items(): result.append({ type: function, function: { name: name, description: fn.__doc__ or , parameters: self._extract_parameters(fn), }, }) return result def _extract_parameters(self, fn: Callable) - dict: sig inspect.signature(fn) properties, required {}, [] for pname, param in sig.parameters.items(): if pname in (self, return): continue properties[pname] {type: self._map_type(param.annotation)} required.append(pname) return {type: object, properties: properties, required: required} staticmethod def _map_type(annotation: Any) - str: return { str: string, int: integer, float: number, bool: boolean, }.get(annotation, string) def run(self, user_input: str) - str: self.messages.append(Message(user, user_input)) for _ in range(self.max_steps): resp self.llm.chat(self.messages, self.schemas()) if resp.tool_calls: self.messages.append( Message(assistant, resp.content, tool_callsresp.tool_calls) ) for call in resp.tool_calls: output self._dispatch(call[name], call[arguments]) self.messages.append( Message(tool, output, tool_call_idcall[id]) ) continue return resp.content or raise RuntimeError(fAgentLoop 超过 {self.max_steps} 步仍未结束) def _dispatch(self, name: str, arguments: str) - str: if name not in self.tools: return json.dumps({error: f工具 {name} 不存在}, ensure_asciiFalse) try: args json.loads(arguments) result self.tools[name](**args) return json.dumps(result, ensure_asciiFalse) except Exception as exc: return json.dumps({error: str(exc)}, ensure_asciiFalse)这个 Harness 的关键点在于模型返回的工具调用被拆成两条消息一条是 assistant 的工具调用记录一条是每个 tool 的执行结果。只有保持这种消息结构下一次请求模型时模型才知道自己刚才调用了哪些工具以及结果是什么。5.3 接入 LLM 的注意点上面的llm对象需要提供一个chat(messages, schemas)方法把内部 Message 列表转成模型 API 允许的格式再把响应统一封装成LLMResponse。这里最容易踩坑的是不同模型服务商对 function calling 的返回格式不一样有的返回字符串参数有的返回对象有的 content 可能为 None有的 tool_calls 是数组。推荐做法是写一个适配器层专门负责模型响应到LLMResponse的归一化。不要让业务代码直接依赖某一个模型 SDK否则换模型时改动会很大。6. 用 Tools 和 MCP 扩展 Agent 能力Harness 只提供了循环骨架真正让 Agent 能干活的是 Tools 和 MCP 接到 Harness 上的那些函数。6.1 Tools 注册与调用约定Tools 的定义应该满足两个约定一是函数必须有类型注解方便 Harness 生成 JSON Schema二是函数必须有清晰的 docstring因为 docstring 会被当作给模型的工具描述。下面是一个工具示例。# tools/career_data.py import requests def query_job_trend(role: str, city: str 北京) - dict: 查询目标岗位的招聘热度、技能要求和薪资参考区间。 参数 role 是目标岗位名称例如“AI 应用开发” 参数 city 是城市名称。 url https://example-job-api.local/v1/job/trend payload {role: role, city: city} resp requests.get(url, paramspayload, timeout10) resp.raise_for_status() return resp.json()注册到 Harness 时直接传入函数对象loop AgentLoop(llmllm, system_promptprofile_prompt) loop.register_tool(query_job_trend)函数名会作为工具名docstring 会作为工具描述参数注解会转成 schema。也就是说工具的定义和注册可以做到零额外配置只要函数本身写得规范。6.2 自定义一个职业数据工具生产环境里岗位数据一定来自内部系统或第三方 API但这里可以使用示例数据源来演示工具语义。工具设计要注意返回结构稳定不要今天返回 dict明天返回 list因为模型会根据返回结构做后续推理。6.3 最小 MCP Server 示例MCP Server 可以把职业数据能力暴露给任意兼容 MCP 的客户端。下面用 MCP Python SDK 的 FastMCP 风格写一个最小 Server展示 Tools 和 Resources 两种原语。# mcp/career_server.py from mcp.server.fastmcp import FastMCP mcp FastMCP(career-data) mcp.tool() def query_job_trend(role: str, city: str 北京) - dict: 查询目标岗位的招聘热度、技能要求和薪资参考区间。 return { role: role, city: city, trend: high, hot_skills: [Python, RAG, Agent, MCP, 向量数据库], salary_range: 15k-30k, source: 示例数据生产环境请接入真实招聘数据源, } mcp.resource(career://skill/{role}) def role_skills(role: str) - dict: 返回某个岗位的通用技能字典。 return {role: role, skills: [编程基础, 机器学习, Agent 工程]} if __name__ __main__: mcp.run()运行这个文件后它会通过 stdio 提供 MCP 服务。MCP Inspector 或任意 MCP Client 都能连接并发现工具和资源。这里还要强调一点示例返回的是写死的示例数据真实环境要接入招聘数据 API 或公司内部的职位库不要在 Server 里硬编码。6.4 在客户端加载 MCP 工具Harness 需要把 MCP Server 暴露的工具拉取成本地 schema才能让模型调用。下面是客户端加载逻辑。# mcp/client_loader.py import asyncio from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client async def load_mcp_tools(command: str, args: list[str]) - list[dict]: server_params StdioServerParameters(commandcommand, argsargs) async with stdio_client(server_params) as (read, write): async with ClientSession(read, write) as session: await session.initialize() listed await session.list_tools() return [ { name: t.name, description: t.description, input_schema: t.inputSchema, } for t in listed.tools ] def sync_load_mcp_tools(command: str, args: list[str]) - list[dict]: return asyncio.run(load_mcp_tools(command, args))注意这个示例只在函数内维持了 session 生命周期适合启动时同步一次工具列表。如果运行时需要持续调用 MCP 工具必须把 session 生命周期提升到 Harness 级别或者使用独立的 MCP 客户端层管理连接。这里只做最小演示生产环境不能照搬这种短连接方式。7. 用 Skills 沉淀职业规划经验Tools 解决的是“Agent 能不能做到”Skills 解决的是“Agent 做得好不好”。同一个模型没有 Skill 时只能靠通用推理硬写答案有了 Skill 就能按沉淀好的方法论执行。7.1 Skill 的标准结构一个 Skill 是一个目录目录里通常有一个 SKILL.md。文件头部使用 YAML frontmatter 写元信息正文写具体流程。description 字段特别重要它决定了 Agent 或路由器在什么场景下加载这个 Skill。以面试准备 Skill 为例--- name: interview-prep description: 当用户提供目标岗位 JD 或希望做模拟面试时使用。 scope: AI 应用开发、前后端、数据工程等研发岗位 --- # 研发岗模拟面试准备流程 1. 提取 JD 中的技能关键词与项目要求。 2. 按基础理论、项目深挖、算法与设计、软技能四类出题。 3. 每道题标注考察点、参考回答框架和常见追问。 4. 最后输出 7 天复习清单按优先级排列。关键是最后两段正文步骤要足够具体模型才能照着执行description 要写清楚触发条件而不是写空泛宣传语。7.2 学习路径 Skill职业规划项目还需要一个学习路径 Skill它负责把技能差距转成按月学习计划。--- name: learning-path description: 当需要根据技能差距生成学习路径和实战项目计划时使用。 --- # 学习路径编排流程 1. 列出目标岗位的技能关键词。 2. 对比用户当前技能标记重叠与缺失。 3. 把缺失技能按依赖关系排序。 4. 按用户每周可投入时间折算成月计划。 5. 每个技能配套一个 1-2 周的实战项目。 6. 输出周期、里程碑、项目清单和自查题。这份 Skill 的价值在于稳定了输出结构。如果没有它同一个模型十次可能给十种完全不同的规划格式业务方很难接受。7.3 Skills 加载与检索策略Skills 不是全部塞进 system prompt。推荐做法是维护一个 Skills 目录每个 Skill 的 description 作为索引。主规划器拿到用户目标后可以根据关键词或向量检索命中相关 Skill再把它注入到对应角色的 system prompt 里。# skills/loader.py from pathlib import Path import re def load_skill_markdown(skill_dir: Path, name: str) - str: skill_path skill_dir / name / SKILL.md return skill_path.read_text(encodingutf-8) def match_skills(skill_dir: Path, query: str) - list[str]: matched [] for skill_path in skill_dir.glob(*/SKILL.md): text skill_path.read_text(encodingutf-8) frontmatter text.split(---)[1] if text.startswith(---) else if any(keyword in frontmatter for keyword in [面试, 学习路径, 用户画像]): if query in frontmatter or any(k in query for k in (面试, 学习, 规划)): matched.append(skill_path.parent.name) return matched这一步只做演示关键词匹配在真实项目中会命中率有限更可靠的方案是使用 embedding 模型给 Skill 的 description 建索引再按用户目标做语义检索。检索结果要限制返回条数一次最多注入两到三个 Skill否则上下文又被撑爆。8. 实现 Multi Agent 协作流程基础能力就绪后开始组装 Multi Agent。核心问题有两个任务消息怎么定义角色之间怎么传递结果。8.1 任务消息与执行契约Multi Agent 之间不要用自由文本传递结果而是定义一份统一任务结构。# agents/task.py from dataclasses import dataclass, field dataclass class Task: task_id: str executor: str # profile / analyst / roadmap / interviewer instruction: str skills: list[str] field(default_factorylist) tools: list[str] field(default_factorylist) status: str pending # pending / running / done / failed output: str executor是执行角色名skills是该角色需要加载的 Skill 名称tools是该角色允许使用的工具白名单。这样每个 Task 就是自包含的任何执行器拿到同一个 Task 都能按契约输出。8.2 规划器与执行器规划器负责把用户目标转成 Task 列表。它本质上是 Deep Agent 思想的简化实现拆解深度固定为四层不递归展开方便控制复杂度。# agents/planner.py import json from agents.task import Task class PlannerAgent: def __init__(self, llm): self.llm llm def decompose(self, user_goal: str) - list[Task]: prompt ( 你是职业规划主规划器。请把用户目标拆成 4 个子任务\n 1. profile整理用户画像提取当前技能栈。\n 2. analyst查询目标岗位市场与 JD 要求。\n 3. roadmap对比差距并生成 3 个月学习路径。\n 4. interview基于目标岗位生成模拟面试。\n 输出 JSON 数组每项包含 executor 和 instruction。\n f用户目标{user_goal}\n 只输出 JSON。 ) raw self.llm.generate(prompt) items json.loads(raw) return [ Task( task_idstr(index), executoritem[executor], instructionitem[instruction], ) for index, item in enumerate(items) ]执行器是通用的只要传入 Task、对应角色的系统提示词和 Skill 内容就用自己的 Harness 执行。# agents/executor.py from agents.task import Task from harness.loop import AgentLoop ROLE_PROMPTS { profile: 你是用户画像分析 Agent负责整理用户的技能栈、项目经历和学习时间。, analyst: 你是岗位行情分析 Agent负责查询目标岗位的技能要求和薪资区间。, roadmap: 你是学习路径规划 Agent负责生成按月拆解的学习计划。, interviewer: 你是模拟面试 Agent负责按 JD 生成面试题和复习清单。, } def run_task(task: Task, loop_factory, skill_dir) - str: system_prompt ROLE_PROMPTS[task.executor] skill_text for skill_name in task.skills: skill_md load_skill_markdown(skill_dir, skill_name) skill_text f\n[Skill: {skill_name}]\n{skill_md}\n loop loop_factory(f{system_prompt}\n{skill_text}.strip()) result loop.run(task.instruction) task.status done task.output result return result8.3 完整协作时序串行协作流程如下用户输入进入 plannerplanner 调用模型生成 Task 列表依次遍历 Task按 executor 匹配系统提示词根据 Task.skills 加载 Skill 内容注入 system prompt执行器创建自己的 AgentLoop执行并写入 task.output所有 Task 完成后planner 汇总四段输出拼成最终报告。这个流程牺牲了并行效率但换来了确定性。每一步的结果都写入 Task调试时只要观察每个 Task 的 status 和 output就知道问题出在哪一步。9. 运行验证与结果分析代码写完后要按“入口 - 输入 - 中间任务 - 汇总输出”的顺序验证。9.1 启动与调用方式主入口 main.py 负责读取配置、初始化 LLM 适配器、创建各角色 Harness然后执行规划与汇总。python main.py 我想从后端开发转行做 AI 应用开发目前会 Java、Spring Boot每周能投入 10 小时学习环境可以直接在命令行传入目标字符串。进入测试环境后建议把用户输入改为从队列或 HTTP 接口读取但调用链保持不变。9.2 一次完整输入与输出规划器拆解出的任务大致如下[ {executor: profile, instruction: 分析用户当前技能栈提取 Java、Spring Boot、MySQL 等项目经验。}, {executor: analyst, instruction: 查询 AI 应用开发岗位在北京的技能要求和薪资区间。}, {executor: roadmap, instruction: 对比用户技能与岗位要求生成 3 个月学习路径。}, {executor: interviewer, instruction: 基于 AI 应用开发岗位 JD 生成模拟面试题和复习清单。} ]最终报告的核心段落【用户画像】 当前技能Java、Spring Boot、MySQL、Redis 目标岗位AI 应用开发 可用时间每周 10 小时 【岗位行情】 AI 应用开发岗位热度高 JD 高频技能Python、RAG、Agent、MCP、向量数据库 薪资参考区间15k-30k 【技能差距】 Java 后端与目标岗位重叠度约 30% 需要补充Python、Prompt 工程、RAG、Agent 框架、MCP 工具开发 【3 个月学习路径】 第 1 个月Python LLM API 基础 第 2 个月RAG 与向量数据库 第 3 个月Agent 工程 MCP 两个实战项目 【模拟面试】 高频考点Python 语法、RAG 原理、Agent 循环、MCP 协议、项目深挖题这个输出不应该是一次碰巧的结果。要确认它稳定需要重复多次输入并记录变化这是 Agent 项目验证的基本功。9.3 验证清单Harness 单测给一个需要两轮工具调用的任务确认循环能正常结束工具单测直接调用 query_job_trend确认返回结构和 docstring 描述一致MCP 连通测试单独运行 career_server.py用 MCP Inspector 能看到工具列表Skill 命中测试输入包含“学习路径”的目标确认 learning-path Skill 被加载Task 流转测试确认每个 Task 的 status 都能从 pending 走到 done成本观察打印每个 Harness 的步数和 token 消耗确认成本在预算内。10. 常见问题与排查链路Agent 项目最大的特点不是难写而是难查。下面按“现象 - 原因 - 检查 - 处理”的方式总结四类高频问题。10.1 MCP 连接失败现象ClientSession.initialize() 报错或者 list_tools() 返回空列表。可能原因command 路径不对、Python 虚拟环境不一致、MCP Server 启动即抛异常、stdio 参数传错。检查方式先手动执行python mcp/career_server.py看进程是否正常启动再检查 MCP Server 是否打印了明确的异常堆栈最后确认脚本运行在同一个 Python 环境。处理建议command 使用绝对路径args 使用绝对路径脚本避免环境变量差异给 MCP Server 增加启动日志方便定位 JSON-RPC 握手失败。10.2 工具参数解析错误现象dispatch 时 json.loads 报错或者模型传入参数与函数签名不匹配。可能原因函数 schema 和函数真实签名不一致说明 required 字段没生效模型返回的 arguments 不是合法 JSONdocstring 中参数单位或枚举写得不清楚模型只能猜。检查方式在 _dispatch 中打印 arguments 原文对比 schemas() 生成的 JSON 与函数签名用一次固定 prompt 复现并记录模型原始输出。处理建议坚持 schema-first函数必须用类型注解由 Harness 统一生成 schema对复杂参数使用 pydantic 校验解析失败时返回结构化错误而不是让 Harness 崩溃。10.3 Skills 没有生效现象模型没有按 SKILL.md 里的步骤执行仍然回答得很泛。可能原因Skill 没有成功注入 system promptSkill 的 description 太短规划器没有命中多个 Skill 同时注入后互相冲突。检查方式在 run_task 中打印最终 system prompt确认包含 Skill 正文检查匹配函数的输出对比一次手动注入 Skill 和一次不注入 Skill 的模型输出差异。处理建议把 Skill 的 description 写清楚触发条件例如“当用户……时使用”一次最多注入两到三个 Skill冲突时明确优先级或拆分 Skill 文件。10.4 Agent 进入死循环现象Harness 一直调用工具迟迟不返回最终结果或者每次都调用同一个工具。可能原因缺少终止条件工具总是返回“还需要继续查询”之类的中间状态max_steps 过大且模型没有终止指令。检查方式开启 debug 日志逐轮打印 tool_calls 的名称和参数统计同一个工具被调用的次数观察模型最后一次响应是否真的没有 tool_calls。处理建议把 max_steps 控制在 8 到 12 步在 system prompt 中明确写“没有更多信息时基于已有内容作答”对重复调用做去重同一参数连续调用两次以上直接终止。问题现象常见原因优先检查处理方案MCP 连接失败环境不一致、Server 崩溃手动运行 server 脚本绝对路径、加启动日志工具参数解析失败schema 与签名不一致打印 arguments 原文schema-first、pydantic 校验Skill 未生效未注入或命中失败查看最终 system prompt改进 description、限注入数量死循环缺少终止条件逐轮打印 tool_calls限制步数、结果去重、明确终止指令11. 最佳实践与生产化建议学习项目跑通只是第一步。真正要放到生产环境还需要在几个关键点上下更多功夫。11.1 工程可落地的具体建议所有工具定义坚持 schema-first。函数签名、docstring、类型注解保持严格一致工具注册零额外配置才能长期维护。工具按权限分级。只读工具和写工具分开注册不同角色只能挂载自己权限范围内的工具避免一个角色越权操作。全程记录 trace 日志。每轮请求都带上 request_id记录模型输入输出、工具名称、参数、耗时和 token 数。没有 traceAgent 故障几乎没法排查。给 Agent 设置成本预算。步数上限、token 上限、并发上限三者一起控制不要只限制步数。Skills 入库管理。Skill 文件放版本库description 写清楚触发条件变更后用固定评测用例回归防止方法论改动导致输出质量下降。不让 Agent 直接访问生产库。优先走只读账号、审批链路或旁路数据库Agent 出错时影响面可控。准备自动化评测集。收集 20 到 50 条真实用户输入每次改提示词、Skill 或模型版本后跑一遍对比关键字段是否仍满足要求。11.2 学习环境与生产环境的差异维度学习 Demo生产系统模型固定一个模型多模型路由、可回退配置写在代码或本地 yaml配置中心、环境变量日志print结构化日志、链路追踪工具权限全放开只读、审批、最小权限MCP 安全本地 stdio鉴权、限流、传输加密Skill 管理本地文件中心化存储、版本、灰度成本不关心预算告警、步数与 token 限制评测人工观察自动化评测集、回归对比11.3 下一步扩展方向当前串行流程适合第一版后续可以朝四个方向演进。一是引入成熟的 Harness 框架把自研循环替换成社区维护的实现同时保留自研版本作为内部原理培训材料。二是扩展 MCP 接入源让行情分析 Agent 连接真实的招聘数据、课程数据和题库数据替换示例数据源。三是增加长期记忆把用户画像、历史规划结果持久化到向量库下一次对话直接复用。四是引入人工审核环节学习路径和薪资数据这类高风险输出先经人工或规则引擎审核再展示给用户。Agent 工程的价值不在于堆叠新概念而在于把模型行为约束在可控的轨道里。Multi Agent 明确分工Harness 控制循环Tools 扩展能力边界MCP 标准化接入Skills 沉淀方法论。把这五层分别做扎实再组合成业务系统会比追逐任何一个新框架都更稳妥。建议新手先按本文骨架复刻一遍再换一个领域场景重写 Skills 和 Tools这个练习过程能覆盖 Agent 工程多数核心问题。