✨ 复制成功!

以文会友,打造好学人设!

📋 已复制到剪贴板:

《Agent Harness入门教程》


1 AI Agent与Harness概述
    1.1  什么是AI Agent

    1.2  什么是Agent Harness

    1.3  为什么需要Harness

2 核心概念与架构模式
    2.1  Agent的核心循环

    2.2  Harness的典型架构

    2.3  单Agent vs 多Agent

    2.4  Agent生命周期

    2.5  核心术语表

3 环境搭建与基础组件
    3.1  选择LLM Provider

    3.2  搭建最小Harness

    3.3  实现工具调用机制

    3.4  结构化输出与解析

    3.5  日志与调试基础

4 工具系统设计
    4.1  工具的定义与注册

    4.2  工具Schema设计

    4.3  安全沙箱

5 记忆与会话管理
6 Agent推理与规划
7 商业项目实战练习

实战:为Agent编写5个实用工具

创建时间:2026-09-05 更新时间:2026-09-05 阅读次数:1096 次

本节目标

在前四节中,你学习了工具的定义与注册、Schema设计、安全沙箱和错误处理。这些知识是分开讲解的,但在实际开发中,你需要将它们整合起来应用。

本节将构建一个完整的实战案例:为Agent编写5个实用工具。这些工具的设计覆盖了不同的分类、权限级别、错误处理策略和安全考量。通过这个案例,你将看到第4章的所有原则如何在一个具体的工具集中落地。

场景设定

假设你在为一个智能办公助手Agent构建工具集。这个Agent帮助用户处理日常办公任务:查询日程、发送消息、管理任务、搜索文档、生成报告。

这5个工具分别是:

工具名分类权限重试策略
query_calendarREADAUTO可重试(指数退避)
send_messageCOMMUNICATEUSER_CONFIRM不重试(非幂等)
create_taskWRITEAUTO可重试(幂等设计)
search_documentsREADAUTO可重试(固定间隔)
execute_calculationEXECUTEAUTO不重试(确定性操作)

这5个工具覆盖了不同的副作用级别和安全需求。让我们逐一实现。

工具一:query_calendar(查询日历)

设计考量:

  • READ类工具,无副作用,可以自动执行

  • 需要处理日期格式的复杂性——LLM可能填"明天"、"下周三"、"2026-09-10"等各种格式

  • 网络请求可能超时,需要指数退避重试

from datetime import datetime, timedelta
import re
from typing import List, Optional

def query_calendar(start_date: str, end_date: str, 
                   calendar_id: str = "primary") -> str:
    """
    查询指定时间范围内的日历事件。

    参数start_date和end_date支持以下格式:
    - YYYY-MM-DD(如"2026-09-01")
    - 相对日期(如"today"、"tomorrow"、"next week")
    """
    # 解析日期
    start = _parse_flexible_date(start_date)
    end = _parse_flexible_date(end_date)

    if start is None or end is None:
        return f"日期格式无法解析:start={start_date}, end={end_date}。请使用YYYY-MM-DD格式。"

    if start > end:
        return f"开始日期({start_date})晚于结束日期({end_date})。请调整日期范围。"

    # 模拟日历数据
    events = [
        {"date": "2026-09-03", "time": "10:00", "title": "产品评审会", "attendees": 6},
        {"date": "2026-09-05", "time": "14:30", "title": "一对一沟通", "attendees": 2},
        {"date": "2026-09-08", "time": "09:00", "title": "周会", "attendees": 12},
    ]

    # 过滤时间范围内的事件
    filtered = []
    for event in events:
        event_date = datetime.strptime(event["date"], "%Y-%m-%d")
        if start <= event_date <= end:
            filtered.append(event)

    if not filtered:
        return f"{start_date} 到 {end_date} 期间没有日程安排。"

    result_lines = [f"{start_date} 到 {end_date} 期间的日程:"]
    for event in filtered:
        result_lines.append(
            f"- {event['date']} {event['time']} | {event['title']}({event['attendees']}人)"
        )
    return "\n".join(result_lines)


