掘金 人工智能 06月30日 10:44
从零开始搭建一个属于自己的Agent
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

本文详细介绍了如何从零开始构建一个基于ReAct范式的轻量级大模型Agent。文章首先构建了谷歌搜索和代码检查工具,随后搭建了Agent的ReAct流程,包括思考、行动、观察和总结四个环节。通过一个代码修复的实战案例,展示了Agent的工作流程。最后,文章总结了Agent的优势与不足,并展望了未来的优化方向,例如多轮交互和更完善的工具。

🛠️ 构建工具库:Agent依赖外部工具执行任务,文中构建了谷歌搜索和代码检查两个工具。工具定义包括人类可读名称、模型调用标识、功能描述和参数规范。

🔁 搭建ReAct流程:ReAct范式包含“思考-行动-观察-总结”四个环节。文章详细介绍了Agent的提示词设置,以及Agent的决策和执行流程。其中,决策阶段模型解析用户问题并生成工具调用指令;执行阶段解析模型输出并调用工具,总结输出结果。

🧪 实战测试:通过一个修复Python代码错误的案例,展示了Agent的实际运行效果。Agent调用代码检查工具,发现并修复了代码中的语法错误。

从零开始搭建一个属于自己的Agent

今天,我们将系统性地从零构建一个定制化Agent。接续上期对Agent原理的探讨,本节以ReAct范式为基础(相关背景请参阅往期文章),逐步实现一个轻量级大模型Agent的完整工作流。

🛠️ 第一步:构建工具库

ReAct 范式中,Agent 依赖外部工具执行任务。以下以 tools.py 中的实现为例。其中包含了谷歌搜索的工具函数。

{    'name_for_human': '谷歌搜索',    'name_for_model': 'google_search',    'description_for_model': '谷歌搜索是一个通用搜索引擎,可用于访问互联网、查询百科知识、了解时事新闻等。',    'parameters': [        {            'name': 'search_query',            'description': '搜索关键词或短语',            'required': True,            'schema': {'type': 'string'},        }    ],}

工具定义需明确四个核心要素:

    人类可读名称(name_for_human)模型调用标识(name_for_model)功能描述(description_for_model)参数规范(parameters)

谷歌搜索的调用函数实现如下,基于Serper API(新注册赠送2500次免费调用):

def google_search(search_query: str):    url = "https://google.serper.dev/search"    payload = json.dumps({"q": search_query})    headers = {        'X-API-KEY': 'xxxxxx',        'Content-Type': 'application/json'    }    response = requests.request("POST", url, headers=headers, data=payload).json()    return response['organic'][0]['snippet']

这样,一个工具就完成了。接下来,为了契合代码智能的主题,我们再增加一个代码编译检测的工具。

首先代码检查函数定义如下:

{    'name_for_human': '代码检查',    'name_for_model': 'code_check',    'description_for_model': '代码检查是一个代码检查工具,可用于检查代码的错误和问题。',    'parameters': [        {            'name': 'language',            'description': '语言类型全称',            'required': True,            'schema': {'type': 'string'},        },        {            'name': 'source_code',            'description': '源代码',            'required': True,            'schema': {'type': 'string'},        }    ]}

代码检查函数涉及到两个参数,分别是语言类型和源代码。代码检查函数 check_code 的实现大家可以在仓库中 tree_sitter_parser.py 中找到。

这样,两个工具就完成了。接下来,我们开始构造Agent流程。

🔁 第二步:搭建ReAct流程

ReAct范式通过迭代执行“思考-行动-观察-总结”四个环节完成任务:

    思考(Thought):Agent分析上下文与任务目标行动(Action):Agent决策并调用工具观察(Observation):Agent解析工具返回结果总结(Final Answer):Agent输出最终结论

根据上述的流程,项目中ReAct对应的提示词如下, 在 agent.py 中:

Answer the following questions as best you can. You have access to the following tools:

google_search: Call this tool to interact with the 谷歌搜索 API. What is the 谷歌搜索 API useful for? 谷歌搜索是一个通用搜索引擎,可用于访问互联网、查询百科知识、了解时事新闻等。 Parameters: [{'name': 'search_query', 'description': '搜索关键词或短语', 'required': True, 'schema': {'type': 'string'}}] Format the arguments as a JSON object.

code_check: Call this tool to interact with the 代码检查 API. What is the 代码检查 API useful for? 代码检查是一个代码检查工具,可用于检查代码的错误和问题。 Parameters: [{'name': 'language', 'description': '语言类型全称', 'required': True, 'schema': {'type': 'string'}}, {'name': 'source_code', 'description': '源代码', 'required': True, 'schema': {'type': 'string'}}] Format the arguments as a JSON object.

Use the following format:

Question: the input question you must answerThought: you should always think about what to doAction: the action to take, should be one of [{tool_names}]Action Input: the input to the actionObservation: the result of the action... (this Thought/Action/Action Input/Observation can be repeated zero or more times)Thought: I now know the final answerFinal Answer: the final answer to the original input question

Begin!

提示词分为两个部分,首先想大模型叙述了可支配其调用的工具,包括了谷歌搜索和代码检查。

