# 2. JSON 对象返回

> SimpleLLMFunc 的工具系统为大语言模型提供了调用外部函数和 API 的能力，让 LLM 能够执行计算、查询数据、调用服务等操作。工具系统支持两种创建方式，并能自动将 Python 函数转换为 LLM 可理解的工具描述格式。

- Skill: `tools-only/2-json` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add tools-only/2-json`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tools-only/2-json/raw
- Safety review: pending (external: skill-scanner WARNING, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: tools-only (https://skillmd.com/u/tools-only)
- Updated: 2026-09-29
- Page: https://skillmd.com/skills/tools-only/2-json

---

# Tool 工具系统

## 实现功能

SimpleLLMFunc 的工具系统为大语言模型提供了调用外部函数和 API 的能力，让 LLM 能够执行计算、查询数据、调用服务等操作。工具系统支持两种创建方式，并能自动将 Python 函数转换为 LLM 可理解的工具描述格式。

### 核心功能特性
- **智能类型推断**: 自动从函数签名中提取参数类型和描述信息
- **文档字符串解析**: 支持从 docstring 中解析参数描述
- **JSON Schema 生成**: 自动生成符合 OpenAI Function Calling API 的工具描述
- **类型安全**: 支持基本类型、容器类型、Pydantic 模型等多种类型
- **灵活创建**: 支持装饰器和继承两种创建方式
- **批量序列化**: 支持将多个工具一次性序列化为 API 格式

### 支持的数据类型（参数类型）
- **基本类型**: `str`, `int`, `float`, `bool`
- **容器类型**: `List[T]`, `Dict[K, V]`
- **Pydantic 模型**: 自动解析模型字段和验证规则
- **可选参数**: 支持带默认值的可选参数
- **复杂嵌套**: 支持嵌套的容器类型和复杂对象

**⚠️ 注意： 由于LLM提供给工具的参数只能是json支持的类型，所以实际上`Tuple`, `Set`等容器是无法作为工具参数的**

### 支持的返回类型
工具函数可以返回多种数据格式，系统会自动处理这些返回值并将其传递给 LLM：

#### 基本返回类型
- **字符串 (`str`)**: 直接作为文本内容返回给 LLM
- **JSON 可序列化对象**: 包括 `dict`, `list`, `int`, `float`, `bool`, `None`
- **Pydantic 模型**: 自动序列化为 JSON 格式

#### 多模态返回类型
- **图片URL (`ImgUrl`)**: 返回网络图片链接，LLM 可以"看到"图片内容
- **本地图片 (`ImgPath`)**: 返回本地图片文件路径，自动转换为 base64 格式
- **文本+图片组合 (`Tuple[str, ImgUrl]` 或 `Tuple[str, ImgPath]`)**: 同时返回文本说明和图片

#### 返回类型示例

```python
from SimpleLLMFunc import tool
from SimpleLLMFunc.llm_decorator import ImgUrl, ImgPath
from typing import Dict, List, Tuple, Any

# 1. 基本类型返回
@tool(name="calculate", description="执行数学计算")
async def calculate(expression: str) -> float:
    """返回计算结果（浮点数）"""
    return eval(expression)

@tool(name="get_status", description="获取系统状态")
async def get_status() -> str:
    """返回状态信息（字符串）"""
    return "系统运行正常"

# 2. JSON 对象返回
@tool(name="get_user_info", description="获取用户信息")
async def get_user_info(user_id: int) -> Dict[str, Any]:
    """返回用户信息（字典）"""
    return {
        "id": user_id,
        "name": "张三",
        "age": 25,
        "skills": ["Python", "AI", "数据分析"]
    }

@tool(name="search_results", description="搜索并返回结果列表")
async def search_results(query: str) -> List[Dict[str, str]]:
    """返回搜索结果（字典列表）"""
    return [
        {"title": "结果1", "url": "https://example1.com"},
        {"title": "结果2", "url": "https://example2.com"}
    ]