def _parse_flexible_date(date_str: str) -> Optional[datetime]:
    """解析灵活的日期格式"""
    today = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)

    # YYYY-MM-DD格式
    if re.match(r'^\d{4}-\d{2}-\d{2}$', date_str):
        try:
            return datetime.strptime(date_str, "%Y-%m-%d")
        except ValueError:
            return None

    # 相对日期
    relative_dates = {
        "today": today,
        "tomorrow": today + timedelta(days=1),
        "yesterday": today - timedelta(days=1),
        "next week": today + timedelta(days=7),
        "last week": today - timedelta(days=7),
        "next month": today + timedelta(days=30),
    }

    if date_str.lower() in relative_dates:
        return relative_dates[date_str.lower()]

    return None


# 注册工具
@registry.tool(
    description=(
        "查询指定时间范围内的日历事件。"
        "当用户询问'我下周有什么安排'、'明天有没有会议'、'查看日历'等问题时使用此工具。"
        "参数start_date和end_date支持YYYY-MM-DD格式或相对日期(today/tomorrow/next week等)。"
        "如果用户只提供了一个日期,将start_date和end_date设为同一天。"
    ),
    category=ToolCategory.READ,
    permission=ToolPermission.AUTO,
    timeout_seconds=10,
    max_retries=3,
    tags=["calendar", "schedule", "query"],
    examples=[
        "query_calendar(start_date='2026-09-01', end_date='2026-09-07')",
        "query_calendar(start_date='tomorrow', end_date='tomorrow')",
    ],
)
def query_calendar_tool(start_date: str, end_date: str, calendar_id: str = "primary") -> str:
    """查询日历"""
    return query_calendar(start_date, end_date, calendar_id)

值得注意的设计:

第一:灵活日期解析。 _parse_flexible_date支持多种日期格式,并处理了解析失败的情况。这很重要——LLM可能填"明天"而不是"2026-09-06",工具应该能处理这种灵活性。

第二:日期范围的合理性检查。 工具验证了start <= end。如果LLM填反了日期(比如start="2026-09-07"而end="2026-09-01"),工具返回清晰的错误信息,LLM可以据此修正。

第三:示例中包含具体参数。 examples字段展示了工具的典型用法,这会在描述中帮助LLM理解如何正确调用。

工具二:send_message(发送消息)

设计考量:

  • COMMUNICATE类工具,有外部副作用,需要用户确认

  • 非幂等——发送两次消息,接收者收到两条。因此不自动重试

  • 需要验证接收者的有效性

def send_message(to_user: str, content: str, 
                 channel: str = "internal") -> str:
    """
    向指定用户发送消息。

    参数to_user是接收者的用户名或邮箱。
    参数channel是发送渠道:internal=站内消息,email=邮件。
    """
    # 验证接收者
    valid_users = {"zhangsan", "lisi", "wangwu", "zhaoliu"}

    # 提取用户名(可能输入的是邮箱)
    username = to_user.lower()
    if "@" in username:
        username = username.split("@")[0]

    if username not in valid_users:
        return f"未找到用户 '{to_user}'。可用的用户:{', '.join(sorted(valid_users))}"

    # 验证消息内容
    if len(content) > 2000:
        return f"消息内容过长({len(content)}字符),最大允许2000字符。请精简消息。"

    if not content.strip():
        return "消息内容不能为空。"

    # 模拟发送
    # 实际场景中这里会调用消息服务API
    send_result = {
        "message_id": f"MSG-{datetime.now().strftime('%Y%m%d%H%M%S')}",
        "to": username,
        "channel": channel,
        "status": "sent",
        "timestamp": datetime.now().isoformat(),
    }

    return (
        f"消息已发送。"
        f"接收者:{username},渠道:{channel},"
        f"消息ID:{send_result['message_id']}"
    )


