上一篇介绍了Agent与LangGraph的基础技能Tool的必知必会
AI菜鸟向前飞 — LangChain系列之十三 - 关于Tool的必知必会
前面已经详细介绍了Promp、RAG,终于来到Agent系列(别急后面还有LangGraph),大家可以先看下这张图: 看完我这系列就都懂了:)
牛刀初试
由于本篇是入门,我们直接边看程序边熟悉整个过程吧 先以BaseTool的方式实现一个Tool,代码如下:
class search_article(BaseTool):
name = "search_article"
description = "查询所有的文章来源"
def _run(self, topic: str):
return chain_rag.invoke({"question": topic})
关于chain_rag的内容,请参考我的这篇公众号文章
LangChain实战技巧之二:RunnablePassthrough.assign的两则妙用
我们看看两种Agent的“书写”方式
-
第一种
agent = (
RunnablePassthrough.assign(agent_scratchpad=lambda x: format_to_tool_messages(x["intermediate_steps"])
)
| hub.pull("hwchase17/openai-tools-agent")
| model.bind_tools(tools=[search_article()])
| ToolsAgentOutputParser()
)
-
第二种
agent = create_tool_calling_agent(prompt=hub.pull("hwchase17/openai-tools-agent"), llm=model, tools=[search_article()])
你喜欢哪种呢?
接下来你是不是想执行看看效果,结果会让你大跌眼镜、你没有眼镜的话配一个 先~
res = agent.invoke({"input": "AI菜鸟向前飞系列文章出自哪里?", "intermediate_steps": []})
这里为什么要加这个"intermediate_steps",不加会报错,不信你试试,若要知道这个机制请看下篇:)
输出结果
# 输出了这样一坨
[ToolAgentAction(tool='search_article', tool_input={'topic': 'AI菜鸟向前飞'}, log="\nInvoking: `search_article` with `{'topic': 'AI菜鸟向前飞'}`\n\n\n", message_log=[AIMessage(content='', additional_kwargs={'documents': None, 'citations': None, 'search_results': None, 'search_queries': None, 'is_search_required': None, 'generation_id': 'd12bfa56-e394-48a0-bff1-97f76dabe92f', 'tool_calls': [{'id': '5617aa7b3ed74d1789befac4b6a9d573', 'function': {'name': 'search_article', 'arguments': '{"topic": "AI\\u83dc\\u9e1f\\u5411\\u524d\\u98de"}'}, 'type': 'function'}], 'token_count': {'output_tokens': 12}}, response_metadata={'documents': None, 'citations': None, 'search_results': None, 'search_queries': None, 'is_search_required': None, 'generation_id': 'd12bfa56-e394-48a0-bff1-97f76dabe92f', 'tool_calls': [{'id': '5617aa7b3ed74d1789befac4b6a9d573', 'function': {'name': 'search_article', 'arguments': '{"topic": "AI\\u83dc\\u9e1f\\u5411\\u524d\\u98de"}'}, 'type': 'function'}], 'token_count': {'output_tokens': 12}}, id='run-592cded4-3fb7-48d3-99d7-87166d7bb232-0', tool_calls=[{'name': 'search_article', 'args': {'topic': 'AI菜鸟向前飞'}, 'id': '092624d480bd461daacc54fdda64c7b5'}])], tool_call_id='092624d480bd461daacc54fdda64c7b5')]
直截了当看结果
通常的教法,应该是你需要引入AgentExecutor,而如果我也是这样跟大家介绍的话,就不是我这个系列的风格了:)
如果用AgentExecutor的话,代码如下:
agent_executor = AgentExecutor(agent=agent, tools=[search_article()], verbose=True)
# 然后再invoke就能得到你想要的
agent_executor.invoke("AI菜鸟向前飞系列文章出自哪里?")
直截了当
{'input': 'AI菜鸟向前飞系列文章出自哪里?', 'output': 'AI菜鸟向前飞系列文章出自Song榆钱儿的公众号。'}
(换个方式)直奔Agent
但是跑题了,我们主要讲的是Agent,而不是AgentExecutor,也就是说如果仅用Agent呢?
看到输出内容
[ToolAgentAction(tool='search_article', tool_input={'topic': 'AI菜鸟向前飞'}, ……
聪明的小伙伴可以用Python去解决问题,如下:
# 为啥这里要用这个,因为它是列表啊,为啥是列表,以后再介绍
for each in res:
result = {"search_article": search_article()}[each.tool].invoke(each.tool_input)
print(result)
最后它确实被执行,结果如下:
content='AI菜鸟向前飞系列文章是出自公众号"Song榆钱儿"的原创作品。截至目前,该系列已经有20多篇原创文章,并且拥有109名关注者。' additional_kwargs={'documents': None, 'citations': None, 'search_results': None, 'search_queries': None, 'is_search_required': None, 'generation_id': 'f12940a2-38de-4bba-a7b0-254f40c90596', 'token_count': {'input_tokens': 156, 'output_tokens': 44}} response_metadata={'documents': None, 'citations': None, 'search_results': None, 'search_queries': None, 'is_search_required': None, 'generation_id': 'f12940a2-38de-4bba-a7b0-254f40c90596', 'token_count': {'input_tokens': 156, 'output_tokens': 44}} id='run-f7cc290b-34f8-4eca-bcfa-85c3bcf2b74e-0'
也就是都是RAG的“功劳”,上面代码提到的:
………………
return chain_rag.invoke({"question": topic})
柳暗花明
各位看官会想,这跟我单独执行Tool函数有啥区别,绕了一个大圈子,这秘密就存在于"intermediate_steps"当中(卖个关子,后面介绍)先看如下代码:
intermediate_steps = []
while not isinstance(
res := agent.invoke({"input": "AI菜鸟向前飞系列文章出自哪里?", "intermediate_steps": intermediate_steps}), AgentFinish):
for each in res:
func_ret = {"search_article": search_article()}[each.tool].invoke(each.tool_input)
intermediate_steps.append((each, func_ret.content))
是不是感觉跟用AgentExecutor一样了:)
简述ReAct
最后分享一个聊到Agent 大部分博文都会提到的ReAct (Reason Act),以一个示例来演示下吧,各处重要内容,我都加上了注释来为大家解释:)
> Entering new AgentExecutor chain...
# 思考
Thought: I can answer this question by searching for the source of the "AI菜鸟向前飞" series.
# 行动
Action: search_article
Action Input: AI菜鸟向前飞
# 观察
Observation content='AI菜鸟向前飞系列文章是出自Song榆钱儿的公众号。该系列文章目前已经有20多篇原创文章,并且已有109人关注。' additional_kwargs={'documents': None, 'citations': None, 'search_results': None, 'search_queries': None, 'is_search_required': None, 'generation_id': 'e07bf920-967a-4269-bae0-6acd4358a90f', 'token_count': {'input_tokens': 158, 'output_tokens': 38}} response_metadata={'documents': None, 'citations': None, 'search_results': None, 'search_queries': None, 'is_search_required': None, 'generation_id': 'e07bf920-967a-4269-bae0-6acd4358a90f', 'token_count': {'input_tokens': 158, 'output_tokens': 38}} id='run-efd02271-a86f-4dee-bd0e-0e9133efd7b7-0'
# 找到正解
Final Answer: AI菜鸟向前飞系列文章出自Song榆钱儿的公众号。该系列文章目前已经有20多篇原创文章,并且已有109人关注。
> Finished chain.
{'input': 'AI菜鸟向前飞系列文章出自哪里?', 'output': 'AI菜鸟向前飞系列文章出自Song榆钱儿的公众号。该系列文章目前已经有20多篇原创文章,并且已有109人关注。'}
原理过程图:关于这张图,后面若大家有需要我再详细深入讲解
敬请期待下一篇:)