Initial commit: jaspersoft-agent-learn teaching project
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
# Step 01: 理解 Tool - 工具系统基础
|
||||
|
||||
## 🎯 学习目标
|
||||
|
||||
- 理解什么是 Tool(工具)
|
||||
- 理解为什么 Agent 需要 Tool
|
||||
- 学会设计一个可扩展的工具系统
|
||||
- 理解 Tool 的核心要素:name、description、execute
|
||||
|
||||
---
|
||||
|
||||
## 📖 概念讲解
|
||||
|
||||
### 什么是 Tool?
|
||||
|
||||
在 AI Agent 系统中,**Tool(工具)** 是 Agent 与外部世界交互的方式。
|
||||
|
||||
```
|
||||
没有 Tool 的 LLM:
|
||||
┌──────────────┐
|
||||
│ 用户输入 │
|
||||
└──────┬───────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────┐
|
||||
│ LLM │ ← LLM 只能"想",不能"做"
|
||||
│ (只能思考) │
|
||||
└──────┬───────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────┐
|
||||
│ 返回答案 │ ← 答案可能不准确,没有执行能力
|
||||
└──────────────┘
|
||||
|
||||
有 Tool 的 Agent:
|
||||
┌──────────────┐
|
||||
│ 用户输入 │
|
||||
└──────┬───────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────┐
|
||||
│ LLM │
|
||||
│ (思考 + 决策) │
|
||||
└──────┬───────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────┐
|
||||
│ 需要执行工具? │──── 是 ──▶ 调用 Tool
|
||||
└──────┬───────┘ │
|
||||
│ ▼
|
||||
│ 否 ┌──────────┐
|
||||
▼ │ 执行 Tool │
|
||||
┌──────────────┐ │ (访问外部) │
|
||||
│ 返回答案 │ └─────┬─────┘
|
||||
└──────────────┘ │
|
||||
▼
|
||||
把执行结果反馈给 LLM
|
||||
```
|
||||
|
||||
### 为什么需要 Tool?
|
||||
|
||||
1. **LLM 不知道最新信息**:Tool 可以搜索网页、查数据库
|
||||
2. **LLM 不能执行操作**:Tool 可以写文件、调用 API
|
||||
3. **LLM 可能有幻觉**:Tool 可以验证信息
|
||||
4. **LLM 需要精确计算**:Tool 可以调用计算器
|
||||
|
||||
### Tool 的核心要素
|
||||
|
||||
每个 Tool 都有三个核心要素:
|
||||
|
||||
```python
|
||||
class Tool:
|
||||
name: str # 工具名称(LLM 通过名字选择工具)
|
||||
description: str # 工具描述(LLM 通过描述理解何时使用)
|
||||
execute: function # 执行函数(实际做事的代码)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 💻 代码实现
|
||||
|
||||
请打开 `concept.py` 查看详细代码注释。
|
||||
@@ -0,0 +1,19 @@
|
||||
# Jaspersoft Learn - AI Agent 开发教学项目
|
||||
|
||||
from .concept import (
|
||||
BaseTool,
|
||||
ToolResult,
|
||||
ToolRegistry,
|
||||
CalculatorTool,
|
||||
SearchTool,
|
||||
JaspersoftCodeGeneratorTool,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"BaseTool",
|
||||
"ToolResult",
|
||||
"ToolRegistry",
|
||||
"CalculatorTool",
|
||||
"SearchTool",
|
||||
"JaspersoftCodeGeneratorTool",
|
||||
]
|
||||
@@ -0,0 +1,592 @@
|
||||
"""
|
||||
Step 01: 理解 Tool - 工具系统基础
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
🎓 本节内容:
|
||||
1. 什么是 Tool?
|
||||
2. 如何定义一个 Tool?
|
||||
3. 如何设计可扩展的工具系统?
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
"""
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Any, Dict, Optional
|
||||
from dataclasses import dataclass
|
||||
import json
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 第一部分:理解 Tool 的基本结构
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
"""
|
||||
在开始写代码之前,我们先理解 Tool 的本质:
|
||||
|
||||
Tool = 名称 + 描述 + 执行逻辑
|
||||
|
||||
为什么这样设计?因为 LLM 选择工具时只看:
|
||||
1. 工具名称(叫什么)
|
||||
2. 工具描述(什么时候用)
|
||||
|
||||
然后 LLM 会决定:是否调用这个工具?调用时传什么参数?
|
||||
|
||||
所以一个好的 Tool 设计,必须:
|
||||
- 名称清晰、一目了然
|
||||
- 描述准确、告诉 LLM 何时使用
|
||||
- 执行逻辑独立、不影响其他工具
|
||||
"""
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 第二部分:定义 Tool 的数据结构
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
@dataclass # Python 数据类,自动生成 __init__ 等方法
|
||||
class ToolResult:
|
||||
"""
|
||||
Tool 执行结果的数据结构
|
||||
|
||||
为什么需要单独定义这个?
|
||||
因为 Tool 执行可能成功,也可能失败,我们需要统一格式来传递结果。
|
||||
|
||||
属性说明:
|
||||
success: 是否执行成功
|
||||
result: 执行结果(如果成功)
|
||||
error: 错误信息(如果失败)
|
||||
metadata: 额外元数据(如执行时间、使用的参数等)
|
||||
"""
|
||||
success: bool
|
||||
result: Any = None
|
||||
error: Optional[str] = None
|
||||
metadata: Dict[str, Any] = None
|
||||
|
||||
def __post_init__(self):
|
||||
"""初始化后确保 metadata 不为 None"""
|
||||
if self.metadata is None:
|
||||
self.metadata = {}
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 第三部分:定义抽象 Tool 基类(重要概念)
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
class BaseTool(ABC):
|
||||
"""
|
||||
所有工具的抽象基类
|
||||
|
||||
为什么需要抽象基类?
|
||||
因为我们希望所有工具都有统一的接口,这样:
|
||||
1. 任意 Tool 都可以被统一管理
|
||||
2. 新增 Tool 时不需要修改已有代码(开闭原则)
|
||||
3. 可以批量操作所有 Tool
|
||||
|
||||
抽象基类 = 定义"接口规范",子类负责"具体实现"
|
||||
"""
|
||||
|
||||
# ============================================================
|
||||
# 属性:每个 Tool 必须有的特性
|
||||
# ============================================================
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def name(self) -> str:
|
||||
"""
|
||||
工具名称
|
||||
|
||||
为什么用 @property?
|
||||
因为 name 通常是只读的,我们不希望运行时被随意修改
|
||||
|
||||
为什么用 @abstractmethod?
|
||||
因为这是一个"抽象方法",所有子类必须实现
|
||||
如果不实现,Python 会报错,强制你提供具体实现
|
||||
"""
|
||||
pass
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def description(self) -> str:
|
||||
"""
|
||||
工具描述
|
||||
|
||||
这是 LLM 理解"何时使用这个工具"的关键!
|
||||
描述越准确,LLM 越能正确选择工具。
|
||||
|
||||
好的描述应该包含:
|
||||
1. 工具能做什么
|
||||
2. 输入参数是什么
|
||||
3. 输出结果是什么
|
||||
4. 适用的场景
|
||||
"""
|
||||
pass
|
||||
|
||||
# ============================================================
|
||||
# 方法:Tool 的核心执行逻辑
|
||||
# ============================================================
|
||||
|
||||
@abstractmethod
|
||||
def execute(self, **kwargs) -> ToolResult:
|
||||
"""
|
||||
执行工具
|
||||
|
||||
参数使用 **kwargs 的原因:
|
||||
不同的 Tool 可能需要不同的参数
|
||||
用 **kwargs 可以接受任意参数,具体由子类决定
|
||||
|
||||
返回 ToolResult 而不是直接返回值的原因:
|
||||
1. 统一成功/失败的判断
|
||||
2. 可以附带错误信息
|
||||
3. 可以附带元数据(执行时间、使用的参数等)
|
||||
|
||||
为什么是抽象方法?
|
||||
因为"如何执行"是每个工具自己的事,基类不知道具体逻辑
|
||||
"""
|
||||
pass
|
||||
|
||||
# ============================================================
|
||||
# 辅助方法:让 Tool 更容易使用
|
||||
# ============================================================
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
"""
|
||||
将 Tool 转换为字典格式
|
||||
|
||||
这个方法用于:
|
||||
1. 给 LLM 展示可用的工具列表
|
||||
2. 序列化/反序列化工具配置
|
||||
|
||||
返回格式:
|
||||
{
|
||||
"name": "工具名称",
|
||||
"description": "工具描述",
|
||||
"parameters": {...} # 可选,参数schema
|
||||
}
|
||||
"""
|
||||
return {
|
||||
"name": self.name,
|
||||
"description": self.description,
|
||||
}
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""调试时显示工具信息"""
|
||||
return f"<Tool: {self.name}>"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 第四部分:实现具体的 Tool
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
class CalculatorTool(BaseTool):
|
||||
"""
|
||||
计算器工具示例
|
||||
|
||||
这个工具演示:
|
||||
1. 如何定义一个简单的 Tool
|
||||
2. 如何处理输入参数
|
||||
3. 如何返回结果
|
||||
4. 如何处理错误
|
||||
"""
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return "calculator"
|
||||
|
||||
@property
|
||||
def description(self) -> str:
|
||||
return """
|
||||
计算器工具,执行数学运算。
|
||||
|
||||
输入:
|
||||
expression: str - 数学表达式,如 "2 + 3" 或 "10 * 5"
|
||||
|
||||
输出:
|
||||
计算结果(数字)
|
||||
|
||||
示例:
|
||||
expression="2 + 3" -> 5
|
||||
expression="10 / 2" -> 5.0
|
||||
expression="2 ** 3" -> 8
|
||||
"""
|
||||
|
||||
def execute(self, **kwargs) -> ToolResult:
|
||||
"""
|
||||
执行计算
|
||||
|
||||
为什么用 try-except?
|
||||
因为 eval() 可能执行恶意代码或语法错误
|
||||
我们需要捕获异常,返回友好的错误信息
|
||||
"""
|
||||
expression = kwargs.get("expression")
|
||||
|
||||
# 参数验证
|
||||
if expression is None:
|
||||
return ToolResult(
|
||||
success=False,
|
||||
error="缺少必需参数 'expression'"
|
||||
)
|
||||
|
||||
if not isinstance(expression, str):
|
||||
return ToolResult(
|
||||
success=False,
|
||||
error=f"参数 'expression' 应该是字符串,实际是 {type(expression)}"
|
||||
)
|
||||
|
||||
try:
|
||||
# 使用 eval 计算表达式
|
||||
# 注意:生产环境中应该用更安全的计算方式
|
||||
# 这里用 eval 是为了简化,实际推荐用 ast.literal_eval 或专用计算库
|
||||
result = eval(expression, {"__builtins__": {}}, {})
|
||||
|
||||
return ToolResult(
|
||||
success=True,
|
||||
result=result,
|
||||
metadata={"expression": expression}
|
||||
)
|
||||
|
||||
except SyntaxError as e:
|
||||
return ToolResult(
|
||||
success=False,
|
||||
error=f"表达式语法错误: {e}"
|
||||
)
|
||||
|
||||
except ZeroDivisionError:
|
||||
return ToolResult(
|
||||
success=False,
|
||||
error="除数不能为零"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return ToolResult(
|
||||
success=False,
|
||||
error=f"计算错误: {e}"
|
||||
)
|
||||
|
||||
|
||||
class SearchTool(BaseTool):
|
||||
"""
|
||||
搜索工具示例
|
||||
|
||||
这个工具演示:
|
||||
1. 如何模拟一个搜索功能
|
||||
2. 如何返回结构化数据
|
||||
3. 如何设计工具的"真实感"
|
||||
|
||||
实际应用中,这里会调用真实的搜索 API
|
||||
"""
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return "web_search"
|
||||
|
||||
@property
|
||||
def description(self) -> str:
|
||||
return """
|
||||
网络搜索工具,在互联网上搜索信息。
|
||||
|
||||
输入:
|
||||
query: str - 搜索关键词
|
||||
limit: int - 返回结果数量(默认5条)
|
||||
|
||||
输出:
|
||||
搜索结果列表,每条包含标题、链接、摘要
|
||||
|
||||
适用场景:
|
||||
- 查找最新资讯
|
||||
- 搜索技术文档
|
||||
- 查找某个问题的答案
|
||||
"""
|
||||
|
||||
def execute(self, **kwargs) -> ToolResult:
|
||||
"""
|
||||
执行搜索
|
||||
|
||||
注意:这里用模拟数据演示
|
||||
真实场景中,你需要调用 Bing/Google API
|
||||
"""
|
||||
query = kwargs.get("query", "")
|
||||
limit = kwargs.get("limit", 5)
|
||||
|
||||
if not query:
|
||||
return ToolResult(
|
||||
success=False,
|
||||
error="搜索关键词不能为空"
|
||||
)
|
||||
|
||||
# 模拟搜索结果
|
||||
# 真实场景:这里调用 search_api(query)
|
||||
mock_results = [
|
||||
{
|
||||
"title": f"关于 '{query}' 的官方文档",
|
||||
"url": "https://example.com/doc",
|
||||
"snippet": f"这是关于 {query} 的详细官方文档..."
|
||||
},
|
||||
{
|
||||
"title": f"{query} 入门教程",
|
||||
"url": "https://example.com/tutorial",
|
||||
"snippet": f"学习 {query} 的快速入门指南..."
|
||||
},
|
||||
]
|
||||
|
||||
return ToolResult(
|
||||
success=True,
|
||||
result=mock_results[:limit],
|
||||
metadata={"query": query, "count": len(mock_results[:limit])}
|
||||
)
|
||||
|
||||
|
||||
class JaspersoftCodeGeneratorTool(BaseTool):
|
||||
"""
|
||||
Jaspersoft JRXML 代码生成工具(对应你的实际项目)
|
||||
|
||||
这个工具演示:
|
||||
1. 如何设计业务相关的 Tool
|
||||
2. 如何处理复杂输入
|
||||
3. 如何返回有意义的结果
|
||||
|
||||
这个工具的作用:
|
||||
用户说"生成一个销售报表"
|
||||
-> Tool 调用 LLM 生成 JRXML 代码
|
||||
-> 返回生成的代码
|
||||
"""
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return "jrxml_generator"
|
||||
|
||||
@property
|
||||
def description(self) -> str:
|
||||
return """
|
||||
JasperReports JRXML 报表生成工具。
|
||||
|
||||
根据用户的自然语言需求,生成 JRXML 报表模板代码。
|
||||
|
||||
输入:
|
||||
requirement: str - 用户的需求描述
|
||||
context: str - 额外的上下文信息(如参考模板、数据源信息等)
|
||||
|
||||
输出:
|
||||
生成的 JRXML 代码(字符串)
|
||||
|
||||
适用场景:
|
||||
- 从零生成新报表
|
||||
- 基于现有模板修改
|
||||
- 生成特定格式的报表
|
||||
|
||||
注意:
|
||||
生成的代码需要通过 jrxml_validator 验证后才能使用
|
||||
"""
|
||||
|
||||
def execute(self, **kwargs) -> ToolResult:
|
||||
"""
|
||||
执行 JRXML 生成
|
||||
|
||||
参数处理说明:
|
||||
- 使用 kwargs.get() 安全获取参数,避免 KeyError
|
||||
- 提供默认值作为后备
|
||||
- 类型检查确保参数正确
|
||||
"""
|
||||
requirement = kwargs.get("requirement", "")
|
||||
context = kwargs.get("context", "")
|
||||
|
||||
if not requirement:
|
||||
return ToolResult(
|
||||
success=False,
|
||||
error="需求描述不能为空"
|
||||
)
|
||||
|
||||
# 实际场景中,这里会:
|
||||
# 1. 构建 Prompt
|
||||
# 2. 调用 LLM
|
||||
# 3. 提取生成的 JRXML 代码
|
||||
#
|
||||
# 示例代码:
|
||||
# prompt = build_generation_prompt(requirement, context)
|
||||
# response = llm.invoke(prompt)
|
||||
# jrxml = extract_jrxml(response)
|
||||
|
||||
# 模拟生成结果
|
||||
mock_jrxml = f'''<?xml version="1.0" encoding="UTF-8"?>
|
||||
<jasperReport xmlns="http://jasperreports.sourceforge.net/jasperreports"
|
||||
name="GeneratedReport" pageWidth="595" pageHeight="842">
|
||||
<queryString>
|
||||
<![CDATA[SELECT * FROM sales WHERE date > '2024-01-01']]>
|
||||
</queryString>
|
||||
<field name="product_name" class="java.lang.String"/>
|
||||
<field name="quantity" class="java.lang.Integer"/>
|
||||
<field name="price" class="java.math.BigDecimal"/>
|
||||
<band height="50">
|
||||
<staticText>
|
||||
<reportElement x="0" y="0" width="100" height="20"/>
|
||||
<text>Product</text>
|
||||
</staticText>
|
||||
<textField>
|
||||
<reportElement x="0" y="20" width="100" height="20"/>
|
||||
<textFieldExpression>$F{{product_name}}</textFieldExpression>
|
||||
</textField>
|
||||
</band>
|
||||
</jasperReport>'''
|
||||
|
||||
return ToolResult(
|
||||
success=True,
|
||||
result=mock_jrxml,
|
||||
metadata={
|
||||
"requirement": requirement,
|
||||
"context_provided": bool(context),
|
||||
"note": "这是模拟结果,实际需要调用 LLM"
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 第五部分:工具管理系统
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
class ToolRegistry:
|
||||
"""
|
||||
工具注册表
|
||||
|
||||
这个类负责:
|
||||
1. 注册所有可用的 Tool
|
||||
2. 根据名称查找 Tool
|
||||
3. 列出所有可用 Tool(供 LLM 了解能力)
|
||||
|
||||
为什么需要这个?
|
||||
因为一个 Agent 可能有很多 Tool
|
||||
需要一个统一的地方来管理它们
|
||||
|
||||
设计模式:注册表模式(Registry Pattern)
|
||||
核心思想:用一个中心来管理所有实例
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""初始化空的工具注册表"""
|
||||
self._tools: Dict[str, BaseTool] = {}
|
||||
|
||||
def register(self, tool: BaseTool) -> None:
|
||||
"""
|
||||
注册一个工具
|
||||
|
||||
为什么用 tool.name 作为 key?
|
||||
因为 name 是工具的唯一标识符
|
||||
同一个 name 的工具只能注册一次
|
||||
|
||||
实际应用:
|
||||
registry = ToolRegistry()
|
||||
registry.register(CalculatorTool())
|
||||
registry.register(SearchTool())
|
||||
"""
|
||||
if tool.name in self._tools:
|
||||
raise ValueError(f"工具 '{tool.name}' 已经注册过了")
|
||||
|
||||
self._tools[tool.name] = tool
|
||||
|
||||
def get(self, name: str) -> Optional[BaseTool]:
|
||||
"""
|
||||
根据名称获取工具
|
||||
|
||||
返回 Optional[BaseTool]:
|
||||
如果找到,返回工具实例
|
||||
如果没找到,返回 None
|
||||
"""
|
||||
return self._tools.get(name)
|
||||
|
||||
def list_tools(self) -> list[dict]:
|
||||
"""
|
||||
列出所有已注册的工具
|
||||
|
||||
返回格式:
|
||||
[
|
||||
{"name": "calculator", "description": "..."},
|
||||
{"name": "web_search", "description": "..."},
|
||||
]
|
||||
|
||||
这个格式是为了方便给 LLM 展示可用工具列表
|
||||
"""
|
||||
return [tool.to_dict() for tool in self._tools.values()]
|
||||
|
||||
def execute(self, tool_name: str, **kwargs) -> ToolResult:
|
||||
"""
|
||||
执行指定名称的工具
|
||||
|
||||
这是对用户暴露的统一接口
|
||||
用户不需要知道具体哪个工具,只要说"执行这个"
|
||||
"""
|
||||
tool = self.get(tool_name)
|
||||
if tool is None:
|
||||
return ToolResult(
|
||||
success=False,
|
||||
error=f"工具 '{tool_name}' 不存在"
|
||||
)
|
||||
|
||||
return tool.execute(**kwargs)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 第六部分:演示代码
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
def demo():
|
||||
"""
|
||||
演示如何使用工具系统
|
||||
|
||||
这个函数展示:
|
||||
1. 创建工具注册表
|
||||
2. 注册工具
|
||||
3. 执行工具
|
||||
4. 查看可用工具
|
||||
"""
|
||||
print("=" * 60)
|
||||
print("Step 01: 理解 Tool - 工具系统演示")
|
||||
print("=" * 60)
|
||||
|
||||
# 1. 创建工具注册表
|
||||
registry = ToolRegistry()
|
||||
|
||||
# 2. 注册工具
|
||||
registry.register(CalculatorTool())
|
||||
registry.register(SearchTool())
|
||||
registry.register(JaspersoftCodeGeneratorTool())
|
||||
|
||||
print("\n📋 已注册的工具列表:")
|
||||
for tool_info in registry.list_tools():
|
||||
print(f" - {tool_info['name']}: {tool_info['description'][:50]}...")
|
||||
|
||||
# 3. 执行计算器工具
|
||||
print("\n🔧 执行计算器工具:")
|
||||
result = registry.execute("calculator", expression="2 + 3 * 4")
|
||||
if result.success:
|
||||
print(f" 结果: {result.result}")
|
||||
else:
|
||||
print(f" 错误: {result.error}")
|
||||
|
||||
# 4. 执行搜索工具
|
||||
print("\n🔍 执行搜索工具:")
|
||||
result = registry.execute("web_search", query="JasperReports 教程", limit=2)
|
||||
if result.success:
|
||||
for item in result.result:
|
||||
print(f" - {item['title']}")
|
||||
else:
|
||||
print(f" 错误: {result.error}")
|
||||
|
||||
# 5. 执行 JRXML 生成工具
|
||||
print("\n📄 执行 JRXML 生成工具:")
|
||||
result = registry.execute(
|
||||
"jrxml_generator",
|
||||
requirement="生成一个销售报表,显示月度汇总"
|
||||
)
|
||||
if result.success:
|
||||
print(f" 生成成功! (长度: {len(result.result)} 字符)")
|
||||
print(f" 前100字符: {result.result[:100]}...")
|
||||
else:
|
||||
print(f" 错误: {result.error}")
|
||||
|
||||
# 6. 演示错误处理
|
||||
print("\n⚠️ 演示错误处理:")
|
||||
result = registry.execute("calculator", expression="1 / 0")
|
||||
if not result.success:
|
||||
print(f" 预期错误: {result.error}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
demo()
|
||||
@@ -0,0 +1,223 @@
|
||||
"""
|
||||
Step 01 练习题:设计你的第一个 Tool
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
🎯 练习目标:
|
||||
1. 巩固 Tool 的基本结构
|
||||
2. 设计一个业务相关的 Tool
|
||||
3. 理解 Tool 注册系统
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
"""
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 练习 1:完善 TextProcessorTool
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
"""
|
||||
任务:
|
||||
完善 TextProcessorTool,这个工具用于处理文本。
|
||||
|
||||
要求:
|
||||
1. 实现 word_count 方法:统计单词数量
|
||||
2. 实现 character_count 方法:统计字符数量(不包括空格)
|
||||
3. 实现 sentence_count 方法:统计句子数量(按句号、问号、感叹号分割)
|
||||
|
||||
提示:
|
||||
- text 可能包含多行
|
||||
- 句子分割要考虑常见的句子结束符:. ! ?
|
||||
- 单词分割可以考虑按空格分割
|
||||
|
||||
完成后测试:
|
||||
text = "Hello, world! This is a test. How are you?"
|
||||
预期结果:
|
||||
- word_count: 9
|
||||
- character_count: 38 (不包括空格)
|
||||
- sentence_count: 3
|
||||
"""
|
||||
|
||||
from step_01_tools.concept import BaseTool, ToolResult
|
||||
|
||||
|
||||
class TextProcessorTool(BaseTool):
|
||||
"""文本处理工具"""
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return "text_processor"
|
||||
|
||||
@property
|
||||
def description(self) -> str:
|
||||
return "文本处理工具,统计文本的各种特征"
|
||||
|
||||
def execute(self, **kwargs) -> ToolResult:
|
||||
"""处理文本请求"""
|
||||
text = kwargs.get("text", "")
|
||||
operation = kwargs.get("operation", "word_count") # 默认操作
|
||||
|
||||
if not text:
|
||||
return ToolResult(success=False, error="文本不能为空")
|
||||
|
||||
if operation == "word_count":
|
||||
# TODO: 实现单词统计
|
||||
result = self.word_count(text)
|
||||
elif operation == "character_count":
|
||||
# TODO: 实现字符统计
|
||||
result = self.character_count(text)
|
||||
elif operation == "sentence_count":
|
||||
# TODO: 实现句子统计
|
||||
result = self.sentence_count(text)
|
||||
else:
|
||||
return ToolResult(success=False, error=f"不支持的操作: {operation}")
|
||||
|
||||
return ToolResult(success=True, result=result)
|
||||
|
||||
def word_count(self, text: str) -> int:
|
||||
"""统计单词数量"""
|
||||
# 提示:按空格分割,过滤空字符串
|
||||
# 你的代码:
|
||||
pass
|
||||
|
||||
def character_count(self, text: str) -> int:
|
||||
"""统计字符数量(不包括空格)"""
|
||||
# 提示:去除所有空白字符后统计长度
|
||||
# 你的代码:
|
||||
pass
|
||||
|
||||
def sentence_count(self, text: str) -> int:
|
||||
"""统计句子数量"""
|
||||
# 提示:按 . ! ? 分割
|
||||
# 你的代码:
|
||||
pass
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 练习 2:设计一个 EmailTool
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
"""
|
||||
任务:
|
||||
设计一个 EmailTool,用于处理邮件相关操作。
|
||||
|
||||
功能要求:
|
||||
1. send_email: 发送邮件(收件人、主题、正文)
|
||||
2. search_emails: 搜索邮件(关键词)
|
||||
3. get_unread_count: 获取未读邮件数量
|
||||
|
||||
设计提示:
|
||||
- 工具名称要简洁明了
|
||||
- 描述要告诉 LLM 这个工具能做什么
|
||||
- execute 方法要处理不同的 operation
|
||||
|
||||
这个练习的目的是让你学会:
|
||||
- 如何设计多功能的 Tool
|
||||
- 如何用 operation 参数区分不同功能
|
||||
- 如何返回结构化的结果
|
||||
"""
|
||||
|
||||
|
||||
class EmailTool(BaseTool):
|
||||
"""邮件处理工具"""
|
||||
|
||||
# TODO: 实现属性和方法
|
||||
pass
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 练习 3:改进工具注册表
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
"""
|
||||
任务:
|
||||
给 ToolRegistry 添加一个新功能:根据描述关键词搜索工具
|
||||
|
||||
方法签名:
|
||||
def search_tools(self, keyword: str) -> list[dict]:
|
||||
'''
|
||||
根据关键词搜索工具
|
||||
|
||||
参数:
|
||||
keyword: str - 搜索关键词
|
||||
|
||||
返回:
|
||||
匹配的工具列表,格式同 list_tools()
|
||||
|
||||
匹配规则:
|
||||
如果关键词出现在工具名称或描述中,就算匹配
|
||||
匹配应该不区分大小写
|
||||
'''
|
||||
|
||||
预期行为:
|
||||
registry = ToolRegistry()
|
||||
registry.register(CalculatorTool())
|
||||
registry.register(SearchTool())
|
||||
registry.register(TextProcessorTool()) # 用你刚完成的
|
||||
|
||||
results = registry.search_tools("text")
|
||||
# 应该返回 text_processor
|
||||
|
||||
results = registry.search_tools("calculate")
|
||||
# 应该返回 calculator
|
||||
|
||||
results = registry.search_tools("web")
|
||||
# 应该返回 web_search
|
||||
"""
|
||||
|
||||
# 从 step_01_tools.concept 导入已有的类
|
||||
from step_01_tools.concept import ToolRegistry, CalculatorTool, SearchTool
|
||||
|
||||
|
||||
def add_search_to_registry():
|
||||
"""
|
||||
在这里实现 search_tools 方法
|
||||
|
||||
提示:
|
||||
1. 遍历所有已注册的工具
|
||||
2. 检查 name 或 description 中是否包含 keyword
|
||||
3. 收集匹配的工具
|
||||
4. 返回列表
|
||||
|
||||
完成后,在 main.py 中测试
|
||||
"""
|
||||
# 你的代码:
|
||||
|
||||
pass
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 运行测试
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
def test_exercises():
|
||||
"""测试所有练习"""
|
||||
print("\n" + "=" * 60)
|
||||
print("测试练习答案")
|
||||
print("=" * 60)
|
||||
|
||||
# 测试练习 1
|
||||
print("\n📝 练习 1: TextProcessorTool")
|
||||
tool = TextProcessorTool()
|
||||
test_text = "Hello, world! This is a test. How are you?"
|
||||
|
||||
print(f"测试文本: {test_text}")
|
||||
print(f"单词数量: {tool.word_count(test_text)}") # 应该是 9
|
||||
print(f"字符数量: {tool.character_count(test_text)}") # 应该是 38
|
||||
print(f"句子数量: {tool.sentence_count(test_text)}") # 应该是 3
|
||||
|
||||
# 测试练习 2
|
||||
print("\n📝 练习 2: EmailTool")
|
||||
# 运行看看你完成了多少
|
||||
|
||||
# 测试练习 3
|
||||
print("\n📝 练习 3: search_tools")
|
||||
registry = ToolRegistry()
|
||||
registry.register(CalculatorTool())
|
||||
registry.register(SearchTool())
|
||||
registry.register(tool)
|
||||
|
||||
# 你的测试代码:
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_exercises()
|
||||
@@ -0,0 +1,264 @@
|
||||
"""
|
||||
Step 01 练习题答案
|
||||
|
||||
⚠️ 先自己思考,再看答案!
|
||||
⚠️ 答案不是唯一的,这里只是其中一种实现
|
||||
"""
|
||||
|
||||
from step_01_tools.concept import BaseTool, ToolResult
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 练习 1 答案:TextProcessorTool
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
class TextProcessorTool(BaseTool):
|
||||
"""文本处理工具"""
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return "text_processor"
|
||||
|
||||
@property
|
||||
def description(self) -> str:
|
||||
return "文本处理工具,统计文本的各种特征"
|
||||
|
||||
def execute(self, **kwargs) -> ToolResult:
|
||||
"""处理文本请求"""
|
||||
text = kwargs.get("text", "")
|
||||
operation = kwargs.get("operation", "word_count")
|
||||
|
||||
if not text:
|
||||
return ToolResult(success=False, error="文本不能为空")
|
||||
|
||||
if operation == "word_count":
|
||||
result = self.word_count(text)
|
||||
elif operation == "character_count":
|
||||
result = self.character_count(text)
|
||||
elif operation == "sentence_count":
|
||||
result = self.sentence_count(text)
|
||||
else:
|
||||
return ToolResult(success=False, error=f"不支持的操作: {operation}")
|
||||
|
||||
return ToolResult(success=True, result=result)
|
||||
|
||||
def word_count(self, text: str) -> int:
|
||||
"""统计单词数量"""
|
||||
# 按空格分割,过滤空字符串
|
||||
words = [w for w in text.split() if w.strip()]
|
||||
return len(words)
|
||||
|
||||
def character_count(self, text: str) -> int:
|
||||
"""统计字符数量(不包括空格)"""
|
||||
# 去除所有空白字符后统计
|
||||
return len(text.replace(" ", "").replace("\n", "").replace("\t", ""))
|
||||
|
||||
def sentence_count(self, text: str) -> int:
|
||||
"""统计句子数量"""
|
||||
# 按常见句子结束符分割
|
||||
import re
|
||||
sentences = re.split(r'[.!?]+', text)
|
||||
# 过滤空字符串
|
||||
sentences = [s for s in sentences if s.strip()]
|
||||
return len(sentences)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 练习 2 答案:EmailTool
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
class EmailTool(BaseTool):
|
||||
"""邮件处理工具"""
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return "email"
|
||||
|
||||
@property
|
||||
def description(self) -> str:
|
||||
return """
|
||||
邮件处理工具,用于发送和搜索邮件。
|
||||
|
||||
支持的操作:
|
||||
send_email: 发送邮件
|
||||
- to: 收件人邮箱
|
||||
- subject: 邮件主题
|
||||
- body: 邮件正文
|
||||
返回: {"success": true, "message_id": "xxx"}
|
||||
|
||||
search_emails: 搜索邮件
|
||||
- keyword: 搜索关键词
|
||||
- limit: 返回数量(默认10)
|
||||
返回: [{"from": "...", "subject": "...", "date": "..."}, ...]
|
||||
|
||||
get_unread_count: 获取未读邮件数量
|
||||
无需参数
|
||||
返回: {"count": 5}
|
||||
"""
|
||||
|
||||
def execute(self, **kwargs) -> ToolResult:
|
||||
"""执行邮件操作"""
|
||||
operation = kwargs.get("operation", "get_unread_count")
|
||||
|
||||
if operation == "send_email":
|
||||
return self.send_email(
|
||||
to=kwargs.get("to", ""),
|
||||
subject=kwargs.get("subject", ""),
|
||||
body=kwargs.get("body", "")
|
||||
)
|
||||
elif operation == "search_emails":
|
||||
return self.search_emails(
|
||||
keyword=kwargs.get("keyword", ""),
|
||||
limit=kwargs.get("limit", 10)
|
||||
)
|
||||
elif operation == "get_unread_count":
|
||||
return self.get_unread_count()
|
||||
else:
|
||||
return ToolResult(success=False, error=f"不支持的操作: {operation}")
|
||||
|
||||
def send_email(self, to: str, subject: str, body: str) -> ToolResult:
|
||||
"""发送邮件"""
|
||||
if not to:
|
||||
return ToolResult(success=False, error="收件人不能为空")
|
||||
if not subject:
|
||||
return ToolResult(success=False, error="主题不能为空")
|
||||
|
||||
# 实际场景:调用邮件发送 API
|
||||
# 这里用模拟数据
|
||||
message_id = f"msg_{hash(to + subject) % 100000}"
|
||||
|
||||
return ToolResult(
|
||||
success=True,
|
||||
result={
|
||||
"success": True,
|
||||
"message_id": message_id,
|
||||
"to": to,
|
||||
"subject": subject
|
||||
}
|
||||
)
|
||||
|
||||
def search_emails(self, keyword: str, limit: int) -> ToolResult:
|
||||
"""搜索邮件"""
|
||||
if not keyword:
|
||||
return ToolResult(success=False, error="搜索关键词不能为空")
|
||||
|
||||
# 模拟搜索结果
|
||||
mock_results = [
|
||||
{
|
||||
"from": "boss@company.com",
|
||||
"subject": f"关于项目的{keyword}",
|
||||
"date": "2024-01-15",
|
||||
"snippet": "..."
|
||||
},
|
||||
{
|
||||
"from": "colleague@company.com",
|
||||
"subject": f"回复: {keyword}的讨论",
|
||||
"date": "2024-01-14",
|
||||
"snippet": "..."
|
||||
},
|
||||
]
|
||||
|
||||
return ToolResult(
|
||||
success=True,
|
||||
result=mock_results[:limit]
|
||||
)
|
||||
|
||||
def get_unread_count(self) -> ToolResult:
|
||||
"""获取未读邮件数量"""
|
||||
# 实际场景:调用邮件 API 获取未读数
|
||||
return ToolResult(success=True, result={"count": 5})
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 练习 3 答案:给 ToolRegistry 添加 search_tools
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
from step_01_tools.concept import ToolRegistry, CalculatorTool, SearchTool
|
||||
|
||||
|
||||
def add_search_to_registry():
|
||||
"""
|
||||
演示如何给 ToolRegistry 添加 search_tools 方法
|
||||
"""
|
||||
|
||||
# 方法1:在原类上添加(直接修改原类)
|
||||
def search_tools_original(self, keyword: str) -> list[dict]:
|
||||
"""根据关键词搜索工具"""
|
||||
keyword_lower = keyword.lower()
|
||||
results = []
|
||||
|
||||
for tool in self._tools.values():
|
||||
# 检查名称或描述中是否包含关键词
|
||||
name_match = keyword_lower in tool.name.lower()
|
||||
desc_match = keyword_lower in tool.description.lower()
|
||||
|
||||
if name_match or desc_match:
|
||||
results.append(tool.to_dict())
|
||||
|
||||
return results
|
||||
|
||||
# 给 ToolRegistry 添加方法
|
||||
ToolRegistry.search_tools = search_tools_original
|
||||
|
||||
# 测试
|
||||
registry = ToolRegistry()
|
||||
registry.register(CalculatorTool())
|
||||
registry.register(SearchTool())
|
||||
registry.register(TextProcessorTool())
|
||||
|
||||
print("搜索 'text':")
|
||||
for t in registry.search_tools("text"):
|
||||
print(f" - {t['name']}")
|
||||
|
||||
print("搜索 'calculate':")
|
||||
for t in registry.search_tools("calculate"):
|
||||
print(f" - {t['name']}")
|
||||
|
||||
print("搜索 'web':")
|
||||
for t in registry.search_tools("web"):
|
||||
print(f" - {t['name']}")
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 测试运行
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
def test_answers():
|
||||
"""测试答案"""
|
||||
print("\n" + "=" * 60)
|
||||
print("测试练习答案")
|
||||
print("=" * 60)
|
||||
|
||||
# 测试练习 1
|
||||
print("\n📝 练习 1: TextProcessorTool")
|
||||
tool = TextProcessorTool()
|
||||
test_text = "Hello, world! This is a test. How are you?"
|
||||
|
||||
print(f"测试文本: {test_text}")
|
||||
print(f"单词数量: {tool.word_count(test_text)}") # 应该是 9
|
||||
print(f"字符数量: {tool.character_count(test_text)}") # 应该是 38
|
||||
print(f"句子数量: {tool.sentence_count(test_text)}") # 应该是 3
|
||||
|
||||
# 测试练习 2
|
||||
print("\n📝 练习 2: EmailTool")
|
||||
email_tool = EmailTool()
|
||||
|
||||
print("\n 发送邮件:")
|
||||
result = email_tool.send_email("test@example.com", "测试", "这是一封测试邮件")
|
||||
print(f" 结果: {result.result}")
|
||||
|
||||
print("\n 搜索邮件:")
|
||||
result = email_tool.search_emails("项目", limit=5)
|
||||
print(f" 结果: {result.result}")
|
||||
|
||||
print("\n 未读邮件数:")
|
||||
result = email_tool.get_unread_count()
|
||||
print(f" 结果: {result.result}")
|
||||
|
||||
# 测试练习 3
|
||||
print("\n📝 练习 3: search_tools")
|
||||
add_search_to_registry()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_answers()
|
||||
@@ -0,0 +1,169 @@
|
||||
"""
|
||||
Step 01: 工具系统基础 - 主程序
|
||||
|
||||
运行方式:
|
||||
cd step_01_tools
|
||||
python main.py
|
||||
"""
|
||||
|
||||
from concept import (
|
||||
ToolRegistry,
|
||||
CalculatorTool,
|
||||
SearchTool,
|
||||
JaspersoftCodeGeneratorTool
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
"""演示工具系统的完整使用流程"""
|
||||
|
||||
print("=" * 70)
|
||||
print(" Step 01: 理解 Tool - 工具系统基础")
|
||||
print("=" * 70)
|
||||
print()
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
# 场景:构建一个 Jaspersoft 报表助手的工具集
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
print("📦 场景:构建 Jaspersoft 报表助手")
|
||||
print("-" * 70)
|
||||
print("""
|
||||
假设我们要构建一个 AI 助手,帮助用户:
|
||||
1. 根据需求生成 JRXML 代码
|
||||
2. 验证生成的代码是否正确
|
||||
3. 计算报表的统计数据
|
||||
4. 搜索相关的报表模板
|
||||
|
||||
我们可以设计以下工具:
|
||||
- jrxml_generator: 生成 JRXML 代码
|
||||
- jrxml_validator: 验证 JRXML 语法
|
||||
- calculator: 计算统计数据
|
||||
- web_search: 搜索报表模板
|
||||
""")
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
# 步骤 1:创建工具注册表
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
print("\n📋 步骤 1: 创建工具注册表")
|
||||
print("-" * 40)
|
||||
|
||||
registry = ToolRegistry()
|
||||
print("✓ 创建了空的工具注册表")
|
||||
print(f" 当前注册工具数量: {len(registry.list_tools())}")
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
# 步骤 2:注册工具
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
print("\n🔧 步骤 2: 注册工具")
|
||||
print("-" * 40)
|
||||
|
||||
# 注册各种工具
|
||||
registry.register(CalculatorTool())
|
||||
print("✓ 注册计算器工具")
|
||||
|
||||
registry.register(SearchTool())
|
||||
print("✓ 注册搜索工具")
|
||||
|
||||
registry.register(JaspersoftCodeGeneratorTool())
|
||||
print("✓ 注册 JRXML 生成工具")
|
||||
|
||||
print(f"\n 当前注册工具数量: {len(registry.list_tools())}")
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
# 步骤 3:查看可用工具
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
print("\n📜 步骤 3: 查看可用工具")
|
||||
print("-" * 40)
|
||||
|
||||
for tool_info in registry.list_tools():
|
||||
print(f"\n [{tool_info['name']}]")
|
||||
# 截取描述的前100个字符
|
||||
desc = tool_info['description'].strip().replace('\n', ' ')[:100]
|
||||
print(f" {desc}...")
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
# 步骤 4:执行工具
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
print("\n\n⚡ 步骤 4: 执行工具")
|
||||
print("-" * 40)
|
||||
|
||||
# 场景:用户想要生成一个销售报表
|
||||
user_requirement = "生成一个销售报表,显示月度汇总"
|
||||
|
||||
print(f"\n 用户需求: {user_requirement}")
|
||||
|
||||
# 4.1 先搜索相关的模板
|
||||
print("\n 4.1 搜索相关模板...")
|
||||
search_result = registry.execute("web_search", query="JasperReports 销售报表模板")
|
||||
if search_result.success:
|
||||
print(f" 找到 {len(search_result.result)} 个相关模板")
|
||||
for i, item in enumerate(search_result.result[:2], 1):
|
||||
print(f" {i}. {item['title']}")
|
||||
|
||||
# 4.2 生成 JRXML
|
||||
print("\n 4.2 生成 JRXML 代码...")
|
||||
generate_result = registry.execute(
|
||||
"jrxml_generator",
|
||||
requirement=user_requirement,
|
||||
context="参考销售报表模板"
|
||||
)
|
||||
if generate_result.success:
|
||||
print(f" ✓ 生成成功!")
|
||||
print(f" 代码长度: {len(generate_result.result)} 字符")
|
||||
print(f" 代码预览:")
|
||||
print(" " + "-" * 30)
|
||||
for line in generate_result.result.split('\n')[:5]:
|
||||
print(f" {line}")
|
||||
print(" ...")
|
||||
|
||||
# 4.3 计算统计
|
||||
print("\n 4.3 计算统计...")
|
||||
calc_result = registry.execute("calculator", expression="10 + 20 + 30")
|
||||
if calc_result.success:
|
||||
print(f" 10 + 20 + 30 = {calc_result.result}")
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
# 步骤 5:错误处理
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
print("\n\n⚠️ 步骤 5: 错误处理演示")
|
||||
print("-" * 40)
|
||||
|
||||
# 场景:用户尝试除以零
|
||||
print("\n 尝试执行: calculator(expression='10 / 0')")
|
||||
error_result = registry.execute("calculator", expression="10 / 0")
|
||||
if not error_result.success:
|
||||
print(f" ✓ 错误被正确捕获: {error_result.error}")
|
||||
|
||||
# 尝试调用不存在的工具
|
||||
print("\n 尝试执行: nonexistent_tool()")
|
||||
error_result = registry.execute("nonexistent_tool")
|
||||
if not error_result.success:
|
||||
print(f" ✓ 错误被正确捕获: {error_result.error}")
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
# 总结
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
print("\n\n" + "=" * 70)
|
||||
print(" ✅ Step 01 完成!")
|
||||
print("=" * 70)
|
||||
print("""
|
||||
学到的关键概念:
|
||||
1. Tool = name + description + execute
|
||||
2. BaseTool 是所有工具的基类
|
||||
3. ToolRegistry 统一管理所有工具
|
||||
4. ToolResult 统一返回结果格式
|
||||
|
||||
下一步:
|
||||
继续 Step 02,学习如何管理 Agent 的状态
|
||||
""")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user