@registry.tool(
    description=(
        "向指定用户发送消息。"
        "当用户要求'通知某人'、'发消息给...'、'告诉...'时使用此工具。"
        "参数to_user是接收者的用户名或邮箱。"
        "参数content是消息内容,使用纯文本格式。"
        "参数channel是发送渠道:internal=站内消息(默认),email=邮件。"
        "注意:此工具会向真实用户发送消息,执行前需要用户确认。"
        "消息发送后不可撤回。"
    ),
    category=ToolCategory.COMMUNICATE,
    permission=ToolPermission.USER_CONFIRM,
    timeout_seconds=15,
    max_retries=0,  # 非幂等,不重试
    tags=["message", "communication", "notify"],
    metadata={"idempotent": False},
    examples=[
        "send_message(to_user='zhangsan', content='请查看最新的项目报告')",
    ],
)
def send_message_tool(to_user: str, content: str, channel: str = "internal") -> str:
    """发送消息"""
    return send_message(to_user, content, channel)

值得注意的设计:

第一:验证接收者。 工具在"发送"之前先验证接收者是否存在。这防止了LLM填一个不存在的用户,导致消息丢失。注意工具返回的错误信息("未找到用户...可用的用户:...")足够LLM理解并做出调整。

第二:非幂等的明确标记。 max_retries=0和metadata={"idempotent": False}双重标记了此工具的非幂等特性。即使发生网络超时,也不会自动重试——宁可发送失败,也不能重复发送。

第三:USER_CONFIRM权限。 这个工具的调用会触发用户确认流程。Agent提议发送消息,但最终由用户批准。

工具三:create_task(创建任务)

设计考量:

  • WRITE类工具,有副作用但可接受自动执行

  • 设计为幂等——相同的task_id不会创建重复任务

  • 需要验证优先级和截止日期的合理性

def create_task(title: str, due_date: str, 
                priority: str = "medium",
                assignee: str = "me",
                task_id: Optional[str] = None) -> str:
    """
    创建一个新任务。

    参数task_id是可选的幂等键。如果提供了task_id且该任务已存在,不会创建重复任务。
    """
    # 验证标题
    if len(title) < 3:
        return f"任务标题过短({len(title)}字符)。请提供至少3字符的描述性标题。"

    if len(title) > 200:
        return f"任务标题过长({len(title)}字符)。请精简到200字符以内。"

    # 验证优先级
    valid_priorities = {"low", "medium", "high", "urgent"}
    if priority not in valid_priorities:
        return f"无效的优先级 '{priority}'。可选值:{', '.join(sorted(valid_priorities))}"

    # 验证截止日期
    parsed_date = _parse_flexible_date(due_date)
    if parsed_date is None:
        return f"截止日期无法解析:{due_date}。请使用YYYY-MM-DD格式。"

    today = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
    if parsed_date < today:
        return f"截止日期({due_date})已过期。请设置未来的日期。"

    # 幂等检查
    if task_id:
        existing = _find_task_by_idempotency_key(task_id)
        if existing:
            return f"任务已存在(任务ID:{existing['id']}),不重复创建。"

    # 模拟创建任务
    generated_id = task_id or f"TASK-{datetime.now().strftime('%Y%m%d%H%M%S')}"
    task = {
        "id": generated_id,
        "title": title,
        "due_date": due_date,
        "priority": priority,
        "assignee": assignee,
        "status": "pending",
        "created_at": datetime.now().isoformat(),
    }

    # 存储任务(实际场景中是数据库写入)
    _store_task(task)

    return (
        f"任务已创建。"
        f"任务ID:{task['id']},"
        f"标题:{title},"
        f"截止日期:{due_date},"
        f"优先级:{priority}"
    )


# 模拟存储
_task_storage = {}

def _store_task(task: dict):
    _task_storage[task["id"]] = task

def _find_task_by_idempotency_key(task_id: str) -> Optional[dict]:
    return _task_storage.get(task_id)


