如何用工具提升大模型

在这里,我们将讨论这样一个系统的最简单示例:LLM + 工具。我将首先从高层次概述这些系统的工作原理,然后分享使用OpenAI的Agents SDK构建一个系统的示例代码。

如何用工具提升大模型

这是关于AI代理系列文章中的第二篇。在上一篇文章中,我们讨论了代理如何结合三个关键特性:LLMs、工具和推理。在这里,我们将讨论这样一个系统的最简单示例:LLM + 工具。我将首先从高层次概述这些系统的工作原理,然后分享使用OpenAI的Agents SDK构建一个系统的示例代码。

让计算机解决问题(通常)需要仔细地分解任务为不同的步骤,然后将这些步骤转化为计算机代码。当输入和工作流程可预测时,这种方法非常有效,但并非总是如此。

例如,一个客服机器人可能会捕获用户输入,将其与已知问题匹配,并返回预定义的解决方案。然而,正如你可能已经经历过的那样,这种基于规则的系统有很大的局限性

问题是用户描述问题的方式不可预测,而且不可能一开始就预见所有的故障排除场景。但是有没有另一种方法呢?

1、AI代理

代理提供了一种新的软件思维方式。与其明确地定义规则和业务逻辑,不如给LLM提供它们解决问题所需的工具

回到客服的例子,与其为所有情况定义一个单一的僵化工作流,你可以给LLM一些关于公司的上下文信息、支持文档的搜索工具以及升级问题的能力。

2、教LLM捕鱼

这让我想起了那句老话:“授人以鱼,不如授人以渔。”意思是如果你给了别人成功的工具,他们就可以独立生存——这是我理解代理的方式。

与其自己解决一个问题,然后用代码实现你的解决方案,你可以给LLM所需的指令和工具,让它自己找到各种问题的解决方案

3、工具使用的机制

由于LLM只是简单的标记生成器,它们不能单独使用工具。这必须通过编写额外的软件来实现,以完成三个基本步骤

  1. 监控LLM输出中的工具调用标记
  2. 暂停生成并用代码执行工具调用
  3. 捕获响应并将结果传递回LLM

虽然具体使用的标记和结构因模型和用途而异,这里有一个GPT风格的玩具示例。

LLM进行工具调用的原始字符序列示例

4、两种使用工具的方式

通过代码执行工具调用足够简单,但如何让不可预测的LLM发起这些调用呢? 实际上有两种方法可以做到这一点。

方法1:提示

让LLM发起工具调用的最简单方法是在提示中指导它。关键是要包括函数描述、输入、输出以及特殊字符串,以便通过基于代码的检查轻松识别函数调用。

虽然你可以用经过指令微调的模型和一些巧妙的提示来实现这一点,但使用专门微调以发起工具调用的模型更为可靠。如果你通过API与这样的模型交互,这甚至更简单,因为大多数API会为你处理提示。你只需要提供你想使用的工具的JSON模式(如下所示)。

{
  "type": "function",
  "function": {
    "name": "transcribe_youtube_video",
    "description": "将YouTube视频的音频转录为文本。",
    "parameters": {
      "type": "object",
      "properties": {
        "video_url": {
          "type": "string",
          "description": "要转录的YouTube视频的完整URL。"
        }
      },
      "required": ["video_url"],
      "additionalProperties": false
    },
    "strict": true
  }
}

方法2:微调

尽管仅仅通过提示模型对许多用例来说已经足够好,但如果LLM被赋予需要以复杂方式使用的专用工具,这种方法可能会失效。

这就像是大多数人可以很快学会如何在一个新网站上使用搜索引擎,但却不容易掌握如何(以及何时)使用AED一样。LLM可能需要在特定领域训练如何使用一套工具。

5、示例:使用Agents SDK构建YouTube视频代理

几个月前,我发布了一个AI应用,用于将YouTube视频转换为博客文章。虽然它使用了LLM,但它采用的是传统软件思维(即解构博客写作过程为离散步骤,其中LLM处理其中一个步骤)。

采用这种方式意味着如果我想让系统总结视频或从中提取时间戳,我需要手动创建新的工作流。然而,如果我将其构建为一个代理,这些功能(以及其他我没有预料到的功能)将自动可用。

在这里,我将使用OpenAI的Agents SDK来构建这个系统。该系统将为LLM提供一个YouTube转录工具,它可以用它来撰写博客、回答问题、生成时间戳等。

代码可以在这个GitHub仓库中找到。

5.1 导入

我们将首先导入一些有用的Python库。由于我将使用OpenAI的API进行模型推理,我将使用dotenv库从*.env*文件中导入我的密钥。

from youtube_transcript_api import YouTubeTranscriptApi  
import re  
from agents import Agent, function_tool, Runner, ItemHelpers, RunContextWrapper  
from openai.types.responses import ResponseTextDeltaEvent  
from dotenv import load_dotenv  
import asyncio  
  
# 从.env文件中导入环境变量  
load_dotenv()

5.2 定义指令和工具

OpenAI将代理定义为配备指令和工具的LLM[1]。让我们来创建它们。

