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

这是关于AI代理系列文章中的第二篇。在上一篇文章中,我们讨论了代理如何结合三个关键特性:LLMs、工具和推理。在这里,我们将讨论这样一个系统的最简单示例:LLM + 工具。我将首先从高层次概述这些系统的工作原理,然后分享使用OpenAI的Agents SDK构建一个系统的示例代码。
让计算机解决问题(通常)需要仔细地分解任务为不同的步骤,然后将这些步骤转化为计算机代码。当输入和工作流程可预测时,这种方法非常有效,但并非总是如此。
例如,一个客服机器人可能会捕获用户输入,将其与已知问题匹配,并返回预定义的解决方案。然而,正如你可能已经经历过的那样,这种基于规则的系统有很大的局限性。
问题是用户描述问题的方式不可预测,而且不可能一开始就预见所有的故障排除场景。但是有没有另一种方法呢?
1、AI代理
代理提供了一种新的软件思维方式。与其明确地定义规则和业务逻辑,不如给LLM提供它们解决问题所需的工具。
回到客服的例子,与其为所有情况定义一个单一的僵化工作流,你可以给LLM一些关于公司的上下文信息、支持文档的搜索工具以及升级问题的能力。
2、教LLM捕鱼
这让我想起了那句老话:“授人以鱼,不如授人以渔。”意思是如果你给了别人成功的工具,他们就可以独立生存——这是我理解代理的方式。
与其自己解决一个问题,然后用代码实现你的解决方案,你可以给LLM所需的指令和工具,让它自己找到各种问题的解决方案。
3、工具使用的机制
由于LLM只是简单的标记生成器,它们不能单独使用工具。这必须通过编写额外的软件来实现,以完成三个基本步骤。
- 监控LLM输出中的工具调用标记
- 暂停生成并用代码执行工具调用
- 捕获响应并将结果传递回LLM
虽然具体使用的标记和结构因模型和用途而异,这里有一个GPT风格的玩具示例。

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()"
以下是最终结果的演示。

6、结束语
AI 代理通过创建灵活的应用程序重新定义了软件的可能性,开发者无需显式定义其完整功能。在这里,我们看到仅仅给大型语言模型(LLM)访问工具的能力,就可以让它 动态地为任意用户查询提供解决方案。
虽然这种灵活性非常强大,但它 伴随着不可预测性,这可能导致糟糕的用户体验。此外,依赖单一的 LLM 可能在解决复杂任务时表现不足。
在本系列的下一篇文章中,我们将讨论 自主工作流 如何帮助我们创建 1) 更可靠的 AI 代理和 2) 能够结合多个 LLM 来处理更复杂任务的系统。
原文链接:How to Improve LLMs with Tools
汇智网翻译整理,转载请标明出处