@registry.tool(
    description=(
        "创建一个新任务。"
        "当用户要求'创建任务'、'添加待办'、'记一下...'时使用此工具。"
        "参数title是任务标题,至少3字符。"
        "参数due_date是截止日期,支持YYYY-MM-DD格式或相对日期。"
        "参数priority是优先级:low/medium/high/urgent,默认为medium。"
        "参数task_id是可选的幂等键,用于防止重复创建。如果不确定,可以不填。"
    ),
    category=ToolCategory.WRITE,
    permission=ToolPermission.AUTO,
    timeout_seconds=10,
    max_retries=2,  # 幂等操作,可以重试
    tags=["task", "todo", "write"],
    metadata={"idempotent": True},
    examples=[
        "create_task(title='准备季度报告', due_date='2026-09-15', priority='high')",
    ],
)
def create_task_tool(title: str, due_date: str, 
                     priority: str = "medium",
                     assignee: str = "me",
                     task_id: Optional[str] = None) -> str:
    """创建任务"""
    return create_task(title, due_date, priority, assignee, task_id)

值得注意的设计:

第一:幂等设计。 通过可选的task_id参数实现幂等性。如果LLM或Harness因为网络原因重试了工具调用,同一个task_id不会创建重复任务。这是WRITE类工具允许重试的前提条件。

第二:多维度验证。 工具验证了标题长度、优先级枚举值、截止日期的合理性和未来性。这些验证确保LLM填的参数是业务上合理的,而不仅仅是格式上正确的。

第三:允许重试但有限度。 max_retries=2——因为幂等设计保证了重试安全,但也不应该无限重试。

工具四:search_documents(搜索文档)

设计考量:

  • READ类工具,无副作用

  • 搜索结果可能很大,需要在工具内部做摘要和截断

  • 需要处理"无结果"的情况

def search_documents(query: str, max_results: int = 5, 
                     doc_type: str = "all") -> str:
    """
    搜索文档库。

    参数doc_type可选值:all=所有类型,report=报告,meeting=会议记录,spec=技术文档
    """
    # 模拟文档库
    documents = [
        {"id": "DOC-001", "title": "2026年Q2产品路线图", "type": "report", 
         "content": "Q2聚焦AI功能开发...", "relevance": 0.95},
        {"id": "DOC-002", "title": "7月产品评审会纪要", "type": "meeting",
         "content": "讨论了用户反馈和优先级...", "relevance": 0.87},
        {"id": "DOC-003", "title": "搜索架构设计文档", "type": "spec",
         "content": "描述了搜索系统的技术架构...", "relevance": 0.82},
        {"id": "DOC-004", "title": "2026年H1销售报告", "type": "report",
         "content": "上半年销售数据分析...", "relevance": 0.78},
        {"id": "DOC-005", "title": "8月战略讨论会纪要", "type": "meeting",
         "content": "确定了Q3的战略重点...", "relevance": 0.75},
    ]

    # 验证搜索词
    if len(query) < 2:
        return f"搜索词过短('{query}')。请提供至少2字符的搜索词。"

    # 过滤类型
    if doc_type != "all":
        documents = [d for d in documents if d["type"] == doc_type]

    # 模拟相关性搜索
    results = [d for d in documents if query.lower() in d["title"].lower() 
               or query.lower() in d["content"].lower()]

    # 按相关性排序
    results.sort(key=lambda x: x["relevance"], reverse=True)

    # 限制结果数量
    results = results[:max_results]

    if not results:
        return f"未找到与 '{query}' 相关的文档。建议尝试更宽泛的搜索词,或检查搜索词的拼写。"

    # 构建结果摘要
    lines = [f"搜索 '{query}' 找到 {len(results)} 个相关文档:"]
    for doc in results:
        # 内容摘要(前50字符)
        content_summary = doc["content"][:50] + "..." if len(doc["content"]) > 50 else doc["content"]
        lines.append(
            f"- [{doc['id']}] {doc['title']}({doc['type']},相关度:{doc['relevance']:.2f})\n"
            f"  摘要:{content_summary}"
        )

    return "\n".join(lines)


