LLM Agent动态技能注入:从技能图谱构建到上下文实时投喂 📅 发布时间:2026/8/17 22:34:35 👁 浏览次数: 1. 从“工具调用”到“技能注入”为什么我们需要动态技能上下文最近和几个做LLM Agent的朋友聊天大家普遍有个感觉Agent的“工具箱”越来越大了。从基础的网页搜索、代码执行到调用各种API、操作数据库甚至控制物理设备Agent能做的事情越来越多。但随之而来的一个核心矛盾也愈发突出能力越强越容易“犯傻”。这听起来有点反直觉但实际开发中经常遇到。比如你给Agent接入了十几个API涵盖了天气查询、股票分析、邮件发送、文档处理。当你问它“帮我查一下北京的天气然后写封邮件提醒我明天带伞”理论上它应该能完美执行。但实际情况往往是Agent要么在调用天气API时把“北京”这个参数传给了邮件API要么在写邮件时试图调用天气查询的函数来生成邮件正文。更常见的是面对一个复杂任务Agent在规划步骤时根本“想不起来”自己还有某个关键技能可用。这就是典型的静态技能上下文的局限性。传统的做法是在Agent初始化时把所有可用工具或技能的函数签名和描述一次性、完整地塞进系统提示词System Prompt里。这就像给一个厨师一本厚厚的、包含所有菜系做法的百科全书然后让他立刻做一桌宴席。信息过载导致他要么反应迟钝上下文窗口被占满思考变慢要么抓不住重点在无关的技能描述中迷失。而SkillsInjector这个概念瞄准的正是这个痛点。它不再是把所有“菜谱”一次性堆给厨师而是根据客人点的“菜”用户任务动态地从书架上抽出最相关的几本摊开在厨师面前。这个“动态抽取和构建相关技能上下文”的过程就是技能注入的核心。它让LLM Agent在每一步决策时只“看到”与当前子任务最相关、最可能被用到的技能从而大幅提升任务规划的准确性、工具调用的精确性以及整体推理的效率。从网络热词“LLM powered autonomous agents”的流行可以看出社区对智能体自主性的期待越来越高。而自主性的基石正是高效、精准的“技能运用”能力。SkillsInjector不是要发明新技能而是要让已有的技能被更聪明地“记起”和“使用”。接下来我们就深入拆解如何从零构建一个动态技能上下文系统。2. 技能图谱为你的技能库建立“搜索引擎”要实现动态注入第一步不是写代码而是重新组织你的技能库。你不能再把技能看作是一堆孤立的函数描述文本而需要为它们建立一个结构化的、可查询的“技能图谱”。这就像图书馆的索引系统书技能本身不变但有了索引我们就能根据关键词快速找到它。2.1 定义技能的元数据超越函数签名一个技能或工具的传统定义可能只包含name函数名、description功能描述、parameters参数列表。这对于静态上下文勉强够用但对于动态检索而言信息粒度太粗语义不够丰富。我们需要为每个技能定义更丰富的元数据我习惯称之为“技能档案”skill_metadata { id: send_email_v1, name: send_email, description: 通过SMTP协议发送一封电子邮件。, # 核心用于向量化检索的“语义描述”应比简短描述更丰富 semantic_description: 此功能用于创建并发送电子邮件消息。它可以指定收件人、主题、正文内容并支持添加附件。常用于通知、报告发送、邮件提醒等场景。, category: [communication, notification], # 技能分类 prerequisites: [recipient_email, smtp_server_config], # 执行前置条件或依赖 typical_input_keywords: [邮件, 发送, email, 通知, 提醒, 报告], # 常见输入关键词 typical_output_description: 返回发送状态如‘成功’或失败原因。, # 输出描述 function_schema: {...} # 原有的OpenAI格式的函数调用Schema }为什么需要这么多字段semantic_description: 这是检索的基石。简短的description可能只说了“发邮件”而语义描述包含了“给谁发”、“发什么”、“为什么发”等场景信息能更好地匹配用户查询的意图。category和typical_input_keywords: 这是构建“检索关键词”的重要来源。当用户说“提醒我一下”系统可以快速关联到category包含notification且关键词包含“提醒”的技能。prerequisites: 这在复杂工作流中至关重要。如果一个技能需要某个前置技能的输出作为输入系统可以在注入时进行关联性推荐。2.2 构建技能向量数据库实现语义检索有了丰富的元数据下一步就是让系统能“理解”这些技能。最有效的方法是利用文本嵌入模型将技能的语义信息转化为向量Vector存入向量数据库。实操步骤选择嵌入模型对于技能检索不需要像回答通用问题那样庞大的模型。text-embedding-3-small、BAAI/bge-small-zh-v1.5中文或all-MiniLM-L6-v2都是轻量且高效的选择。重点是保持一致性所有技能和用户查询都用同一个模型编码。生成技能向量不要只对name或简短description编码。最佳实践是将多个元数据字段拼接成一个“检索文本”。例如retrieval_text f 技能名称{skill[name]} 功能描述{skill[description]} 详细语义{skill[semantic_description]} 适用类别{, .join(skill[category])} 典型用途处理与{, .join(skill[typical_input_keywords])}相关的任务。 将这个retrieval_text通过嵌入模型得到向量。存入向量数据库将技能ID和对应的向量存入如ChromaDB、Weaviate、Qdrant或PGVector等数据库。同时将技能的完整元数据JSON格式也关联存储以便检索后快速获取详细信息。关键设计点更新策略技能库不是一成不变的。当新增、删除或修改一个技能时需要同步更新向量数据库。建议将此过程自动化作为技能注册流程的一部分。混合检索纯向量检索语义相似有时会漏掉一些关键词完全匹配但表述不同的技能。可以结合稀疏检索如BM25进行混合查询提升召回率。例如用户查询“发邮件”BM25能精准命中包含“邮件”关键词的技能而向量检索能命中“发送电子信函”这种语义相似但措辞不同的描述。3. 动态上下文构建引擎在任务流中实时“投喂”技能技能图谱建好了相当于我们有了一个智能的“技能仓库”。接下来我们需要一个“调度员”在Agent执行任务的每一个关键节点根据当前情况从仓库里精准调取技能并组装成LLM能理解的上下文。这就是动态上下文构建引擎。3.1 触发时机何时需要注入技能不是每一步都需要重新检索技能。无脑的频繁检索会浪费算力增加延迟。合理的触发时机包括任务规划阶段Plan当用户提出一个复杂任务Agent进行任务分解Task Decomposition时需要根据顶级任务目标检索一批可能相关的技能作为规划的背景知识。例如任务“监控服务器日志并在发现错误时发邮件告警”应触发检索“读取文件”、“日志分析”、“模式匹配”、“发送邮件”等技能。子任务执行前Act当Agent决定执行某个具体子任务如“发送告警邮件”时需要精确检索与这个子任务最匹配的1-3个技能并将它们的调用格式function schema注入到当前的LLM对话上下文中。这是最核心的注入点。执行失败或遇到障碍时Reflect如果工具调用返回错误如参数错误、权限不足、网络超时系统可以基于错误信息重新检索可能提供替代方案或能解决当前问题的其他技能。例如调用“发送邮件”失败可以检索“发送即时消息”或“写入日志文件”作为备选通知方案。3.2 查询构造如何让检索更精准检索的输入查询Query构造直接影响结果质量。不能直接把用户的原始问题或Agent的简单思考扔给检索器。对于规划阶段查询应由“任务目标”“预期动作”构成。例如任务目标是“分析销售数据并生成报告”查询可以构造为“需要数据读取、数据分析、图表生成、文档编写等功能”。对于执行阶段查询应由“子任务描述”“已具备的上下文”构成。例如子任务是“生成柱状图”已知道数据源是一个CSV文件。查询可以构造为“使用CSV数据创建柱状图可视化。已有数据需要绘图功能。”利用对话历史将当前轮次的对话和前几轮的上下文摘要而非全文作为查询的一部分可以帮助理解任务的延续性。3.3 上下文组装如何呈现给LLM检索到相关技能后不能简单地把所有技能描述堆砌到系统提示里。需要精心组装以最小、最清晰的成本让LLM理解。一个高效的组装格式如下你目前可以调用以下与当前任务相关的工具 工具1: [技能名称] 描述: [精简的功能描述1-2句话] 参数: - param1 (类型): [说明] - param2 (类型): [说明] 调用示例: [一个简单的JSON示例展示调用结构] 工具2: [技能名称] ...组装策略去重与排序检索结果可能包含相似技能需要根据相似度分数去重并按相关性排序只保留Top-K个K通常为3-5。描述精简从完整的skill_metadata中提取最核心的description和function_schema避免将冗长的semantic_description全部注入节省令牌。动态上下文窗口管理始终计算已注入的技能上下文长度确保加上用户查询和Agent的思考后不超过LLM的上下文窗口限制。可以设计一个淘汰机制当需要注入新技能而窗口不足时优先移除最早注入或与当前步骤最不相关的旧技能上下文。4. 核心架构与代码实现拆解理论讲完了我们来点硬的。一个基础的SkillsInjector模块应该包含哪些组件下面是一个简化的Python类结构示意和关键代码逻辑。4.1 系统组件设计class SkillsInjector: def __init__(self, embedding_model, vector_db_client, skill_registry): self.embedder embedding_model # 文本嵌入模型 self.vector_db vector_db_client # 向量数据库客户端 self.registry skill_registry # 技能注册中心存储所有技能的完整元数据 self._skill_cache {} # 技能缓存避免频繁查询DB async def retrieve_skills(self, query: str, top_k: int 5) - List[SkillMetadata]: 核心检索方法 # 1. 将查询文本向量化 query_vector await self.embedder.embed(query) # 2. 向量数据库相似性搜索 vector_results await self.vector_db.similarity_search(query_vector, top_k*2) # 多查一些用于后续过滤 # 3. (可选) 关键词检索 (如使用BM25) keyword_results self._keyword_search(query, top_k*2) # 4. 结果融合与重排 (如 Reciprocal Rank Fusion) fused_results self._fuse_results(vector_results, keyword_results) # 5. 去重、过滤如根据当前会话状态过滤掉不可用的技能 filtered_results self._filter_skills(fused_results, context) # 6. 返回Top-K个技能的完整元数据 return filtered_results[:top_k] def construct_context_prompt(self, skills: List[SkillMetadata]) - str: 将技能列表组装成LLM可用的提示词片段 prompt_sections [] for i, skill in enumerate(skills, 1): section fTool {i}: {skill[name]}\n section fDescription: {skill[description]}\n if skill.get(parameters): section Parameters:\n for param, info in skill[parameters].items(): section f- {param} ({info.get(type, string)}): {info.get(description, )}\n # 添加一个简单的调用示例极大降低LLM的格式错误率 example self._generate_example_call(skill) if example: section fExample call: {example}\n prompt_sections.append(section) return \n---\n.join(prompt_sections) async def inject_for_subtask(self, subtask_description: str, conversation_history: str) - str: 为子任务执行注入技能上下文 # 构造更丰富的查询 enriched_query fSubtask: {subtask_description}. Context: {self._summarize_history(conversation_history)} relevant_skills await self.retrieve_skills(enriched_query, top_k3) return self.construct_context_prompt(relevant_skills)4.2 与Agent框架的集成SkillsInjector不应该是一个孤立的模块它需要与ReAct、AutoGPT、LangChain、LlamaIndex等Agent框架的工作流紧密耦合。以经典的ReAct循环思考-行动-观察为例集成点如下# 在ReAct循环的“Act”阶段之前 def decide_next_action(agent_state): # ... agent的思考过程决定下一步要做什么subtask... subtask agent_state[current_subtask] # 调用SkillsInjector获取与该子任务相关的技能上下文 skill_context skills_injector.inject_for_subtask( subtask_descriptionsubtask, conversation_historyagent_state[conversation_summary] ) # 将技能上下文拼接到本次给LLM的提示词中 prompt f {system_prompt} 当前可用的工具 {skill_context} 请基于以上工具和当前状态决定下一步行动。 当前状态{agent_state} 你的思考 # 将prompt发送给LLM得到包含工具调用的响应 llm_response call_llm(prompt) return parse_llm_response(llm_response)关键集成心得注入的时机和频率需要精细调优。对于步骤清晰、工具明确的简单任务可以在循环开始前一次性注入所有可能技能。对于探索性强、路径不确定的复杂任务必须在每个“Act”步骤前动态注入确保LLM始终基于最相关的工具集进行决策。5. 效果评估与迭代优化如何证明它真的有用引入SkillsInjector增加了系统复杂性我们必须有方法评估其收益。不能只靠“感觉更聪明了”。5.1 建立评估指标体系可以从以下几个维度设计评估实验任务成功率在同一组涵盖不同复杂度的测试任务上对比使用静态技能上下文和动态技能注入的Agent其完整执行并达成目标的比例。工具调用准确率选择准确率在需要调用工具的子步骤中Agent选择了正确工具的比例。参数填充准确率调用工具时参数填写正确且完整的比例。推理效率平均Tokens消耗完成相同任务动态注入由于上下文更精简通常消耗的总令牌数更少。平均步骤数动态注入是否能通过更精准的技能提示让Agent规划出更优、更短的执行路径。长上下文任务表现在技能库特别庞大如超过50个工具时静态上下文可能因窗口限制无法全部加载而动态注入能否成功处理此类任务。5.2 构建测试集与A/B测试设计测试集是关键。测试任务应覆盖简单单技能任务验证基础功能不受影响。多技能组合任务验证技能检索和关联能力。技能描述歧义任务两个技能描述相似测试检索的精准度。长链条规划任务验证在任务流中多次注入的连贯性。采用A/B测试框架让同一Agent核心相同的LLM、相同的规划逻辑分别搭载静态上下文和动态注入器在测试集上并行运行并收集上述指标数据。5.3 基于反馈的迭代优化评估不是终点而是优化的起点。根据测试结果如果工具选择不准检查技能semantic_description的撰写质量是否足够区分度查询构造是否合理可以考虑引入技能使用的历史共现信息哪些技能经常被一起使用来优化检索排序。如果参数填充错误检查注入的function_schema是否清晰example call是否具有代表性可以考虑在上下文中加入更详细的参数约束说明。如果检索延迟影响体验优化向量检索的索引如使用HNSW引入技能缓存或对检索结果进行异步预加载。6. 进阶思考与未来方向实现一个基础的SkillsInjector只是一个开始。在实际生产中我们还会面临更多挑战这也指向了未来的优化方向。1. 技能抽象与分层当技能数量爆炸式增长后直接进行海量技能检索效率低下。可以引入技能分层或抽象机制。例如底层是具体的API函数send_email_smtp,send_email_ses上层是抽象技能send_message。Agent先检索抽象技能再根据具体上下文如成本、速度由系统选择具体的实现。这要求技能图谱具备层级关系。2. 上下文感知与状态管理当前的检索主要基于文本语义。更高级的注入器应能感知整个Agent的执行状态。例如上一步刚刚从数据库查询出了一组用户ID那么下一步检索“发送消息”类技能时系统应能自动将“用户ID列表”作为潜在的参数上下文进行提示甚至自动关联到“批量发送”技能。3. 技能的学习与生成终极形态的Agent或许不需要人类预先定义所有技能。它可以通过观察人类操作如RPA、阅读API文档甚至通过少量示例自动学习并生成新的技能描述function schema并将其注册到技能图谱中。SkillsInjector则负责管理和调用这些“自学成才”的技能。4. 与“记忆”模块的协同一个强大的Agent通常拥有长期记忆向量数据库存储历史经验和短期工作记忆。SkillsInjector应与记忆模块联动。例如当检索技能时可以同时查询记忆库“过去在类似情境下我调用哪个技能的成功率最高” 将历史效用作为检索排序的一个权重实现基于经验的技能推荐。踩坑心得在项目初期我们过于追求检索的“语义精准”使用了非常复杂的查询构造和重排算法结果引入了不小的延迟。后来发现对于80%的场景一个简单的、基于任务描述的向量检索加上基础的关键词过滤已经能带来显著提升。优化永远要权衡收益与成本先解决主要矛盾再打磨细节。另一个教训是技能元数据的质量至关重要。初期让开发同学随意写描述导致“查天气”的技能检索不出“获取温度”的查询。后来我们制定了技能描述的撰写规范要求必须包含功能、输入、输出和典型场景检索效果立刻上了一个台阶。动态技能上下文构建不是一个炫技的概念而是解决LLM Agent规模化、实用化道路上必然要攻克的基础设施。它让Agent从“一本通书读到老”的笨拙执行者变成了一个懂得“按需取用、灵活组合”的智能协作者。