Skip to content

工具

工具(BaseTool)是 Robot 在执行阶段被 Drive 调度的最小执行单元。它封装一个可被 LLM 调用的能力 —— 从 git diff、网页抓取、向量召回到 IDE 集成皆是。本页讲基础封装范式(含 yield / return 混用);上下文相关的两条进阶能力另成独立章节。

上下文能力总览

工具有两条独立的「上下文」通道,按需选用、可叠加:

能力 解决什么问题 入口字段 详细文档
运行时上下文注入 工具想读取/修改 Chain 当前的 intermediateneuralcurrent_tool_call_id merge_context: bool + _run**kwargs 运行时上下文注入
Prompt 段位注入 工具想向 LLM 的 prompt 流中加入一段背景信息(如长期记忆、当前环境快照) prompt_position: tuple[...] + 覆写 _format_2_prompt() Prompt 段位注入

二者职责不重叠:前者是「Chain → Tool」的运行时数据流;后者是「Tool → LLM」的 prompt 装配。一个工具完全可以同时用两条路径,例如「_run 内 prefetch 命中结果 → _format_2_prompt 在下次 LLM 调用时把结果注入到 system prompt」。

基础工具

注意事项

在工具的当前支持中,我们可以采用迭代器/生成器的方式输出,例如使用yield语句。此外,我们的工具现在支持在一个函数中同时使用yieldreturn语句。不同于传统的生成器,您不再需要捕获StopIteration异常来获取return语句的值,可以直接在循环中处理所有结果。

但需要注意这种yield与return混用的模式权支持在同步工具中使用,异步工具中不支持。因为目前Python对于Async模式下的生成器,还有没有特别好的办法直接获取最终的return值。

但无论是同步还是异步工具,我们都建议您使用for循环来获取所有生成器的结果,而不是使用next函数。也建议在封装工具时返回数据全部使用yield语句,可以配合使用return结束迭代,但不要使用return返回数据。

示例:非Async生成器工具

def simple_generator():
    yield "yielded value 1"
    yield "yielded value 2"
    return "returned value"


def test_simple_generator() -> None:
    gen = simple_generator()
    try:
        while True:
            print(next(gen))
    except StopIteration as e:
        print("Generator return value:", e.value)


# 输出:
# yielded value 1
# yielded value 2
# Generator return value: returned value

在使用TFRobotBaseTool基类时,您可以更简单地通过for循环来获取所有生成器的结果。

示例:Git Diff 工具封装

import subprocess
from pathlib import Path
from typing import ClassVar, Iterator, Optional

import cachetools

from tfrobot.drive.tool.base import BaseTool
from tfrobot.schema.drive.tool.base import MessageCategory, MessageMeta, ToolReturn
from tfrobot.schema.meta.tf_field import TFField


class GitDiffTool(BaseTool):
    """
    GitDiff Tool
    此工具用于查看项目的Git Diff信息,并支持通过配置的excludes参数排除不需差异对比的文件。
    """

    name: ClassVar[str] = "git_diff"
    version: ClassVar[str] = "0.0.1"
    description: ClassVar[str] = "查看项目的Git Diff信息,支持文件排除配置。"
    cache: cachetools.TTLCache = cachetools.TTLCache(maxsize=100, ttl=300)
    project_root: Path
    excludes: list[str] = TFField(default_factory=list)

    def _run(self, tool_params: Optional[dict] = None, **kwargs) -> Iterator[ToolReturn] | ToolReturn:
        # 获取所有变化的文件列表
        if not self.cache.get("all_files"):
            cmd = ["git", "diff", "--name-only"] + (self.excludes if self.excludes else [])
            changed_files_result = subprocess.run(cmd, stdout=subprocess.PIPE, text=True, cwd=str(self.project_root))
            self.cache["all_files"] = changed_files_result.stdout.splitlines()

        changed_files = self.cache["all_files"]
        if not changed_files:
            raise ValueError("当前没有文件变化")

        # 逐个显示文件的变化
        for file in changed_files:
            diff_result = subprocess.run(
                ["git", "diff", file], stdout=subprocess.PIPE, text=True, cwd=str(self.project_root)
            )
            res = diff_result.stdout
            yield ToolReturn(
                origin=res,
                msg_meta=MessageMeta(category=MessageCategory.Map, summary=f"{res[:30]}...已省略...{res[-30:]}"),
            )

        return ToolReturn(
            origin="你已经查看完所有的变更文件,请根据目前的信息再次整合所有的变更,形成一个中英双语的Commit Message",
            msg_meta=MessageMeta(category=MessageCategory.Map, summary=""),
        )

    async def _async_run(self, tool_params: Optional[dict] = None, **kwargs) -> Iterator[ToolReturn]:
        return self._run(tool_params, **kwargs)


def test_git_diff_tool() -> None:
    """
    测试封装的Git Diff Tool可以正常运行
    """
    git_diff_tool = GitDiffTool(
        project_root=Path("project_root"), excludes=[":(exclude)poetry.lock", ":(exclude)*.png", ":(exclude)*.svg"]
    )
    for r in git_diff_tool.run():
        print(r.origin)

在以上封装中,您仅需使用for循环即可获取所有结果,包括return语句返回的最终ToolReturn对象。