# 3. 多模态返回 - 单独图片
@tool(name="get_chart", description="生成数据图表")
async def get_chart(data: List[float]) -> ImgPath:
    """返回图表图片（本地文件）"""
    # 在chart path下有一个对应的图片文件
    chart_path = "/path/to/generated/chart.png"
    return ImgPath(chart_path)

@tool(name="fetch_image", description="获取网络图片")
async def fetch_image(image_url: str) -> ImgUrl:
    """返回网络图片URL"""
    return ImgUrl(image_url)

# 4. 多模态返回 - 文本+图片组合
@tool(name="analyze_image", description="分析图片并生成报告")
async def analyze_image(image_path: str) -> Tuple[str, ImgPath]:
    """返回分析报告和标注后的图片"""
    analysis_text = "检测到3个对象：2个人、1辆汽车"
    annotated_image = ImgPath("/path/to/annotated_image.png")
    return (analysis_text, annotated_image)
```

#### 返回类型处理机制

1. **基本类型**: 直接序列化为 JSON 字符串传递给 LLM
2. **图片类型**: 
   - `ImgUrl`: 直接使用网络 URL
   - `ImgPath`: 自动转换为 base64 编码的 data URL
3. **组合类型**: 将文本和图片组合成多模态消息，LLM 可以同时看到文本说明和图片内容
4. **错误处理**: 不支持的返回类型会自动转换为字符串格式

**⚠️ 返回类型注意事项**:
- 确保本地图片文件路径存在且可读
- 网络图片 URL 应该是公开可访问的
- 组合类型的元组必须是 `(str, ImgPath)` 或 `(str, ImgUrl)` 格式, 不能交换`str`和`ImgPath`/`ImgUrl`的顺序
- 避免返回过大的数据结构，以免影响 LLM 处理效率

**⚠️ 注意：`@tool` 装饰器要求被装饰的函数本身定义为 `async def`，以便在异步执行链路中无缝协作。**

## 使用方法

### 基本语法

#### 方式一：装饰器方式（推荐）

```python
from SimpleLLMFunc import tool

@tool(name="工具名称", description="工具简短描述")
async def your_function(param1: Type1, param2: Type2 = default_value) -> ReturnType:
    """
    详细的函数说明，这部分会被包含在工具描述中
    
    Args:
        param1: 参数1的详细描述
        param2: 参数2的详细描述
        
    Returns:
        返回值描述
    """
    # 函数实现
    pass
```

#### 方式二：继承方式（兼容旧版本）

```python
from SimpleLLMFunc import Tool

class YourTool(Tool):
    def __init__(self):
        super().__init__(
            name="工具名称",
            description="工具描述"
        )
    
    def run(self, *args, **kwargs):
        # 工具执行逻辑
        pass
```

### 参数说明

#### @tool 装饰器参数
- **name** (必需): 工具名称，应该简洁明了，符合函数命名规范
- **description** (必需): 工具的简短描述，说明工具的主要功能

#### 函数要求
- **类型标注**: 建议为所有参数添加类型标注，以便自动生成准确的 JSON Schema
- **文档字符串**: 建议编写详细的 docstring，特别是 Args 部分的参数描述
- **返回类型**: 建议添加返回类型标注

### 工具调用流程

1. **工具注册**: 使用 `@tool` 装饰器或继承 `Tool` 类创建工具
2. **参数解析**: 系统自动从函数签名中提取参数信息
3. **Schema 生成**: 自动生成符合 OpenAI API 的工具描述
4. **LLM 调用**: LLM 根据工具描述决定是否调用工具
5. **参数验证**: 系统验证 LLM 提供的参数是否符合要求
6. **函数执行**: 调用原始 Python 函数并返回结果

## 实现方法

### 核心类结构

#### Parameter 类
```python
class Parameter:
    """工具参数的包装类"""
    def __init__(self, name, description, type_annotation, required, default=None, example=None):
        self.name = name                    # 参数名
        self.description = description      # 参数描述
        self.type_annotation = type_annotation  # Python 类型标注
        self.required = required            # 是否必需
        self.default = default             # 默认值
        self.example = example             # 示例值