然后,提示词中描述了ReAct的流程,包括了思考、行动、观察和总结

在Agent流程的建立上,是两阶段进行的。

第一阶段:决策阶段:模型解析用户问题并生成工具调用指令:

第一个阶段会告诉大模型整体的流程和用户提出的问题,让大模型进行思考,并决定使用什么工具。

这段逻辑在 agent.py 中的 text_completion 函数的前三行中。

def text_completion(self, text, history=[]):    text = "\nQuestion:" + text    response, his = self.model.chat(text, history, self.system_prompt)    print("first response:\n")    print(response)    print("-"*100)

第二阶段:执行阶段:解析模型输出并调用工具,并总结输出结果:

然后,程序会读取大模型请求执行的方法,并执行。

# 解析大模型请求执行的方法def parse_latest_plugin_call(self, text):    plugin_name, plugin_args = '', ''    i = text.rfind('\nAction:')    j = text.rfind('\nAction Input:')    k = text.rfind('\nObservation:')    if 0 <= i < j:  # If the text has `Action` and `Action input`,        if k < j:  # but does not contain `Observation`,            text = text.rstrip() + '\nObservation:'  # Add it back.        k = text.rfind('\nObservation:')        plugin_name = text[i + len('\nAction:') : j].strip()        plugin_args = text[j + len('\nAction Input:') : k].strip()        text = text[:k]    return plugin_name, plugin_args, text# 执行大模型请求执行的方法def call_plugin(self, plugin_name, plugin_args):    plugin_args = json5.loads(plugin_args)    if plugin_name == 'google_search':        return '\nObservation:' + self.tool.google_search(**plugin_args)    elif plugin_name == 'code_check':        return '\nObservation:' + self.tool.code_check(**plugin_args)

可以从 parse_latest_plugin_call 看出,目前解析大模型的方法还是通过硬匹配的方式,如果大模型输出出现些许偏差,就会导致解析失败。

目前只支持了两个工具,如果增加更多的工具,也可能会导致模型输出异常,所以function calling的数量并不是越多越好的。

通过 call_plugin 函数得到结果后,我们继续进行ReAct的第二阶段,执行结果的总结

function_call_result = self.call_plugin(plugin_name, plugin_args)response += function_call_resultresponse, his = self.model.chat(response, his, self.system_prompt)

观察内容是我们自己去拼上去的,在二阶段总结过程中,大模型可以知道前面的思考、行动、观察内容,在这个基础上进行总结。

以上就是基于ReAct范式的Agent流程。

🧪 第三步:来场实战测试

最后我们在给一个简单的测试用例,看下Agent的执行效果吧。

以下测试用例演示Agent修复Python语法错误的能力:

def hello_world():print("Hello, World!")

def hello_world2():::::print("Hello, World2!")

请修复这段代码的错误。

这里我用了很简单的一个case,在hello_world2函数中,我故意写错了多个冒号,期待Agent可以修复这个错误。

第一阶段输出

Thought: 代码中存在语法错误,需要调用代码检查工具来修复。Action: code_checkAction Input: {"language": "Python", "source_code": "def hello_world():\n print("Hello, World!")\n\n def hello_world2():::::\n print("Hello, World2!")\n\n "}

大模型在思考后,决定调用代码检查工具来修复错误。给出了调用 code_check 方法,并给出了正确的参数。

工具执行结果

Observation:code compile error at (3, 23) to (3, 27), the error text is: "b'::::'"

通过TreeSitter可以拿到编译异常的代码位置和错误代码,因此我们将这些信息返回给大模型。

Agent最终输出

Thought: 观察到的错误信息表明在定义hello_world2函数时存在多余的冒号。现在知道了错误原因,可以直接给出修复后的代码。Final Answer: 修复后的代码如下:def hello_world():print("Hello, World!")

def hello_world2():print("Hello, World2!")

在原始代码中,def hello_world2()::::: 这里多了几个冒号,将其修正为 def hello_world2(): 即可。

在工具帮助下,Agent成功修复了代码中的错误。

当然这个示例太简单了,即使没有工具,大模型应该也能输出一段修复后的代码来。

先通过这个简单的例子,来了解Agent的运行流程

💡 总结与展望

今天,我们通过一个简单的例子,了解了Agent的运行流程。 我们可以看到,Agent的工作流程可总结为:

    问题解析:模型分析任务并选择工具工具调用:执行外部工具获取结果结果整合:基于反馈生成最终输出

完整代码已开源至 TinyCodeBase

github.com/codemilesto…

欢迎大家来点个 star。

可以看到本次实现的Agent,还存在很多问题,比如:

    ReAct流程仅能执行一轮,无法进行和工具的多轮交互。代码检查工具能力有限,仅能检查初级的静态语法错误,对运行时错误无法检测。

后面我们会进一步优化 TinyCodeBase 项目,使其能力更加完善。

在继续强化能力之前,我们还缺少一个重要的环节,那就是大模型能力的评估

因此我们将基于TinyEval项目,学习大模型能力的评估方法,并应用到TinyCodeBase项目中,敬请期待。

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

Agent ReAct 大模型 工具
相关文章