Files
jaspersoft-agent-learn/step_03_simple_agent/concept.py
T

600 lines
20 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
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 接口
# ═══════════════════════════════════════════════════════════════════════════════
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'''<?xml version="1.0" encoding="UTF-8"?>
<jasperReport xmlns="http://jasperreports.sourceforge.net/jasperreports"
name="{requirement}" pageWidth="595" pageHeight="842">
<title>{requirement}</title>
<queryString>
<![CDATA[SELECT * FROM data WHERE 1=1]]>
</queryString>
<field name="id" class="java.lang.Integer"/>
<field name="name" class="java.lang.String"/>
<band height="50">
<staticText>
<reportElement x="0" y="0" width="100" height="20"/>
<text>名称</text>
</staticText>
<textField>
<reportElement x="0" y="20" width="100" height="20"/>
<textFieldExpression>$F{{name}}</textFieldExpression>
</textField>
</band>
</jasperReport>'''
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 "<?xml" not in jrxml:
issues.append("缺少 XML 声明")
if "<jasperReport" not in jrxml:
issues.append("缺少 <jasperReport> 根元素")
if "<field" not in jrxml and "<band" not in jrxml:
issues.append("报表中没有定义字段或波段")
return {
"valid": len(issues) == 0,
"issues": issues
}
# ═══════════════════════════════════════════════════════════════════════════════
# 第五部分:构建 Agent 的大脑(LLM 决策)
# ═══════════════════════════════════════════════════════════════════════════════
class AgentBrain:
"""
Agent 的大脑——负责决定下一步做什么
在真实系统中,这里会调用 LLM
这里用规则引擎模拟 LLM 的决策过程
决策逻辑:
1. 如果用户要求生成报表 → 调用 jrxml_generator
2. 如果用户要求搜索模板 → 调用 template_search
3. 如果用户要求验证 → 调用 jrxml_validator
4. 如果用户只是聊天 → 直接回答
5. 如果工具执行完成 → 返回结果或继续调用
"""
def __init__(self):
# 工具描述(用于告诉 LLM 有哪些工具可用)
self.tools = {
"calculator": "计算器,执行数学表达式,如 2+3*4",
"jrxml_generator": "生成 JasperReports JRXML 报表模板",
"template_search": "搜索报表模板",
"jrxml_validator": "验证 JRXML 报表的正确性",
}
def decide(self, state: SimpleAgentState) -> 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"
)
# 对话轮次计数器
self.round_count = 0
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"
self.state["tool_result"] = None
self.round_count += 1
# 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()