```

#### Tool 类
```python
class Tool(ABC):
    """抽象工具基类"""
    def __init__(self, name, description, func=None):
        self.name = name
        self.description = description
        self.func = func                   # 关联的函数
        self.parameters = self._extract_parameters()  # 参数列表
    
    def run(self, *args, **kwargs):
        """执行工具"""
        
    def to_openai_tool(self):
        """转换为 OpenAI 工具格式"""
        
    @staticmethod
    def serialize_tools(tools):
        """批量序列化工具"""
```

### 关键实现细节

#### 参数提取机制
系统通过以下步骤提取函数参数信息：

1. **签名分析**: 使用 `inspect.signature()` 获取函数签名
2. **类型提示**: 使用 `get_type_hints()` 获取类型标注
3. **文档解析**: 使用正则表达式解析 docstring 中的参数描述
4. **默认值处理**: 识别可选参数和默认值

#### 类型转换规则
```python
# 基本类型映射
str  -> {"type": "string"}
int  -> {"type": "integer"}
float -> {"type": "number"}
bool -> {"type": "boolean"}

# 容器类型
List[T] -> {"type": "array", "items": schema_of_T}
Dict[K, V] -> {"type": "object", "additionalProperties": schema_of_V}

# Pydantic 模型
BaseModel -> {"type": "object", "properties": model_json_schema}
```

#### 文档字符串解析
系统支持标准的 docstring 格式：

```python
def example_function(param1: str, param2: int = 10):
    """
    函数的主要描述
    
    Args:
        param1: 第一个参数的描述
        param2: 第二个参数的描述，可选
        
    Returns:
        返回值描述
    """
```

## 兼容写法

### 装饰器方式示例

```python
from SimpleLLMFunc import tool
from typing import List, Dict, Any, Optional
from pydantic import BaseModel, Field

# 示例1：基本数据类型
@tool(name="calculate", description="执行数学计算")
async def calculate(expression: str) -> float:
    """
    计算数学表达式的值
    
    Args:
        expression: 要计算的数学表达式，如 "2 + 3 * 4"
        
    Returns:
        计算结果
    """
    return eval(expression)

# 示例2：带可选参数
@tool(name="search_web", description="搜索网络信息")
async def search_web(query: str, max_results: int = 10, language: str = "zh") -> List[Dict[str, str]]:
    """
    在网络上搜索信息
    
    Args:
        query: 搜索关键词
        max_results: 最大返回结果数量，默认10条
        language: 搜索语言，默认中文
        
    Returns:
        搜索结果列表，每个结果包含标题和链接
    """
    # 模拟搜索实现
    return [
        {"title": f"搜索结果 {i}", "url": f"http://example.com/{i}"} 
        for i in range(max_results)
    ]

# 示例3：使用 Pydantic 模型
class Location(BaseModel):
    latitude: float = Field(..., description="纬度")
    longitude: float = Field(..., description="经度")
    name: Optional[str] = Field(None, description="位置名称")

@tool(name="get_weather", description="获取天气信息")
async def get_weather(location: Location, days: int = 1) -> Dict[str, Any]:
    """
    获取指定位置的天气预报
    
    Args:
        location: 位置信息，包含经纬度坐标
        days: 预报天数，1-7天，默认1天
        
    Returns:
        天气预报数据，包含温度、湿度、天气状况等
    """
    return {
        "location": location.name or f"{location.latitude},{location.longitude}",
        "forecast": [
            {
                "day": i + 1,
                "temperature": 25,
                "humidity": 60,
                "condition": "晴朗"
            } for i in range(days)
        ]
    }

# 示例4：复杂数据处理
@tool(name="analyze_data", description="分析数据集")
async def analyze_data(
    data: List[Dict[str, Any]], 
    analysis_type: str,
    include_charts: bool = False
) -> Dict[str, Any]:
    """
    对数据集进行统计分析
    
    Args:
        data: 要分析的数据集，每个元素是一个包含字段的字典
        analysis_type: 分析类型，可选值：summary, trend, correlation
        include_charts: 是否包含图表数据，默认否
        
    Returns:
        分析结果，包含统计信息和可选的图表数据
    """
    result = {
        "type": analysis_type,
        "record_count": len(data),
        "summary": "数据分析完成"
    }
    
    if include_charts:
        result["charts"] = {"type": "bar", "data": "chart_data"}
    
    return result