@registry.tool(
    description=(
        "搜索文档库中的文档。"
        "当用户询问'有没有关于...的文档'、'查找...资料'、'搜索文档'时使用此工具。"
        "参数query是搜索关键词,至少2字符。"
        "参数doc_type是文档类型过滤:all=所有,report=报告,meeting=会议记录,spec=技术文档。"
        "参数max_results是返回的最大结果数,默认为5。"
        "返回文档的标题、类型和相关度摘要。"
    ),
    category=ToolCategory.READ,
    permission=ToolPermission.AUTO,
    timeout_seconds=10,
    max_retries=3,
    tags=["search", "document", "query"],
    examples=[
        "search_documents(query='产品路线图', doc_type='report')",
    ],
)
def search_documents_tool(query: str, max_results: int = 5, doc_type: str = "all") -> str:
    """搜索文档"""
    return search_documents(query, max_results, doc_type)

值得注意的设计:

第一:工具内部做摘要。 搜索工具返回的是摘要(标题+内容前50字符+相关度),而不是完整的文档内容。这样设计的原因:LLM拿到摘要后,如果认为某个文档相关,可以再请求完整内容(通过其他工具)。搜索工具返回摘要,文档读取工具返回全文——责任分离。

第二:"无结果"也是有用的信息。 工具返回"未找到相关文档"并给出建议(尝试更宽泛的搜索词),这比返回空字符串更有用。LLM看到"未找到"后可以调整搜索策略。

工具五:execute_calculation(执行计算)

设计考量:

  • EXECUTE类工具,执行代码。安全风险最高的工具

  • 需要使用安全沙箱隔离执行

  • 确定性操作,不需要重试

import ast
import math

def execute_calculation(expression: str) -> str:
    """
    安全地计算数学表达式。

    只支持基本的数学运算:加减乘除、括号、指数、常用数学函数。
    不支持变量赋值、循环、函数定义等。
    """
    # 安全验证:只允许安全的AST节点
    allowed_nodes = (
        ast.Expression,
        ast.BinOp,       # 二元运算:+, -, *, /
        ast.UnaryOp,     # 一元运算:-x, +x
        ast.Constant,    # 常量
        ast.Call,        # 函数调用
        ast.Name,        # 变量名(用于math函数)
        ast.Load,
        ast.Add, ast.Sub, ast.Mult, ast.Div, ast.Pow, ast.Mod,
        ast.USub, ast.UAdd,
    )

    allowed_functions = {
        "abs": abs, "round": round, "min": min, "max": max,
        "sqrt": math.sqrt, "sin": math.sin, "cos": math.cos,
        "tan": math.tan, "log": math.log, "log10": math.log10,
        "exp": math.exp, "floor": math.floor, "ceil": math.ceil,
    }

    try:
        # 解析表达式
        tree = ast.parse(expression, mode='eval')

        # 验证AST节点
        for node in ast.walk(tree):
            if not isinstance(node, allowed_nodes):
                return f"表达式包含不允许的操作:{type(node).__name__}。只支持基本的数学运算。"
            if isinstance(node, ast.Call):
                if isinstance(node.func, ast.Name):
                    if node.func.id not in allowed_functions:
                        return f"不允许的函数:{node.func.id}。允许的函数:{', '.join(allowed_functions.keys())}"
                else:
                    return "不支持复杂的函数调用。"
            if isinstance(node, ast.Name):
                if node.id not in allowed_functions:
                    return f"不允许的变量:{node.id}。只支持数学函数。"

        # 在受限环境中执行
        safe_globals = {"__builtins__": {}}
        safe_globals.update(allowed_functions)

        result = eval(compile(tree, '<expression>', 'eval'), safe_globals, {})

        # 格式化结果
        if isinstance(result, float):
            return f"{result:.10g}"
        return str(result)

    except SyntaxError as e:
        return f"表达式语法错误:{e}"
    except ZeroDivisionError:
        return "除零错误。"
    except Exception as e:
        return f"计算出错:{type(e).__name__}: {e}"