# 定义指令  
instructions = "你提供与YouTube视频相关的帮助。"
# 定义工具  
@function_tool  
def fetch_youtube_transcript(url: str) -> str:  
    """  
    从YouTube视频URL提取带时间戳的字幕,并格式化以供LLM消费  
      
    参数:  
        url (str): 要转录的YouTube视频的完整URL  
          
    返回:  
        str: 格式化的带时间戳的字幕,每个条目都在一行上,格式为:"[MM:SS] 文本"  
    """  
    # 从URL中提取视频ID  
    video_id_pattern = r'(?:v=|\/)([0-9A-Za-z_-]{11}).*'  
    video_id_match = re.search(video_id_pattern, url)  
      
    if not video_id_match:  
        raise ValueError("无效的YouTube URL")  
      
    video_id = video_id_match.group(1)  
      
    try:  
        transcript = YouTubeTranscriptApi.get_transcript(video_id)  
          
        # 格式化每个条目为时间戳和文本  
        formatted_entries = []  
        for entry in transcript:  
            # 将秒数转换为MM:SS格式  
            minutes = int(entry['start'] // 60)  
            seconds = int(entry['start'] % 60)  
            timestamp = f"[{minutes:02d}:{seconds:02d}]"  
              
            formatted_entry = f"{timestamp} {entry['text']}"  
            formatted_entries.append(formatted_entry)  
          
        # 使用换行符连接所有条目  
        return "\n".join(formatted_entries)  
      
    except Exception as e:  
        raise Exception(f"获取字幕时出错: {str(e)}")

Agents SDK的一个很棒的功能是,你可以通过在任何Python函数上添加*@function_tool*装饰器轻松地将其转换为LLM工具。这会自动解析函数结构和文档字符串,以创建通过OpenAI API执行工具调用所需的JSON结构。

此外,Agents SDK为我们处理了函数调用,所以我们不需要自己编写代码来解析LLM输出并执行函数调用!

5.3 创建代理

Agents SDK有一个方便的原语叫做Agent(),它使得启动一个新的代理变得非常简单。以下是它的样子。

# 定义代理  
agent = Agent(  
    name="YouTube字幕代理",  
    instructions=instructions,  
    tools=[fetch_youtube_transcript],  
)

5.4 运行代理

到目前为止,编码代理非常简单。然而,由于系统是异步的(即不遵循预定义的步骤序列),实现UI并不那么简单。

两个特性带来了复杂性。首先,我们希望在标记可用时实时流式传输模型的标记,使其感觉像实时的。其次,函数调用需要处理。

...所有这些可能需要一些时间,在此期间我们可能希望允许应用程序执行其他操作(例如,UI 更新、用户中断等)。

在这里,我将通过在命令行中实现聊天系统来保持事情相对简单。

# 定义主聊天函数  
async def main():  
    input_items = []  
  
    print("=== YouTube 字幕代理 ===")  
    print("输入 'exit' 结束对话")  
    print("问我任何关于 YouTube 视频的问题!")  
  
    while True:  
        # 获取用户输入  
        user_input = input("\n你: ").strip()  
        input_items.append({"content": user_input, "role": "user"})  
          
        # 检查退出命令  
        if user_input.lower() in ['exit', 'quit', 'bye']:  
            print("\n再见!")  
            break  
              
        if not user_input:  
            continue  
  
        print("\n代理: ", end="", flush=True)  
        result = Runner.run_streamed(  
            agent,  
            input=input_items,  
        )  
  
        async for event in result.stream_events():  
            # 我们将忽略原始响应事件增量  
            if event.type == "raw_response_event" and isinstance(event.data, ResponseTextDeltaEvent):  
                print(event.data.delta, end="", flush=True)  
            elif event.type == "agent_updated_stream_event":  
                continue  
            elif event.type == "run_item_stream_event":  
                if event.item.type == "tool_call_item":  
                    print("\n-- 获取字幕...")  
                elif event.item.type == "tool_call_output_item":  
                    input_items.append({"content": f"字幕:\n{event.item.output}", "role": "system"})  
                    print("-- 字幕获取完成。")  
                elif event.item.type == "message_output_item":  
                    input_items.append({"content": f"{event.item.raw_item}", "role": "assistant"})  
                else:  
                    pass  # 忽略其他事件类型  
  
        print("\n")  # 在每次响应后添加一个换行符  
  
# 运行主程序  
if __name__ == "__main__":  
    asyncio.run(main())  
  
# 注意:如果在 Jupyter Lab 中运行,请直接运行 "await main()"

以下是最终结果的演示。

命令行 YouTube 代理演示

6、结束语

AI 代理通过创建灵活的应用程序重新定义了软件的可能性,开发者无需显式定义其完整功能。在这里,我们看到仅仅给大型语言模型(LLM)访问工具的能力,就可以让它 动态地为任意用户查询提供解决方案

虽然这种灵活性非常强大,但它 伴随着不可预测性,这可能导致糟糕的用户体验。此外,依赖单一的 LLM 可能在解决复杂任务时表现不足。

在本系列的下一篇文章中,我们将讨论 自主工作流 如何帮助我们创建 1) 更可靠的 AI 代理和 2) 能够结合多个 LLM 来处理更复杂任务的系统。


原文链接:How to Improve LLMs with Tools

汇智网翻译整理,转载请标明出处