# 示例5：多模态返回类型
from SimpleLLMFunc import ImgUrl, ImgPath
from typing import Tuple

@tool(name="generate_chart", description="生成数据图表")
async def generate_chart(data: List[float], chart_type: str = "bar") -> ImgPath:
    """
    根据数据生成图表并保存为本地文件
    
    Args:
        data: 数据列表
        chart_type: 图表类型，如 bar、line、pie
        
    Returns:
        生成的图表文件路径
    """
    # 模拟图表生成
    import matplotlib.pyplot as plt
    import tempfile
    import os
    
    plt.figure(figsize=(10, 6))
    if chart_type == "bar":
        plt.bar(range(len(data)), data)
    elif chart_type == "line":
        plt.plot(data)
    
    # 保存到临时文件
    temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.png')
    plt.savefig(temp_file.name)
    plt.close()
    
    return ImgPath(temp_file.name)

@tool(name="fetch_web_image", description="获取网络图片")
async def fetch_web_image(image_url: str) -> ImgUrl:
    """
    验证并返回网络图片URL
    
    Args:
        image_url: 图片的网络地址
        
    Returns:
        验证后的图片URL对象
    """
    # 可以添加URL验证逻辑
    return ImgUrl(image_url, detail="high")

@tool(name="analyze_image_with_report", description="分析图片并生成详细报告")
async def analyze_image_with_report(image_path: str) -> Tuple[str, ImgPath]:
    """
    分析图片内容并生成带标注的图片
    
    Args:
        image_path: 要分析的图片路径
        
    Returns:
        分析报告文本和标注后的图片路径
    """
    # 模拟图像分析
    analysis_report = """
    图像分析报告：
    - 检测到 3 个对象
    - 主要颜色：蓝色、绿色
    - 场景类型：户外风景
    - 置信度：95%
    """
    
    # 模拟生成标注图片
    annotated_image_path = "/path/to/annotated_image.png"
    
    return (analysis_report.strip(), ImgPath(annotated_image_path))

@tool(name="create_data_visualization", description="创建在线数据可视化")
async def create_data_visualization(dataset: Dict[str, Any]) -> Tuple[str, ImgUrl]:
    """
    创建数据可视化并上传到云端
    
    Args:
        dataset: 包含数据和配置的字典
        
    Returns:
        可视化说明和在线图片URL
    """
    # 模拟数据可视化处理
    description = f"""
    数据可视化已创建：
    - 数据点数量：{len(dataset.get('data', []))}
    - 图表类型：{dataset.get('chart_type', '未指定')}
    - 创建时间：刚刚
    """
    
    # 模拟上传到云端并获取URL
    visualization_url = ImgUrl("https://example.com/visualizations/chart_12345.png")
    
    return (description.strip(), visualization_url)
```

### 继承方式示例

```python
from SimpleLLMFunc import Tool
import requests
from typing import Dict, Any, List

class WebSearchTool(Tool):
    """网络搜索工具的继承实现方式"""
    
    def __init__(self):
        super().__init__(
            name="web_search",
            description="在网络上搜索信息并返回相关结果"
        )
    
    def run(self, query: str, max_results: int = 5) -> List[Dict[str, str]]:
        """
        执行网络搜索
        
        Args:
            query: 搜索关键词
            max_results: 最大结果数量
            
        Returns:
            搜索结果列表
        """
        # 实际的搜索逻辑
        results = []
        for i in range(max_results):
            results.append({
                "title": f"搜索结果 {i+1}: {query}",
                "url": f"https://example.com/result/{i+1}",
                "snippet": f"关于{query}的相关信息..."
            })
        return results