@registry.tool(
    description=(
        "安全地计算数学表达式。"
        "当用户需要进行数学计算时使用此工具。"
        "参数expression是数学表达式,如'(3+5)*2'、'sqrt(16)'、'2**10'。"
        "支持基本运算(+,-,*,/,**,%)、括号和常用数学函数(sqrt, sin, cos, abs, round等)。"
        "不支持变量赋值、循环、文件操作。"
    ),
    category=ToolCategory.EXECUTE,
    permission=ToolPermission.AUTO,
    timeout_seconds=5,   # 计算应该很快
    max_retries=0,       # 确定性操作,重试无意义
    tags=["calculation", "math", "execute"],
    metadata={"idempotent": True},  # 确定性操作,天然幂等
    examples=[
        "execute_calculation(expression='(3+5)*2')",
        "execute_calculation(expression='sqrt(144)')",
    ],
)
def execute_calculation_tool(expression: str) -> str:
    """执行安全计算"""
    return execute_calculation(expression)

值得注意的设计:

第一:基于AST的安全验证。 这是最安全的方式——不依赖字符串黑名单,而是解析代码结构,只允许白名单中的节点类型。任何不在白名单中的操作(如导入模块、变量赋值)都会被拒绝。

第二:受限的执行环境。 eval的globals只包含允许的函数,__builtins__被设为空字典。即使AST验证有漏洞,执行环境本身也是一道防线。

第三:确定性操作不需要重试。 计算结果不会因为重试而改变。如果执行失败(如表达式语法错误),重试也不会修正问题。

整合:完整的工具注册

将5个工具注册到同一个ToolRegistry中:

# examples/office_agent_tools.py

from harness.tools import ToolRegistry, ToolCategory, ToolPermission

def create_office_tool_registry() -> ToolRegistry:
    """创建办公助手的工具注册表"""
    registry = ToolRegistry(
        default_timeout=10,
        default_max_memory=256,
        default_max_output=5000,
    )

    # 注册5个工具
    # (使用上面定义的装饰器函数)
    registry.register(ToolDefinition(
        name="query_calendar",
        description="查询指定时间范围内的日历事件。当用户询问日程安排时使用。",
        func=query_calendar,
        parameters={
            "type": "object",
            "properties": {
                "start_date": {
                    "type": "string",
                    "description": "开始日期。YYYY-MM-DD格式或相对日期(today/tomorrow/next week)。"
                },
                "end_date": {
                    "type": "string",
                    "description": "结束日期。YYYY-MM-DD格式或相对日期。"
                },
                "calendar_id": {
                    "type": "string",
                    "description": "日历ID,默认为'primary'。通常不需要修改。"
                }
            },
            "required": ["start_date", "end_date"]
        },
        category=ToolCategory.READ,
        permission=ToolPermission.AUTO,
        timeout_seconds=10,
        max_retries=3,
        tags=["calendar", "schedule"],
    ))

    # ... 注册其他4个工具 ...

    return registry

测试这5个工具

创建一个测试Agent来验证工具集:

# examples/test_office_agent.py

