尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

AI应用开发实战:Agent、Skill与Command三种执行单元对比解析

AI应用开发实战:Agent、Skill与Command三种执行单元对比解析 1. 项目概述当AI的三种“执行单元”同台竞技最近在折腾Claude相关的开发时脑子里突然冒出一个挺有意思的想法如果我们让AI领域里几个听起来很像、但内核不同的概念——Agent、Skill和Command——去完成同一件具体的任务会发生什么这就像让三个不同工种的人比如项目经理、技术专家和操作工去组装同一台电脑过程、效率和结果肯定大相径庭。这个实验的目的不是为了分个高下而是想彻底搞明白在构建AI应用时我们手头这些“工具”到底该怎么选、怎么用。简单来说Agent更像一个拥有自主规划和决策能力的“智能体”它理解目标能拆解步骤调用工具并处理过程中的意外。Skill则是一个封装好的、针对特定领域的“技能包”比如“写邮件”或“分析数据”它功能聚焦但通常需要被触发或调用。而Command是最基础的“原子指令”就像命令行里的一条具体操作比如“读取文件A的第10行”它直接、明确但缺乏上下文理解和灵活性。我选择用Claude作为实验平台一方面是因为它的代码解释器Claude Code和强大的上下文理解能力非常适合构建和测试这些概念另一方面网上关于Agent开发的讨论很多但把这三者放在同一个具体任务下横向对比的实践分享却很少。这次我们就用一个实际案例亲手把它们都实现一遍看看各自的代码怎么写、逻辑怎么跑、又会遇到哪些坑。2. 实验设计定义一个共同的挑战任务为了让对比有意义我们需要一个足够具体、有步骤、且可能遇到意外情况的任务。我设计了一个在开发中很常见的场景“监控并处理服务器日志中的错误”。任务详情如下目标定期检查指定目录下的应用日志文件例如/var/log/myapp/app.log找出所有包含“ERROR”关键词的行。处理将找到的每条错误日志提取出时间戳、错误级别和错误信息整理成结构化数据如JSON。通知如果发现新的错误与上一次检查的结果相比通过一个模拟的Webhook端点发送通知。持久化将本次检查的结果存储下来用于下一次的“新错误”比对。异常处理需要考虑日志文件不存在、格式意外、网络通知失败等情况。这个任务包含了感知读取文件、分析模式匹配、决策判断是否为新错误、执行发送通知、记忆存储状态等多个环节足以让三种执行单元的特性充分展现。实验环境与工具栈核心AIClaude 3.5 Sonnet (通过Claude API调用)。选择Sonnet是因为它在逻辑推理和代码生成上取得了很好的平衡且成本可控。开发语言Python。生态丰富适合快速原型开发。关键库openai(兼容Claude API)用于与Claude交互。schedule用于模拟定时任务在实际Agent中定时可能由外部调度器控制。pytest用于单元测试确保每个实现的功能正确性。辅助工具一个简单的Flask服务器用于模拟接收通知的Webhook。注意整个实验将在本地开发环境进行所有“服务器”、“日志文件”都是本地模拟的以避免对真实系统造成影响。重点在于逻辑的对比而非部署。3. 实现方式一构建一个全能型AgentAgent的实现思路是赋予其最高的自主权。我们不会告诉它每一步具体怎么做而是给它一个目标、可用的工具函数以及一些基本规则让它自己制定计划并执行。3.1 Agent的核心架构设计一个典型的Agent系统包含几个核心部分规划器Planner将大目标分解为可执行的小步骤。这里我们可以让Claude扮演规划器的角色。工具集ToolsAgent可以调用的具体函数如read_file,search_pattern,send_notification等。执行器Executor负责按计划调用工具并传递参数。记忆Memory存储历史执行结果、上下文和目标状态。我们用简单的JSON文件来模拟。反思Reflection评估执行结果决定是继续下一步、重试还是失败处理。这部分逻辑可以整合在执行循环中。我设计了一个相对简单的LogMonitorAgent类其工作流程如下图所示概念图[用户目标] - [Agent规划] - [选择工具] - [执行工具] - [观察结果] ^ | | v [更新记忆] -------------------------------- [反思与决策]它会在一个循环中运行直到任务完成或达到最大重试次数。3.2 关键代码实现与Claude的协作首先我们定义Agent可以使用的工具。这里用Python的装饰器来标记方便Claude识别import json import re from datetime import datetime from functools import wraps # 模拟一个存储上次结果的文件 LAST_RESULT_FILE “last_result.json” def tool(func): 装饰器用于标记Agent可用的工具函数 wraps(func) def wrapper(*args, **kwargs): print(f“[工具调用] {func.__name__} 参数: {args}, {kwargs}”) try: result func(*args, **kwargs) print(f“[工具结果] 成功: {result}”) return result except Exception as e: print(f“[工具结果] 失败: {e}”) return {“status”: “error”, “message”: str(e)} return wrapper tool def read_log_file(filepath: str) - str: 读取日志文件内容 try: with open(filepath, ‘r’, encoding‘utf-8’) as f: return f.read() except FileNotFoundError: return “” # 返回空字符串而非抛出异常让Agent决策 tool def extract_errors(log_content: str) - list: 从日志内容中提取ERROR行并解析 if not log_content: return [] error_lines [] # 简单的正则匹配假设日志格式为[时间戳] LEVEL 消息 pattern r‘\[(.*?)\] (ERROR) (.*)’ for line in log_content.split(‘\n’): match re.match(pattern, line) if match: timestamp, level, message match.groups() error_lines.append({ “timestamp”: timestamp, “level”: level, “message”: message.strip() }) return error_lines tool def load_previous_results() - list: 加载上一次检查的结果 try: with open(LAST_RESULT_FILE, ‘r’) as f: return json.load(f) except (FileNotFoundError, json.JSONDecodeError): return [] # 文件不存在或为空返回空列表 tool def save_current_results(results: list): 保存当前检查结果 with open(LAST_RESULT_FILE, ‘w’) as f: json.dump(results, f, indent2) tool def send_webhook_notification(new_errors: list, webhook_url: str) - dict: 发送新错误通知到Webhook import requests if not new_errors: return {“status”: “skipped”, “reason”: “No new errors”} payload {“new_errors”: new_errors, “checked_at”: datetime.now().isoformat()} try: # 这里用requests.post实际中可能需要处理超时、认证等 # 为简化我们假设有一个本地运行的测试服务器在 http://localhost:5000/webhook resp requests.post(webhook_url, jsonpayload, timeout5) resp.raise_for_status() return {“status”: “success”, “response”: resp.json()} except requests.exceptions.RequestException as e: return {“status”: “error”, “message”: f“Webhook调用失败: {e}”}接下来是Agent的核心——与Claude交互的规划与决策循环。我们通过API将目标、工具描述和历史上下文发送给Claude让它生成下一步的“思考”和要调用的工具。import openai import os class LogMonitorAgent: def __init__(self, api_key, model“claude-3-5-sonnet-20241022”): self.client openai.OpenAI(api_keyapi_key, base_url“https://api.anthropic.com”) self.model model self.memory [] # 存储对话和工具调用历史 self.available_tools { “read_log_file”: {“func”: read_log_file, “desc”: “读取指定路径的日志文件内容”}, “extract_errors”: {“func”: extract_errors, “desc”: “从日志文本中提取ERROR级别的日志行并解析为结构”}, “load_previous_results”: {“func”: load_previous_results, “desc”: “加载上一次保存的错误结果列表”}, “save_current_results”: {“func”: save_current_results, “desc”: “将当前错误结果列表保存到文件”}, “send_webhook_notification”: {“func”: send_webhook_notification, “desc”: “向指定的Webhook URL发送新错误通知”}, } def run(self, goal: str, max_steps10): 运行Agent尝试达成目标 system_prompt “””你是一个日志监控智能体。你的目标是{goal} 你可以使用以下工具 {tools_desc} 请逐步思考每次只选择一个最必要的工具调用。在得到工具返回的结果后分析结果并决定下一步。 如果任务成功完成或确定无法继续请输出‘任务结束’及最终结论。 “””.format(goalgoal, tools_descself._get_tools_description()) messages [{“role”: “user”, “content”: system_prompt}] for step in range(max_steps): print(f“\n Agent 步骤 {step1} ) # 调用Claude进行规划 response self.client.chat.completions.create( modelself.model, messagesmessages, max_tokens1000, temperature0.1, # 低温度保证决策稳定 ) assistant_msg response.choices[0].message.content print(f“[Agent思考] {assistant_msg}”) # 检查是否结束 if “任务结束” in assistant_msg: print(“[Agent] 任务流程终止。”) break # 解析Claude的回复提取工具调用指令这里简化实际需更鲁棒的解析 tool_name, tool_args self._parse_tool_call(assistant_msg) if tool_name and tool_name in self.available_tools: tool_func self.available_tools[tool_name][“func”] result tool_func(**tool_args) # 将工具调用和结果加入历史 self.memory.append({“step”: step, “tool”: tool_name, “args”: tool_args, “result”: result}) # 将结果反馈给Claude继续循环 feedback f“工具‘{tool_name}’调用结果{result}” messages.append({“role”: “assistant”, “content”: assistant_msg}) messages.append({“role”: “user”, “content”: feedback}) else: print(f“[Agent] 无法解析或找不到工具: {tool_name}。结束任务。”) break3.3 Agent模式的优缺点与实战心得运行一次Agent的典型输出可能如下 Agent 步骤 1 [Agent思考] 我需要先读取日志文件来获取内容。我将调用工具 read_log_file。 [工具调用] read_log_file 参数: (‘/var/log/myapp/app.log’,), {} [工具结果] 成功: [2024-01-01 10:00:00] INFO 应用启动...\n[2024-01-01 10:05:00] ERROR 数据库连接失败... Agent 步骤 2 [Agent思考] 成功读取日志。现在需要从中提取ERROR行。调用工具 extract_errors。 [工具调用] extract_errors 参数: (‘[2024-01-01 10:00:00] INFO...’,), {} [工具结果] 成功: [{‘timestamp’: ‘2024-01-01 10:05:00’, ‘level’: ‘ERROR’, ‘message’: ‘数据库连接失败’}] ...后续步骤加载历史、对比、发送通知、保存结果优点高自主性与适应性Agent能自己决定先做什么、后做什么。如果read_log_file返回空文件不存在一个设计良好的Agent可能会在思考后决定创建文件或直接报告失败而不是僵住。处理复杂性与不确定性面对未预见的日志格式或网络波动Agent可以通过“反思”环节调整策略比如重试或降级处理。可解释性通过记录“思考”过程我们能清晰看到Agent的决策链便于调试和信任构建。缺点与踩坑点开发与调试成本高让Claude稳定地输出可解析的工具调用指令本身就是一个挑战。你需要精心设计系统提示词System Prompt并编写健壮的解析代码来处理Claude输出的各种自然语言变体。延迟与成本每一步都需要调用一次LLM API整个流程的耗时和Token消耗远高于直接写代码。对于简单的定时任务这可能是“杀鸡用牛刀”。状态管理复杂Agent的“记忆”需要妥善管理对话历史messages会越来越长可能触及上下文窗口限制需要做摘要或选择性遗忘。可靠性依赖提示词工程Agent的行为极大程度上取决于你给它的系统指令。指令不清晰它可能陷入循环或做出匪夷所思的决策。实操心得在实现Agent时不要追求一步到位的完美规划。更好的方法是采用“小步快跑”先让Agent用1-2个步骤完成核心子任务稳定后再扩展。另外为工具函数提供清晰、结构化的返回值如始终返回包含status和data字段的字典能极大简化Agent的结果解析逻辑。4. 实现方式二封装一个专用SkillSkill的实现思路截然不同。它不负责宏观规划而是专注于把一件特定的事情做得又快又好。我们将整个“监控并处理日志错误”的任务打包成一个即插即用的技能。4.1 Skill的设计哲学与接口定义Skill应该是功能完整、接口清晰、内部逻辑封装的。用户或另一个调度系统只需要关心“触发”这个技能并传入必要的参数而不需要知道内部是如何一步步实现的。我设计的LogMonitorSkill类其接口非常简单class LogMonitorSkill: def execute(self, log_file_path: str, webhook_url: str) - dict: 执行日志监控技能。 参数: log_file_path: 日志文件路径 webhook_url: Webhook地址 返回: dict: 包含执行状态、发现的新错误数量等信息的字典 # 内部封装所有步骤 pass调用方只需要skill.execute(“/path/to/log”, “http://webhook.url”)即可。4.2 技能内部逻辑的完整实现Skill的内部实现了Agent模式中由Claude负责的“规划”部分但这是由我们开发者用硬编码的流程写死的。import hashlib class LogMonitorSkill: def __init__(self, state_file“skill_state.json”): self.state_file state_file def execute(self, log_file_path: str, webhook_url: str) - dict: 技能的主执行方法 result { “status”: “success”, “new_errors_count”: 0, “total_errors_count”: 0, “details”: “” } try: # 步骤1: 读取日志 log_content self._read_file(log_file_path) if log_content is None: result.update({“status”: “error”, “details”: “日志文件读取失败或不存在”}) return result # 步骤2: 提取错误 current_errors self._extract_errors(log_content) result[“total_errors_count”] len(current_errors) # 步骤3: 加载上一次的状态例如错误内容的哈希值 previous_error_hashes self._load_state() # 步骤4: 识别新错误通过对比哈希值实现 new_errors [] for error in current_errors: error_hash self._generate_hash(error) if error_hash not in previous_error_hashes: new_errors.append(error) previous_error_hashes.add(error_hash) result[“new_errors_count”] len(new_errors) # 步骤5: 如果有新错误发送通知 if new_errors: notification_result self._send_notification(new_errors, webhook_url) if notification_result.get(“status”) ! “success”: # 通知失败不影响主流程但记录在结果中 result[“notification_status”] “failed” result[“notification_detail”] notification_result.get(“message”) # 步骤6: 保存当前状态 self._save_state(previous_error_hashes) result[“details”] f“处理完成。发现{len(current_errors)}条错误其中{len(new_errors)}条为新错误。” except Exception as e: result.update({“status”: “error”, “details”: f“技能执行过程中发生未预期异常: {str(e)}”}) return result # 以下是内部辅助方法对调用方透明 def _read_file(self, path): # ... 同Agent中的read_log_file但直接返回None或内容 pass def _extract_errors(self, content): # ... 同Agent中的extract_errors pass def _load_state(self): # 加载已处理错误的哈希集合 try: with open(self.state_file, ‘r’) as f: return set(json.load(f)) except FileNotFoundError: return set() def _save_state(self, state_set): with open(self.state_file, ‘w’) as f: json.dump(list(state_set), f) def _generate_hash(self, error_dict): # 生成错误条目的唯一哈希用于去重 error_str json.dumps(error_dict, sort_keysTrue) return hashlib.md5(error_str.encode()).hexdigest() def _send_notification(self, new_errors, url): # ... 同Agent中的send_webhook_notification pass4.3 Skill模式的适用场景与优化技巧Skill的执行是静默且高效的skill LogMonitorSkill() outcome skill.execute(“./test.log”, “http://localhost:5000/webhook”) print(outcome) # 输出: {‘status’: ‘success’, ‘new_errors_count’: 2, …}优点高性能与低延迟所有逻辑都是本地代码执行无需多次网络API调用速度极快资源消耗低。高可靠性与确定性流程是固定的只要输入相同输出就相同。没有LLM生成内容的不确定性易于测试和调试。易于集成与部署作为一个封装好的类或函数可以轻松被其他系统如Celery定时任务、FastAPI接口调用。功能边界清晰一个Skill只做一件事符合单一职责原则代码可维护性高。缺点与局限缺乏灵活性如果任务需求变更比如需要增加“将错误分类”的功能就必须修改Skill的内部代码并重新部署。无法处理预定义之外的情况如果日志格式突然变成JSON行而不是正则匹配的格式这个Skill就会解析失败它不会像Agent那样尝试换一种解析方式。智能程度有限它只是一个自动化脚本没有“理解”和“决策”能力。优化技巧虽然Skill内部逻辑是硬编码的但我们可以通过配置化来增加其灵活性。例如将日志解析的正则模式、Webhook的请求头、状态存储的位置等提取为配置文件或初始化参数。这样当需求微调时我们可能只需要改配置而无需改代码。此外为Skill设计详尽的日志记录和监控指标对于生产环境排查问题至关重要。5. 实现方式三编排一组原子化CommandCommand模式将任务分解到最细粒度。每一个操作读取文件、解析行、发送请求都是一个独立的、可复用的命令。任务本身则由一个外部的“编排器”或“工作流引擎”通过组合这些命令来完成。5.1 Command的原子性设计与组合逻辑每个Command都是一个独立的函数或类它职责单一接口明确并且 ideally 是幂等的执行多次效果相同。我们定义以下几个命令# command_read.py def execute_read_log(file_path: str) - dict: “”“命令读取日志文件。返回{‘content’: str}或{‘error’: msg}”“” # ... 实现略 # command_parse.py def execute_parse_errors(log_content: str, pattern: str) - dict: “”“命令解析日志内容。返回{‘errors’: list}”“” # ... 实现略 # command_diff.py def execute_diff_errors(current_errors: list, previous_errors_file: str) - dict: “”“命令对比新旧错误。返回{‘new_errors’: list, ‘all_errors_hash’: set}”“” # ... 实现略 # command_notify.py def execute_send_notification(new_errors: list, webhook_url: str) - dict: “”“命令发送通知。返回{‘sent’: bool}”“” # ... 实现略 # command_save.py def execute_save_state(state_data: dict, state_file: str) - dict: “”“命令保存状态。返回{‘saved’: bool}”“” # ... 实现略任务的完成需要一个编排器Orchestrator来按顺序执行这些命令并处理它们之间的数据传递和错误。5.2 通过工作流引擎或简单脚本串联Commands我们可以用一个简单的Python脚本来实现最直接的线性编排# orchestrator_linear.py def run_log_monitoring_workflow(log_path, webhook_url, state_path): “”“线性编排命令的执行流程”“” context {} # 用于在命令间传递数据 # 1. 读取 read_result execute_read_log(log_path) if ‘error’ in read_result: return {“status”: “failed_at_read”, “error”: read_result[‘error’]} context[‘log_content’] read_result[‘content’] # 2. 解析 parse_result execute_parse_errors(context[‘log_content’], r‘\[(.*?)\] (ERROR) (.*)’) context[‘current_errors’] parse_result.get(‘errors’, []) # 3. 对比 diff_result execute_diff_errors(context[‘current_errors’], state_path) context[‘new_errors’] diff_result.get(‘new_errors’, []) context[‘new_errors_count’] len(context[‘new_errors’]) # 4. 通知 (条件执行) if context[‘new_errors_count’] 0: notify_result execute_send_notification(context[‘new_errors’], webhook_url) context[‘notification_success’] notify_result.get(‘sent’, False) # 5. 保存状态 save_state execute_save_state({‘hashes’: diff_result.get(‘all_errors_hash’, set())}, state_path) context[‘state_saved’] save_state.get(‘saved’, False) # 汇总结果 final_result { “status”: “completed”, “total_errors”: len(context[‘current_errors’]), “new_errors”: context[‘new_errors_count’], “context”: context } return final_result更复杂的编排可以使用专门的工作流引擎如Apache Airflow, Prefect或支持有向无环图DAG的库这样可以实现并行执行、条件分支和错误重试等高级特性。5.3 Command模式的灵活性与维护成本优点极高的可复用性execute_parse_errors命令不仅可以用于这个监控任务也可以被其他需要解析日志的任何任务调用。易于测试每个命令都可以独立进行单元测试mock其输入输出非常方便。编排灵活通过修改编排逻辑可以轻松创建新的工作流。例如可以轻松调整为先保存状态再发送通知或者并行执行解析和加载历史状态。便于监控与调试每个命令都是一个清晰的边界可以单独记录其开始时间、结束时间、成功与否便于分布式追踪和问题定位。缺点与挑战“胶水代码”复杂度编排器本身需要处理命令间的数据依赖、错误传递和流程控制这部分逻辑可能变得复杂尤其是当工作流有很多分支和循环时。数据传递开销命令之间通过上下文context字典传递数据可能会产生不必要的序列化/反序列化开销对于大数据量场景需要优化。分布式协调如果命令部署在不同的服务或机器上微服务架构编排会涉及网络通信、事务一致性等更复杂的问题。设计建议为每个Command定义严格的输入输出契约可以用Pydantic模型并强制所有结果都包含一个如{“status”: “success”|”error”, “data”: …, “error_msg”: …}的标准结构。这能让编排器的错误处理逻辑保持一致和清晰。对于简单的线性流程自己写编排脚本足矣一旦流程复杂引入一个轻量级的工作流引擎是值得的。6. 横向对比与选型指南为了更直观地对比我将三种实现方式的核心特性总结如下特性维度Agent (智能体)Skill (技能)Command (命令)核心思想自主规划与决策封装完整功能原子化操作灵活性⭐⭐⭐⭐⭐ (高)⭐⭐ (低)⭐⭐⭐⭐ (中通过编排调整)执行效率⭐ (低多次LLM调用)⭐⭐⭐⭐⭐ (高本地代码)⭐⭐⭐⭐⭐ (高本地代码)开发复杂度⭐⭐⭐⭐⭐ (高需设计提示词、解析、状态管理)⭐⭐ (低传统编程)⭐⭐⭐ (中需设计命令接口和编排)可维护性⭐⭐ (低行为受LLM影响调试难)⭐⭐⭐⭐⭐ (高逻辑清晰固定)⭐⭐⭐⭐ (高命令独立编排集中)可复用性⭐⭐ (低与具体任务目标强绑定)⭐⭐⭐ (中功能模块可复用)⭐⭐⭐⭐⭐ (高原子命令可任意组合)适用场景目标复杂、路径不确定、需应对未知情况功能明确、流程固定、要求高性能高稳定需要灵活组装、流程多变、强调复用和测试如何选择选择 Agent当你的需求是“给我一个目标你自己想办法完成”。例如一个自动化的客户支持助手需要理解模糊的用户请求自主决定是查询知识库、生成回复还是转接人工。这适合探索性、创新性的场景但要做好应对高成本和不可预测性的准备。选择 Skill当你的需求是“把这个确定的事情高效可靠地做好”。例如每周生成销售数据报表、每天备份数据库、监控服务器CPU使用率。这适合所有成熟的、需求稳定的后台自动化任务。这是目前企业中最主流、最稳妥的AI应用集成方式。选择 Command当你的需求是“我有许多基础操作需要经常以不同方式组合它们”。例如一个数据处理平台用户可以通过拖拽“读取CSV”、“过滤行”、“合并列”、“生成图表”等命令来创建自定义的数据流水线。这适合构建平台或框架或者流程经常需要调整的业务。在Claude生态中的具体体现Claude Code / Claude Desktop你可以直接让它编写Skill或Command的代码这是最高效的用法。构建自定义Agent你可以利用Claude API结合类似LangChain的框架构建具备专业工具调用能力的智能体。Skill as a Function在Claude的上下文中你可以将一个Skill描述成一个“函数”通过系统提示词告诉Claude这个函数的功能和调用方式让Claude在对话中决定何时“调用”它。这其实是一种混合模式。7. 常见问题与实战排坑记录在实际实现和测试这三种模式时我遇到了不少典型问题这里分享排查思路和解决方案。Q1: Agent模式中Claude不按我期望的格式返回工具调用指令怎么办现象Claude的回复是“我应该先调用A工具参数是…”而不是结构化的{“tool”: “A”, “args”: {…}}。排查首先检查系统提示词是否明确要求了输出格式。其次Claude在对话初期可能需要“示例”来学习。解决在系统提示词中提供少样本示例Few-shot Example。例如请用以下JSON格式回应 {“thought”: “你的思考过程”, “action”: {“name”: “工具名”, “args”: {参数键: 参数值}}} 示例 用户目标是检查日志。 你{“thought”: “我需要先读取日志文件内容”, “action”: {“name”: “read_log_file”, “args”: {“filepath”: “/var/log/app.log”}}}这能极大提高输出格式的稳定性。Q2: Skill执行时如何避免重复处理同一条错误日志现象每次运行Skill即使日志内容没变它都报告有新错误。排查对比逻辑可能只比较了错误信息字符串但日志可能附带进程ID、线程ID等每次运行都变化的字段。解决像我们实现中那样对错误条目计算一个稳定的哈希值。哈希时可以只选取核心字段如时间戳、错误级别、核心错误消息忽略可变字段。或者更简单的方法是如果业务允许只记录错误首次出现的时间之后相同错误不再报警。Q3: Command编排中某个命令失败如何让整个工作流优雅失败或重试现象send_webhook_notification命令因网络问题失败导致整个流程中断状态也未保存。排查线性脚本中一个命令失败会抛出异常中断后续所有命令。解决在编排器中实现错误处理策略。例如快速失败任何命令失败立即终止并回滚已执行的有副作用命令如果可能。忽略并继续对于非核心命令如通知记录失败但继续后续流程。重试对可能 transient failure 的命令如网络请求加入指数退避的重试机制。def execute_with_retry(command_func, max_retries3, **kwargs): for i in range(max_retries): try: return command_func(**kwargs) except TemporaryError as e: # 自定义的临时错误异常 if i max_retries - 1: raise time.sleep(2 ** i) # 指数退避Q4: 如何为这三种模式编写有效的单元测试对于Agent测试重点不在LLM的输出不可控而在工具函数和解析逻辑。Mock掉LLM API调用模拟返回固定的“思考”和“动作”文本测试你的Agent是否能正确解析并调用工具。对于Skill这是最容易测试的。为execute方法提供不同的输入正常日志、空文件、错误格式文件、网络不通等断言其输出状态和结果是否符合预期。使用pytest和unittest.mock来模拟文件IO和网络请求。对于Command对每个命令函数进行独立的单元测试。对于编排器测试其流程控制逻辑可以通过Mock每个命令的返回值来模拟各种成功和失败场景。Q5: 在生产环境中如何调度和监控这些任务调度无论是哪种模式最终都需要一个触发器。对于定时任务成熟的选择是Linux Cron、Celery Beat或Apache Airflow。将你的Agent主循环、Skill的execute方法或Command编排脚本包装成一个可执行入口交给调度器。监控关键是要有日志和指标。在代码关键节点记录INFO、WARNING、ERROR级别的日志。记录每次运行的耗时、处理日志行数、发现错误数、通知成功与否等作为指标可以推送到Prometheus或类似系统。对于Agent额外记录其“思考”步骤和工具调用历史这对调试其异常行为至关重要。最后无论选择哪种模式从简单开始逐步迭代总是对的。可以先用一个Skill快速实现核心需求上线跑通。如果发现流程经常需要人工调整再考虑将其拆分为Command以增加灵活性。如果遇到了大量无法预见的边缘情况再评估引入Agent的智能决策能力是否划算。技术选型没有银弹最适合当前阶段业务需求和技术团队能力的就是最好的。
返回列表