class APICallTool(Tool):
    """API 调用工具"""
    
    def __init__(self, api_base_url: str):
        super().__init__(
            name="api_call",
            description="调用外部 API 获取数据"
        )
        self.api_base_url = api_base_url
    
    def run(self, endpoint: str, method: str = "GET", data: Dict[str, Any] = None) -> Dict[str, Any]:
        """
        调用 API 端点
        
        Args:
            endpoint: API 端点路径
            method: HTTP 方法
            data: 请求数据
            
        Returns:
            API 响应数据
        """
        url = f"{self.api_base_url}/{endpoint.lstrip('/')}"
        
        if method.upper() == "GET":
            response = requests.get(url, params=data)
        elif method.upper() == "POST":
            response = requests.post(url, json=data)
        else:
            raise ValueError(f"不支持的 HTTP 方法: {method}")
        
        return response.json()
```

### 工具使用示例

```python
import asyncio
from SimpleLLMFunc import llm_function, llm_chat, OpenAICompatible

# 初始化 LLM（从配置文件加载）
models = OpenAICompatible.load_from_json_file("provider.json")
llm = models["openai"]["gpt-3.5-turbo"]

# 在 llm_function 中使用工具
@llm_function(
    llm_interface=llm,
    toolkit=[calculate, search_web, get_weather]
)
async def intelligent_assistant(query: str) -> str:
    """
    智能助手，可以进行计算、搜索和查询天气。
    根据用户查询的内容，选择合适的工具来提供准确的答案。
    """
    pass

# 在 llm_chat 中使用工具
@llm_chat(
    llm_interface=llm,
    toolkit=[calculate, search_web, get_weather, analyze_data]
)
async def chat_with_tools(message: str, history: List[Dict[str, str]] | None = None):
    """
    支持工具调用的聊天助手。
    可以执行计算、搜索网络、查询天气和分析数据。
    """
    yield "", history or []

# 使用示例
async def main():
    # 使用 llm_function
    result = await intelligent_assistant("帮我计算 25 * 4 + 18 的结果")
    print(f"计算结果: {result}")

    # 使用 llm_chat
    history = []
    async for response, updated_history in chat_with_tools("北京今天天气怎么样？", history):
        if response:
            print(response, end="")
        history = updated_history

if __name__ == "__main__":
    asyncio.run(main())
```

### 高级用法

#### 工具序列化和检查

```python
from SimpleLLMFunc import Tool
import json

# 创建工具列表
tools = [calculate, search_web, get_weather]

# 序列化为 OpenAI 格式
openai_tools = Tool.serialize_tools(tools)

# 查看生成的工具描述
for tool_spec in openai_tools:
    print(json.dumps(tool_spec, indent=2, ensure_ascii=False))
```

#### 动态工具创建

```python
def create_tool_from_config(config: Dict[str, Any]):
    """根据配置动态创建工具"""

    @tool(name=config["name"], description=config["description"])
    async def dynamic_tool(**kwargs):
        # 根据配置执行相应逻辑
        return config["handler"](kwargs)

    return dynamic_tool

# 配置示例
tool_config = {
    "name": "custom_processor",
    "description": "自定义数据处理器",
    "handler": lambda data: {"processed": True, "data": data}
}

custom_tool = create_tool_from_config(tool_config)
```

#### 工具链组合

```python
@tool(name="multi_step_analysis", description="多步骤数据分析")
async def multi_step_analysis(data: List[Dict[str, Any]], steps: List[str]) -> Dict[str, Any]:
    """
    执行多步骤数据分析流程
    
    Args:
        data: 原始数据
        steps: 分析步骤列表，如 ["clean", "analyze", "visualize"]
        
    Returns:
        分析结果
    """
    results = {"steps_completed": []}
    
    for step in steps:
        if step == "clean":
            # 数据清洗
            results["cleaned_records"] = len(data)
        elif step == "analyze":
            # 数据分析
            results["analysis"] = {"mean": 0, "std": 0}
        elif step == "visualize":
            # 数据可视化
            results["charts"] = ["bar", "line", "pie"]
        
        results["steps_completed"].append(step)
    
    return results