def test_office_agent():
    """测试办公助手Agent"""
    registry = create_office_tool_registry()

    config = HarnessConfig(
        llm=LLMConfig(
            api_key=os.getenv("OPENAI_API_KEY", ""),
            model="gpt-4o-mini",
            temperature=0.0,
        ),
        runtime=RuntimeConfig(
            max_iterations=15,
            timeout_seconds=120,
            retry_attempts=2,
        )
    )

    memory = MemorySystem(
        system_prompt=(
            "你是一个智能办公助手。你可以帮助用户:\n"
            "1. 查询日历和日程安排\n"
            "2. 向团队成员发送消息\n"
            "3. 创建和管理任务\n"
            "4. 搜索文档\n"
            "5. 执行数学计算\n\n"
            "在使用工具前,确保你理解了用户的需求。"
            "如果信息不足,先询问用户。"
        )
    )

    tracer = Tracer()
    runtime = AgentRuntime(
        config=config,
        tool_registry=registry,
        memory=memory,
        tracer=tracer,
    )

    # 测试场景
    test_inputs = [
        "帮我查一下下周的日程安排",
        "计算 (15 + 7) * 3 的结果",
        "搜索包含'产品'的文档",
        "创建一个任务:周五前完成项目报告,优先级高",
    ]

    for user_input in test_inputs:
        print(f"\n{'='*50}")
        print(f"用户:{user_input}")
        result = runtime.run(user_input)
        print(f"状态:{result.status}")
        print(f"回答:{result.content}")
        print(f"迭代:{result.iterations},耗时:{result.duration_ms}ms")

观察:Agent如何组合使用多个工具

运行上面的测试,观察Agent如何处理需要多个工具协作的请求。例如:

用户:"查一下我下周三的日程,如果有空的话帮我创建一个准备演示文稿的任务。"

Agent的行为可能是:

1. 调用query_calendar(start_date='next week', end_date='next week')
   → 返回:下周三有"产品评审会"(10:00-11:30)

2. 判断:下周三上午有会,下午空闲

3. 调用create_task(
       title='准备演示文稿',
       due_date='2026-09-09',  # 下周三
       priority='high'
   )
   → 返回:任务已创建

4. 总结回答:"下周三上午10点有产品评审会,下午有空。"
   "我已帮你创建了一个'准备演示文稿'的高优先级任务,截止日期为下周三。"

这个例子展示了工具之间的协作:一个工具的输出(日历查询)影响了另一个工具的调用决策(是否创建任务、截止日期怎么定)。这正是Agent相对于传统程序的核心优势——动态决策、灵活组合。

从这5个工具中总结的设计模式

回顾这5个工具,可以提炼出几个可复用的设计模式:

模式一:验证优先。 每个工具在执行核心逻辑之前,都先验证输入的合理性。验证失败时返回清晰的错误信息,让LLM能够理解并修正。

模式二:无结果也是结果。 查询类工具在"无结果"时返回有意义的信息("未找到..."+"建议..."),而非空字符串。这让LLM能够基于"无结果"这一事实做出合理的后续决策。

模式三:副作用明确。 有副作用的工具(send_message)明确标记了权限级别和非幂等特性。无副作用的工具(query_calendar、search_documents)可以安全地自动执行和重试。

模式四:安全执行。 execute_calculation展示了如何安全地执行不可信代码——AST白名单验证+受限执行环境双层防御。

模式五:内部摘要。 可能返回大量数据的工具(search_documents)在工具内部做摘要,而非返回原始数据。这节省了上下文窗口,也让LLM更容易处理。

本节小结

本节构建了5个实用工具,覆盖了工具设计的核心模式:

工具关键设计模式
query_calendar灵活输入解析、范围验证
send_message用户确认、非幂等保护
create_task幂等设计、多维验证
search_documents内部摘要、无结果处理
execute_calculationAST安全验证、受限执行

这些模式的核心思想:工具不仅要"能用",还要"安全地用"和"被LLM正确地用"。 好的工具设计让LLM更容易做正确的事,更难做错误的事。

第4章到此结束。在下一章中,我们将讨论记忆与会话管理——如何让Agent记住之前的对话、跨会话保持信息、并在有限的上下文窗口中高效管理记忆。

本教程共32节,当前为第20节!
本教程最新修订时间为:2026-09-30 09:02:04

📌 面试天下网:一款服务于上班族的口袋书,让大家在地铁里/公交上可以学习大模型技术!
📌 网站公告:【悬赏 200 元/篇】寻找“大模型面试战场”的一手回忆录
📌 网站公告:【大模型/Agent面试陪练1V1指导】正式启动......

评论专区

评论加载中...