LLM智能体技能程序:从提示词驱动到程序化编排的工程实践

📅 2026/8/18 4:30:44
LLM智能体技能程序:从提示词驱动到程序化编排的工程实践
1. 项目概述当LLM智能体学会“编程”最近在跟几个做AI应用落地的朋友聊天大家普遍有个痛点大语言模型LLM本身很聪明能说会道但让它去干点实际的、复杂的活儿比如跨系统操作、处理多步骤业务流程就常常显得“眼高手低”。它可能给你一个完美的计划但执行起来却漏洞百出或者干脆卡在某个环节。这背后的核心问题是LLM缺乏一种稳定、可靠、可复用的“技能”执行框架。这正是“Harnessing LLM Agents with Skill Programs”用技能程序驾驭LLM智能体这个方向要解决的。它不是一个具体的工具而是一种设计范式。简单来说就是把过去我们手动给智能体“打补丁”、写一堆胶水代码才能完成的复杂任务抽象成一个个定义清晰、可组合、可调度的“技能程序”。你可以把它想象成给LLM智能体配备了一个功能强大的“技能商店”和一套严谨的“工作流引擎”。智能体大脑负责理解意图、规划和决策而具体的执行动作则由这些预先定义好的、经过验证的“技能程序”来可靠地完成。这个概念最近热度很高尤其是随着Lilian Weng等研究者对LLM Powered Autonomous Agents的系统性梳理以及像HASPHierarchical Agent Skill Programs这类框架的提出让大家看到了构建更强大、更实用AI智能体的清晰路径。它跳出了单纯依赖提示工程Prompt Engineering的范畴进入了“程序化智能体”的新阶段。对于开发者而言这意味着我们能构建出真正能处理现实世界复杂任务、且行为可预测、结果可保障的AI应用。2. 核心理念从“提示词驱动”到“程序化编排”要理解技能程序的价值得先看看我们之前是怎么折腾LLM智能体的。传统方式我称之为“提示词驱动”模式。比如你想让智能体帮你分析一份销售报告然后发一封总结邮件。典型的做法是写一个非常长的、结构复杂的提示词Prompt里面塞满了指令、格式要求、思考步骤甚至还要用上“Chain-of-Thought”之类的技巧引导模型一步步思考。这种方式的问题显而易见脆弱性提示词稍微变动或者遇到模型没见过的任务变体输出就可能失控。不可靠性模型可能会“幻觉”出一些不存在的步骤或工具调用。难以复用为特定任务精心设计的提示词很难直接应用到另一个略有不同的任务上。缺乏保障对于涉及外部API调用、数据修改等有副作用的操作纯提示词无法提供任何执行原子性、错误回滚等保障。而“技能程序”范式则引入了软件工程的思想。它将一个完整的、可执行的任务单元封装成一个“程序”。这个程序不仅仅是一段自然语言描述它通常包含清晰的接口定义输入参数、输出格式。明确的执行逻辑可能是一段代码、一个工作流定义、或一系列原子操作的组合。可靠的工具绑定与外部API、数据库、软件工具进行稳定连接。错误处理机制定义执行失败时的应对策略。在这个范式下LLM智能体的角色发生了转变。它不再需要“凭空”生成每一个操作步骤而是更像一个“调度员”或“架构师”。它的核心工作变成了理解用户意图。从技能库中检索和组合合适的技能程序。为技能程序填充必要的参数。监督技能程序的执行流程并在遇到分支或异常时做出决策。注意这并不意味着LLM变得不重要了。恰恰相反它的价值从“微观操作生成”提升到了“宏观任务分解与规划”这对模型的推理和规划能力提出了更高要求但也让它的输出更可控、结果更可靠。2.1 技能程序的核心组件PFs程序函数在像HASP这样的框架中技能程序的具体体现就是“程序函数”Program Functions, PFs。理解PFs是掌握整个范式的关键。你可以把一个PF类比为编程中的一个函数或者一个微服务。一个标准的PF通常包含以下几个部分描述Description用自然语言清晰描述这个技能是做什么的。这是给LLM看的“说明书”用于让智能体理解何时该调用此技能。例如“根据给定的城市名称查询该城市未来三天的天气预报详情。”签名Signature定义函数的输入和输出。这通常是强类型的例如输入city_name: string输出{ date: string, weather: string, temp_range: [low: number, high: number] }[]实现Implementation这是技能的具体执行逻辑。它可以是一段代码例如Python函数里面封装了调用天气API的细节。一个工作流例如用YAML或DSL定义的多个步骤序列。对另一个LLM的调用但这次调用被严格限定在特定上下文中用于完成子任务。验证与后处理Validation Post-processing对执行结果进行清洗、格式化或验证确保返回给智能体的数据是结构良好且可用的。为什么PF比单纯的工具调用Tool Calling更强大OpenAI的Function Calling也是一种让LLM使用外部能力的方式。但PF更进一步层次性PF内部可以封装多个基础工具调用甚至调用其他PF形成层次结构。一个“安排会议”的PF内部可能依次调用“查询日历空闲时间”、“生成会议议程草稿”、“发送日历邀请”等多个子技能。状态管理PF可以在执行过程中维护局部状态而简单的工具调用通常是无状态的。流程控制PF内部可以包含条件判断、循环等逻辑实现更复杂的业务流程。2.2 HASP框架一个具体的实现蓝图HASPHierarchical Agent Skill Programs是学术界提出的一个概念框架它很好地诠释了技能程序的层次化思想。我们可以把它看作构建此类智能体的最佳实践指南。HASP的核心是“层次化”。它将智能体的能力组织成多个层级高层技能High-level Skills对应复杂的、面向业务的目标。例如“完成客户 onboarding”、“处理用户投诉”。这些技能通常由LLM智能体直接规划和调用。中层技能Mid-level Skills由高层技能分解而来是具体的任务单元。例如“验证客户邮箱”、“创建用户账户”、“发送欢迎邮件”。这些就是PF的主要形态。底层动作Low-level Actions最原子的操作如调用一个特定的API、执行一条数据库查询、操作一个UI元素。这些被封装在中层技能的“实现”里。智能体的工作流就变成了接收用户目标如“为新客户小明开通服务” - LLM将其分解为高层技能序列 - 每个高层技能进一步分解或直接映射到中层技能PF - 调度执行这些PF - 整合结果并推进。这种层次化带来了巨大的优势可复用性“发送邮件”这个PF既可以被“客户onboarding”流程使用也可以被“发送周报”流程使用。可维护性修改邮件服务的API只需要更新底层的“发送邮件”动作或对应的PF实现所有上层业务都不受影响。可解释性整个任务的执行过程被清晰地记录为技能调用树便于调试和审计。3. 实战设计并实现你的第一个技能程序理论说了这么多我们来点实际的。假设我们要为一个“智能销售助手”构建一个核心技能qualify_sales_lead评估销售线索。目标输入一个潜在客户Lead的基本信息公司名称、行业、官网、需求描述自动输出一个评分0-10分和下一步行动建议。3.1 技能程序PF定义我们首先用类YAML的格式来定义这个PF的元数据这将是注册到智能体“技能库”中的信息name: qualify_sales_lead description: “根据潜在客户的公司信息、行业和需求描述综合评估其销售线索质量给出评分和行动建议。评分考虑公司规模匹配度、需求紧迫性、预算可能性等因素。” signature: input: company_name: string industry: string website: string (optional) need_description: string output: score: integer (范围 0-10) reasoning: string (评分理由分点说明) recommendation: string (“立即联系”、“培育孵化”、“暂缓”) confidence: float (0-1) implementation_type: “composite” # 这是一个组合技能内部会调用多个子能力3.2 实现分解组合技能的构建qualify_sales_lead是一个典型的组合技能。它自己并不直接干活而是协调多个子技能共同完成。它的内部执行逻辑伪代码如下def execute_qualify_sales_lead(input_params): # 子技能1公司背景调研 company_info execute_subskill(“research_company_background”, { “company_name”: input_params.company_name, “website”: input_params.website }) # 子技能2需求分析 need_analysis execute_subskill(“analyze_customer_need”, { “industry”: input_params.industry, “need_description”: input_params.need_description }) # 子技能3评分与决策这里可以是一个规则引擎也可以是一个专用的微调小模型 # 我们假设用一个决策LLM来整合前两步信息 qualification_result execute_subskill(“llm_judgment_scoring”, { “company_background”: company_info.summary, “need_analysis”: need_analysis.key_points, “urgency”: need_analysis.urgency_level, “budget_indicator”: company_info.budget_indicator }) # 格式化输出确保符合PF定义的签名 return { “score”: qualification_result.score, “reasoning”: qualification_result.reasoning, “recommendation”: qualification_result.recommendation, “confidence”: qualification_result.confidence }你看这个主PF协调了三个子技能research_company_background这可能是一个封装了爬取公开信息、查询企业数据库API的技能。analyze_customer_need这是一个纯文本分析技能可能用LLM提取需求关键词、判断紧迫性。llm_judgment_scoring这是一个决策技能输入结构化信息输出评分和建议。3.3 关键实现细节与避坑指南细节1子技能的稳定性设计research_company_background技能如果依赖外部爬虫极易失败。在实现时必须加入重试机制和降级方案。例如当爬取官网失败时转而调用天眼查或企查查的API如果有权限或者仅使用公司名称进行简单的网络搜索摘要。# 伪代码示例带降级的公司调研 def research_company_background(company_name, website): data None if website: try: data scrape_website(website) # 方法1爬取官网 except ScrapeError: log.warning(f“官网爬取失败尝试公开API: {company_name}”) if not data: try: data query_enterprise_api(company_name) # 方法2企业信息API except ApiError: data {“summary”: f“无法获取{company_name}的详细信息”} # 方法3降级 # 提取关键信息规模、融资、业务等 return extract_key_info(data)细节2LLM子技能的提示词工程llm_judgment_scoring技能虽然也调用LLM但它的提示词是高度专业化、固定且经过反复测试的。这与让主智能体自由发挥完全不同。它的提示词可能长这样“你是一个专业的销售线索评估专家。请根据以下结构化信息进行评估 公司背景{company_background} 需求分析{need_analysis} 需求紧迫性{urgency} 预算迹象{budget_indicator}请严格按照以下JSON格式输出不要有任何额外解释 { “score”: (0-10的整数10分最高), “reasoning”: “分点列出评分依据每点不超过20字”, “recommendation”: “三选一’立即联系’、’培育孵化’、’暂缓’”, “confidence”: (0-1之间的小数表示你对评估的确信程度) }”这种提示词确保了输出结构的绝对稳定方便主PF进行后续处理。实操心得在构建技能库时一定要把“可变”和“不变”的部分分离。LLM的创造性应用于任务分解和参数填充可变部分而每个技能程序内部的执行逻辑和对外交互必须是稳定、可靠的不变部分。切忌在技能实现内部使用开放式的、不稳定的LLM调用。4. 智能体与技能程序的协同工作流有了技能程序库我们的LLM智能体该如何工作呢整个协同流程可以概括为“规划-调用-执行-整合”的循环。4.1 规划阶段从目标到技能树用户说“帮我跟进一下上周研讨会收集的50个潜在客户筛选出高价值的并给他们的负责人发一封个性化的跟进邮件。”主智能体通常是一个具备强规划能力的LLM如GPT-4的工作是理解与分解理解这是一个批处理任务涉及“批量评估线索”和“批量个性化沟通”。检索技能从技能库中检索相关技能。它会发现qualify_sales_lead评估单个线索和send_personalized_email发送邮件这两个PF。制定计划生成一个执行计划“对于客户列表中的每一个客户首先调用qualify_sales_lead技能如果评分大于7则调用send_personalized_email技能。邮件模板需要融入客户公司和需求信息。”参数化计划中会标明qualify_sales_lead的输入来自客户数据库的每条记录send_personalized_email的收件人、姓名、公司等信息来自客户记录邮件内容需要根据qualify_sales_lead输出的reasoning字段进行个性化生成。这个计划本身也可以被视作一个动态生成的、一次性的“高阶技能程序”。4.2 调用与执行阶段技能调度引擎智能体生成计划后就将具体的执行工作交给了“技能调度引擎”。这个引擎是系统的核心执行组件它负责解析计划识别出需要调用的PF序列及其依赖关系。管理状态维护整个工作流的上下文例如当前处理到哪个客户上一步的输出是什么。调用PF以正确的参数调用每个PF。PF的执行可能是在本地也可能是远程服务。处理错误当某个PF执行失败如网络超时、API限额引擎会根据预定义的策略重试、跳过、终止整个流程进行处理并将错误信息反馈给智能体由智能体决定如何调整计划。4.3 整合与推进阶段闭环与学习所有PF执行完毕后调度引擎将结果汇总给智能体。智能体需要结果整合将多个PF的输出整合成对用户有意义的答复。例如“已处理50个线索其中12个被评估为高价值评分7已向其负责人发送个性化跟进邮件。列表如下...”状态汇报如果任务是长期或持续的如“监控某个话题并每日汇报”智能体需要更新任务状态并可能规划下一轮的执行。经验学习可选但重要高级的系统可以将本次执行的轨迹哪个PF被调用、输入输出、成功与否记录下来用于优化未来的规划。例如如果发现research_company_background技能对某些行业经常失败智能体下次规划时可能会为这些行业的客户添加一个备用的调研技能或者直接提示用户提供更多信息。5. 技术选型与架构设计考量当你决定采用技能程序范式来构建智能体时会面临一系列技术选择。这里没有银弹只有适合你场景的权衡。5.1 技能实现方式对比实现方式描述优点缺点适用场景纯代码函数用Python/Go等语言编写实现逻辑。性能高控制力强易于调试和测试。开发成本高不易动态修改对非开发者不友好。核心业务逻辑需要高性能计算或复杂数据处理的技能。工作流引擎使用Airflow、Prefect、或低代码工作流工具定义。可视化易于编排复杂流程内置重试、监控。运行时开销大与智能体框架集成可能需要适配。涉及多系统、多步骤的复杂业务流程技能。专用微服务将技能部署为独立的HTTP/gRPC服务。语言无关可独立扩展版本化管理方便。网络延迟运维复杂度高。团队协作或技能本身是重型的独立应用。封装LLM调用技能内部主要是一个精心设计的提示词模板。灵活能处理非结构化任务开发快。成本高API调用延迟不稳定输出可能波动。需要创造性、理解或生成自然语言的子任务。我的建议采用混合模式。对于确定性强、逻辑固定的操作如数据查询、API调用用纯代码函数实现。对于需要决策、分类、总结等认知任务用封装好的LLM调用。然后将多个这样的原子技能用一个轻量级的工作流描述可以是简单的Python脚本或DSL组合成高级PF。这样在灵活性和可靠性之间取得了平衡。5.2 技能注册与发现机制智能体如何知道有哪些技能可用这就需要一套注册与发现机制。集中式注册表所有PF在一个中心目录如数据库、配置文件中注册包含其名称、描述、签名和访问端点。智能体在规划时查询这个目录。这是最简单直接的方式。动态发现PF在启动时向一个服务注册中心如Consul, etcd注册自己。智能体通过查询注册中心来发现可用技能。这更适合微服务架构。文件系统扫描将每个PF定义为一个独立的文件如YAML智能体框架在启动时扫描特定目录加载所有技能。这种方式便于版本控制和管理。关键点无论哪种方式技能的描述Description和签名Signature必须机器可读且对智能体友好。描述要足够准确以便LLM能理解其用途签名要明确以便智能体能正确生成调用参数。5.3 错误处理与鲁棒性设计这是技能程序范式能否投入生产的关键。必须为整个系统设计多层错误处理PF内部错误处理每个PF自身要有完善的异常捕获和容错逻辑并返回统一的错误格式。调度引擎重试与降级引擎调用PF失败时应根据错误类型网络超时、临时错误、逻辑错误决定重试、切换备用PF还是上报。智能体级异常恢复当引擎上报一个无法处理的错误时智能体需要介入。它可以尝试重新规划例如换一种方式完成任务或者向用户请求更多信息/权限。一个实用的模式是**“技能链路备用”**。为关键技能定义1-2个功能相似的备用技能。当主技能失败时引擎自动尝试备用技能。例如“发送邮件”主技能用SMTP备用技能用第三方邮件发送API。6. 常见问题与实战排坑记录在实际构建和运用技能程序时你会遇到不少坑。以下是我从项目中总结的一些典型问题及解决方案。6.1 技能描述“不准”导致智能体“叫错人”问题你定义了一个技能叫fetch_news描述是“获取新闻”。当用户说“看看特斯拉最近有什么动态”时智能体可能会调用这个技能。但你的fetch_news实现可能只从某个特定科技媒体抓取结果完全没提到特斯拉。根因技能描述太宽泛与实现能力不匹配。解决描述要精确限定范围。改为“从预定义的科技新闻源RSS中获取最新的新闻标题和摘要。”这样智能体只有在用户明确要求“科技新闻”时才会考虑调用它。对于更通用的需求你应该有另一个技能search_web_news。6.2 PF输出格式“漂移”导致下游技能崩溃问题analyze_sentimentPF 承诺输出{“sentiment”: “positive”/“negative”, “score”: float}。但某次LLM调用后它返回了{“sentiment”: “非常积极”, “confidence”: 0.95}。导致依赖此输出的generate_responsePF 解析失败。根因依赖LLM生成输出的PF其输出结构可能不稳定。解决强验证在PF的实现末尾必须加入输出模式验证。使用JSON Schema或Pydantic模型确保返回的数据结构严格符合签名定义。不符合则触发重试或返回明确错误。后处理层在LLM输出后添加一个简单的后处理函数将非标准表述映射到标准值。例如将“非常积极”、“积极”都映射为“positive”。6.3 技能组合产生“循环依赖”或“死锁”问题技能A在执行时需要调用技能B的结果而技能B又依赖于技能A提供的某个中间状态。或者两个并行执行的技能同时去修改数据库里的同一条记录。根因规划阶段没有检查出技能间的资源竞争或循环依赖。解决在PF签名中声明资源让每个PF声明它需要“读”或“写”哪些资源如“用户表:写”、“产品库存:读”。智能体在规划时可以进行简单的冲突检测。设计无状态技能尽可能让PF成为纯函数输出仅由输入决定不依赖外部可变状态。状态由智能体或调度引擎通过输入参数传递。使用事务或锁对于必须操作共享状态的技能由调度引擎协调将其序列化或在数据库层面使用事务。6.4 长流程任务中的“上下文丢失”问题一个需要调用5个PF才能完成的长任务执行到第3个PF时智能体已经“忘记”了最初的目标和之前的上下文导致第4个PF的参数填充错误。根因LLM智能体的工作记忆有限在长链条中容易丢失信息。解决显式状态传递调度引擎必须负责将整个工作流的“上下文”包括原始目标、已执行步骤的结果作为参数传递给每一个需要它的PF。不要依赖LLM的隐性记忆。设计“摘要”技能在关键节点插入一个summarize_contextPF将冗长的中间结果提炼成简洁的摘要再传递给后续步骤和智能体减轻其记忆负担。采用分层规划不要一次性规划所有细节。让智能体先做高层规划然后每完成一个高层步骤对应一个组合PF再规划下一个步骤的细节。这符合HASP的层次化思想。6.5 性能瓶颈串行调用导致的延迟问题一个任务需要调用10个PF如果全部串行执行总耗时将是每个PF耗时的总和用户体验极差。解决依赖分析并行化调度引擎需要分析PF之间的数据依赖关系。没有依赖关系的PF可以并行执行。异步非阻塞调用采用异步编程模型在等待一个PF尤其是调用外部API的返回时可以去执行其他计算或准备下一个PF的输入。设置超时与截止时间为每个PF设置合理的超时时间。对于非关键路径上的慢技能可以设置较短的超时超时后使用默认值或跳过保证整体流程的响应性。构建基于技能程序的LLM智能体是一个将AI的“智能”与软件的“工程化”紧密结合的过程。它要求我们不仅是一个Prompt工程师更要是一个系统架构师。你需要仔细地分解领域问题设计稳定可靠的技能模块并构建一个能灵活协调它们的“大脑”。这条路虽然前期设计成本较高但它带来的可维护性、可靠性和可扩展性对于构建严肃的、生产级的AI应用而言是绝对值得的。