```

### 返回类型最佳实践

#### 选择合适的返回类型

1. **文本输出优先**: 如果工具主要产生文本结果，使用 `str` 或结构化的 `Dict`
2. **结构化数据**: 复杂数据使用 `Dict` 或 `List`，便于 LLM 理解和处理
3. **多模态内容**: 需要展示图片时使用 `ImgPath`、`ImgUrl` 或组合类型
4. **组合输出**: 需要同时提供说明和图片时使用 `Tuple[str, ImgPath/ImgUrl]`

#### 性能优化建议

```python
# ✅ 推荐：结构化返回，便于LLM理解
@tool(name="search_products", description="搜索商品")
async def search_products(query: str) -> Dict[str, Any]:
    return {
        "total": 10,
        "products": [
            {"name": "商品1", "price": 99.9, "in_stock": True},
            {"name": "商品2", "price": 149.9, "in_stock": False}
        ],
        "query_time": "2024-01-01 12:00:00"
    }

# ✅ 推荐：多模态组合返回
@tool(name="generate_report", description="生成分析报告")
async def generate_report(data: List[Dict]) -> Tuple[str, ImgPath]:
    summary = f"分析了 {len(data)} 条记录，发现 3 个关键趋势"
    chart_path = ImgPath("/tmp/analysis_chart.png")
    return (summary, chart_path)

# ❌ 避免：返回过大的数据结构
def bad_example() -> Dict:
    return {
        "huge_data": list(range(10000)),  # 过大的数据
        "binary_content": b"..."  # 二进制数据无法JSON序列化
    }
```

#### 错误处理模式

```python
@tool(name="safe_division", description="安全除法运算")
async def safe_division(a: float, b: float) -> Dict[str, Any]:
    """
    安全的除法运算，包含错误处理
    
    Returns:
        包含结果或错误信息的字典
    """
    if b == 0:
        return {
            "success": False,
            "error": "除数不能为零",
            "result": None
        }
    
    return {
        "success": True,
        "error": None,
        "result": a / b
    }

@tool(name="robust_image_tool", description="鲁棒的图像处理工具")
async def robust_image_tool(image_path: str) -> Tuple[str, ImgPath]:
    """
    带错误处理的图像工具
    """
    try:
        # 图像处理逻辑
        processed_image = process_image(image_path)
        return ("图像处理成功", ImgPath(processed_image))
    except Exception as e:
        # 返回错误信息和默认图片
        error_msg = f"图像处理失败: {str(e)}"
        default_img = ImgPath("/path/to/error_placeholder.png")
        return (error_msg, default_img)
```

#### 多模态类型详细说明

```python
from SimpleLLMFunc import ImgUrl, ImgPath

# ImgPath 使用示例
img_local = ImgPath(
    path="/path/to/image.jpg",
    detail="high"  # 可选：图片细节级别 low/high
)

# ImgUrl 使用示例  
img_url = ImgUrl(
    url="https://example.com/image.jpg",
    detail="low"  # 网络图片建议使用low以节省token
)

# 组合类型使用
def complex_analysis() -> Tuple[str, ImgPath]:
    analysis = """
    检测结果：
    - 人数：3人
    - 车辆：1辆
    - 置信度：92%
    """
    annotated_img = ImgPath("/tmp/detection_result.jpg")
    return (analysis.strip(), annotated_img)
```

---

工具系统提供了强大而灵活的扩展机制，让 LLM 能够调用各种外部功能。通过装饰器方式，开发者可以轻松地将现有函数转换为 LLM 可用的工具，而继承方式则提供了更多的自定义控制。系统自动处理类型转换和参数验证，确保工具调用的安全性和准确性。

**关键要点总结**：
- 支持多种返回类型：基本类型、JSON对象、多模态内容
- 多模态支持：单独图片或文本+图片组合
- 自动类型转换：本地图片转base64，网络图片直接使用URL
- 错误处理：推荐返回结构化的错误信息
- 性能考虑：避免返回过大数据，网络图片使用低细节级别
