""" Step 03: 构建简单 Agent ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 🎓 本节内容: 1. 什么是 Agent?(LLM + Tool + Loop) 2. Agent 的核心循环:Think → Act → Observe 3. 如何构建一个完整的 Agent 4. LangGraph 的基本用法 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ """ from typing import TypedDict, List, Dict, Any, Literal, Optional from dataclasses import dataclass from abc import ABC, abstractmethod import json # ═══════════════════════════════════════════════════════════════════════════════ # 第一部分:理解 Agent 的本质 # ═══════════════════════════════════════════════════════════════════════════════ """ Agent 的核心公式: Agent = LLM + Tool + Loop 为什么这个公式很重要? 1. LLM:负责"思考"——理解用户意图,决定下一步行动 2. Tool:负责"行动"——执行具体的操作 3. Loop:负责"循环"——不断执行直到任务完成 没有 LLM,Agent 只能机械地执行预设命令 没有 Tool,LLM 只能思考不能行动 没有 Loop,Agent 只能执行一步 只有三者结合,才是真正的"智能代理" """ # ═══════════════════════════════════════════════════════════════════════════════ # 第二部分:定义 Agent State # ═══════════════════════════════════════════════════════════════════════════════ class SimpleAgentState(TypedDict, total=False): """ 简单 Agent 的状态 相比 Step 02 的状态,这个状态更精简,适合教学 """ # 用户输入 user_input: str # 对话历史 messages: List[dict] """ 格式: [ {"role": "user", "content": "用户说了什么"}, {"role": "assistant", "content": "助手说了什么"}, {"role": "system", "content": "系统做了什么"}, ] """ # 当前行动 current_action: str """ 当前正在执行的动作: - "thinking": LLM 正在思考 - "using_tool": 正在使用工具 - "finished": 完成 """ # 工具调用相关 tool_calls: List[dict] # 记录所有工具调用 last_tool_result: Any # 最后一个工具的执行结果 tool_result: Any # 当前工具执行结果 # 生成结果 final_output: str # 最终输出 generation: str # 当前正在生成的文本 # 状态 status: str """ 状态值: - "input": 等待输入 - "thinking": 思考中 - "acting": 执行中 - "finished": 完成 - "error": 错误 """ error: str # ═══════════════════════════════════════════════════════════════════════════════ # 第三部分:定义 Tool 接口 # ═══════════════════════════════════════════════════════════════════════════════ @dataclass class ToolCall: """工具调用的数据结构""" name: str # 工具名称 arguments: dict # 传递给工具的参数 result: Any = None # 工具执行结果 error: str = None # 错误信息 class BaseTool(ABC): """ 工具基类(简化版,来自 Step 01) """ @property @abstractmethod def name(self) -> str: """工具名称""" pass @property @abstractmethod def description(self) -> str: """工具描述""" pass @abstractmethod def execute(self, **kwargs) -> Any: """执行工具""" pass # ═══════════════════════════════════════════════════════════════════════════════ # 第四部分:实现具体的 Tool # ═══════════════════════════════════════════════════════════════════════════════ class CalculatorTool(BaseTool): """计算器工具""" @property def name(self) -> str: return "calculator" @property def description(self) -> str: return "执行数学计算。输入一个数学表达式,返回计算结果。" def execute(self, expression: str) -> str: """执行计算""" try: result = eval(expression, {"__builtins__": {}}, {}) return str(result) except Exception as e: return f"计算错误: {e}" class JRXMLGeneratorTool(BaseTool): """JRXML 生成工具""" @property def name(self) -> str: return "jrxml_generator" @property def description(self) -> str: return "生成 JasperReports JRXML 报表模板。根据用户需求生成 XML 代码。" def execute(self, requirement: str, context: str = "") -> str: """ 执行 JRXML 生成 实际应用中,这里会调用 LLM 这里用模拟数据演示 """ # 模拟生成 template = f''' {requirement} 名称 $F{{name}} ''' return template class TemplateSearchTool(BaseTool): """模板搜索工具""" @property def name(self) -> str: return "template_search" @property def description(self) -> str: return "搜索 JRXML 报表模板。在知识库中搜索相关的报表模板作为参考。" def execute(self, keyword: str, limit: int = 3) -> str: """搜索模板""" # 模拟搜索结果 results = [ f"模板1: 关于 '{keyword}' 的基础报表模板", f"模板2: '{keyword}' 的高级报表模板", f"模板3: '{keyword}' 的数据透视表模板", ] return "\n".join(results[:limit]) class JRXMLValidatorTool(BaseTool): """JRXML 验证工具""" @property def name(self) -> str: return "jrxml_validator" @property def description(self) -> str: return "验证 JRXML 报表模板的正确性。检查 XML 语法和 JasperReports 规范。" def execute(self, jrxml: str) -> dict: """验证 JRXML""" # 模拟验证 issues = [] # 检查基本结构 if " 根元素") if " dict: """ 决定下一步行动 返回: { "action": "use_tool" | "respond" | "finish", "tool_name": str, # 如果 action == "use_tool" "tool_args": dict, # 如果 action == "use_tool" "response": str, # 如果 action == "respond" } """ user_input = state.get("user_input", "").lower() messages = state.get("messages", []) tool_result = state.get("tool_result") # 如果有工具执行结果 if tool_result is not None: return { "action": "respond", "response": f"工具执行完成,结果:{tool_result}" } # 根据用户输入决定行动 if "生成" in user_input or "报表" in user_input: return { "action": "use_tool", "tool_name": "jrxml_generator", "tool_args": { "requirement": state.get("user_input", ""), "context": "" } } elif "搜索" in user_input or "模板" in user_input: return { "action": "use_tool", "tool_name": "template_search", "tool_args": { "keyword": user_input.replace("搜索", "").replace("模板", "").strip(), "limit": 3 } } elif "验证" in user_input and "<" in user_input: return { "action": "use_tool", "tool_name": "jrxml_validator", "tool_args": { "jrxml": user_input } } elif any(op in user_input for op in ["计算", "+", "-", "*", "/"]): # 提取表达式 import re expr = re.search(r'[\d\+\-\*\/\(\)\.]+', user_input) if expr: return { "action": "use_tool", "tool_name": "calculator", "tool_args": { "expression": expr.group() } } else: # 默认:直接回答 return { "action": "respond", "response": f"我理解你的需求:{state.get('user_input', '')}。请问具体想做什么?" } # ═══════════════════════════════════════════════════════════════════════════════ # 第六部分:构建 Agent # ═══════════════════════════════════════════════════════════════════════════════ class SimpleAgent: """ 简单 Agent 实现 这个 Agent 的工作流程: 1. 接收用户输入 2. LLM(大脑)决定下一步行动 3. 执行行动(使用工具或直接回答) 4. 返回结果 5. 循环直到用户结束 """ def __init__(self): # 初始化工具 self.tools = { "calculator": CalculatorTool(), "jrxml_generator": JRXMLGeneratorTool(), "template_search": TemplateSearchTool(), "jrxml_validator": JRXMLValidatorTool(), } # 初始化大脑 self.brain = AgentBrain() # 初始化状态 self.state = SimpleAgentState( messages=[], tool_calls=[], status="input" ) def reset(self): """重置 Agent 状态""" self.state = SimpleAgentState( messages=[], tool_calls=[], status="input" ) def get_available_tools(self) -> list[dict]: """获取可用工具列表""" return [ {"name": tool.name, "description": tool.description} for tool in self.tools.values() ] def execute_tool(self, tool_name: str, **kwargs) -> Any: """ 执行工具 封装了工具执行的逻辑,包括: - 查找工具 - 执行工具 - 记录调用 - 处理错误 """ if tool_name not in self.tools: return {"error": f"工具 '{tool_name}' 不存在"} tool = self.tools[tool_name] try: result = tool.execute(**kwargs) # 记录工具调用 self.state["tool_calls"].append({ "tool": tool_name, "args": kwargs, "result": result }) return result except Exception as e: error_result = {"error": str(e)} self.state["tool_calls"].append({ "tool": tool_name, "args": kwargs, "error": str(e) }) return error_result def process(self, user_input: str) -> str: """ 处理用户输入 这是 Agent 的主入口 每次用户输入,调用这个方法来处理 """ # 1. 保存用户输入 self.state["user_input"] = user_input self.state["messages"].append({ "role": "user", "content": user_input }) self.state["status"] = "thinking" # 2. 大脑决定行动 decision = self.brain.decide(self.state) # 3. 执行行动 if decision["action"] == "use_tool": # 使用工具 self.state["status"] = "acting" tool_name = decision["tool_name"] tool_args = decision["tool_args"] result = self.execute_tool(tool_name, **tool_args) # 保存结果到状态 self.state["tool_result"] = result self.state["current_action"] = f"使用了工具: {tool_name}" # 再次调用大脑,决定下一步 decision = self.brain.decide(self.state) if decision["action"] == "respond": response = decision["response"] self.state["messages"].append({ "role": "assistant", "content": response }) self.state["final_output"] = response self.state["status"] = "finished" return response elif decision["action"] == "finish": self.state["status"] = "finished" return self.state.get("final_output", "任务完成") # 默认返回当前状态 return self.state.get("current_action", "处理中...") def get_history(self) -> list[dict]: """获取对话历史""" return self.state.get("messages", []) # ═══════════════════════════════════════════════════════════════════════════════ # 第七部分:使用 LangGraph 重构(可选进阶内容) # ═══════════════════════════════════════════════════════════════════════════════ """ LangGraph 是 LangChain 提供的图结构框架 核心概念: 1. StateGraph:状态图 2. Node:节点(执行函数) 3. Edge:边(状态转换) 4. Conditional Edge:条件边(根据状态决定下一步) 为什么使用 LangGraph? 1. 代码结构更清晰 2. 内置状态管理 3. 支持复杂的条件分支 4. 内置循环和回退 如果你想学习 LangGraph: 请参考 jaspersoft 项目中的实际实现: - agent/graph.py:状态图定义 - agent/nodes.py:节点实现 - agent/state.py:状态定义 这里的 SimpleAgent 展示的是核心原理 LangGraph 只是实现方式之一 """ # ═══════════════════════════════════════════════════════════════════════════════ # 第八部分:演示代码 # ═══════════════════════════════════════════════════════════════════════════════ def demo(): """演示简单 Agent 的使用""" print("=" * 60) print("Step 03: 构建简单 Agent - 演示") print("=" * 60) # 创建 Agent agent = SimpleAgent() print("\n📋 可用工具:") for tool in agent.get_available_tools(): print(f" - {tool['name']}: {tool['description']}") # 场景 1:生成报表 print("\n\n🔄 场景 1: 生成报表") print("-" * 40) print("用户: 生成一个销售报表") response = agent.process("生成一个销售报表") print(f"Agent: {response}") # 场景 2:搜索模板 print("\n\n🔄 场景 2: 搜索模板") print("-" * 40) print("用户: 搜索采购相关的模板") response = agent.process("搜索采购相关的模板") print(f"Agent: {response}") # 场景 3:计算 print("\n\n🔄 场景 3: 数学计算") print("-" * 40) print("用户: 计算 100 + 200 * 3") response = agent.process("计算 100 + 200 * 3") print(f"Agent: {response}") # 场景 4:多轮对话 print("\n\n🔄 场景 4: 多轮对话") print("-" * 40) print("用户: 我想生成一个报表") response1 = agent.process("我想生成一个报表") print(f"Agent: {response1}") print("用户: 显示月度汇总数据") response2 = agent.process("显示月度汇总数据") print(f"Agent: {response2}") # 展示对话历史 print("\n\n📜 对话历史:") for msg in agent.get_history(): print(f" [{msg['role']}]: {msg['content'][:50]}...") # 展示工具调用记录 print("\n\n🔧 工具调用记录:") for call in agent.state.get("tool_calls", []): if "error" in call: print(f" - {call['tool']}: ❌ {call['error']}") else: result = str(call.get("result", ""))[:30] print(f" - {call['tool']}: ✓ {result}...") print("\n" + "=" * 60) print("✅ 演示完成") print("=" * 60) if __name__ == "__main__": demo()