欢迎来到Llama Index的第三节实战课,阅读本系列的其他课程请点击Llama Index案例实战,感谢关注作者或订阅专栏获得后续课程的推送。
1. 案例简介
1.1 模拟场景
你打算通过Llama Index开发一个智能聊天助手。当你初次进入与这个聊天助手对话时,它并不知晓你的名字。你可以通过自然语言指令,如 "我的名字是 [你的名字]",让助手记住你的名字。之后,你可以询问 "我的名字是什么",助手应能准确回复你的名字。在本次实战案例的教学中,我们将学习如何将名字存储在对话上下文中,以便后续查询使用。
1.2 核心功能及需求分析
- 名称的设定:用户能够通过自然语言输入,让助手记录自己的名字,满足用户个性化设置的需求。
- 名称的查询:助手能在用户询问时,准确从已记录的信息中查询并回复用户的名字,实现信息的有效反馈。
- 上下文管理:助手需要在多轮对话中,保存用户设定的名字这一关键信息,确保上下文的连贯性,为用户提供持续且准确的交互体验。
- 自然语言理解与处理:借助语言模型,助手能够理解用户输入的自然语言指令,判断是设定名字还是查询名字的操作,并做出相应的正确回应。
1.3 解决思路
AgentWorkflow 在整个系统中起着核心调度的作用。它连接了语言模型与外部工具,就像一个智能中枢。当用户输入自然语言指令,比如 "我的名字是凉拌三丝",语言模型首先对这条指令进行分析,判断这是一个名称设定的操作。然后,语言模型指示 AgentWorkflow 调用我们自定义的set_name工具来执行具体的名称设定任务。AgentWorkflow 执行工具调用后,将结果反馈给语言模型,语言模型再将结果以自然语言的形式呈现给用户,告知用户名称已成功设定。同样,当用户询问 "我的名字是什么" 时,AgentWorkflow 也会协调工具和语言模型,从上下文存储中获取并返回用户的名字。这种机制使得系统能够灵活处理不同类型的用户请求,通过组合工具来满足多样化的需求。
2. 关键知识点讲解
2.1 自定义工具集成
在这个案例中,自定义工具set_name是实现名称设定功能的关键。通过定义set_name函数,我们赋予了系统处理用户名称设定的能力。
python
async def set_name(ctx: Context, name: str) -> str:
state = await ctx.get("state")
state["name"] = name
await ctx.set("state", state)
return f"Name set to {name}"
这个函数接收两个参数,ctx是上下文对象,name是用户输入的名字。函数首先从上下文中获取当前的状态state,然后将用户输入的名字添加到状态中,再将更新后的状态重新保存回上下文。最后返回一条确认消息给用户。通过将这个自定义函数作为工具添加到 AgentWorkflow 中,系统便具备了处理名称设定的独特功能,体现了 LlamaIndex 工具集成的灵活性和扩展性,开发者可以根据实际业务需求,轻松添加各种自定义工具来丰富系统的功能。
2.2 上下文管理
上下文管理在本案例中至关重要,它确保了用户名称在多轮对话中的有效保存和使用。在代码中,通过Context对象来实现上下文管理。
ini
ctx = Context(workflow)
创建了一个与当前工作流workflow相关的上下文对象ctx。在set_name函数中,利用ctx对象的get和set方法来获取和设置上下文状态。
csharp
state = await ctx.get("state")
state["name"] = nameawait ctx.set("state", state)
当用户设定名字时,从上下文中获取当前状态state,将名字添加到状态中,再将更新后的状态保存回上下文。当用户查询名字时,也可以从上下文中获取存储的名字信息。这种上下文管理机制使得系统能够在多轮对话中记住用户的关键信息,为用户提供连贯的交互体验,是实现有记忆对话的重要基础。
3. 主要实现代码
python
from dotenv import load_dotenv
load_dotenv()
from llama_index.llms.openai import OpenAI
from llama_index.core.agent.workflow import AgentWorkflow
from llama_index.core.workflow import Context
llm = OpenAI(model="gpt-4o-mini")
async def set_name(ctx: Context, name: str) -> str:
state = await ctx.get("state")
state["name"] = name
await ctx.set("state", state)
return f"Name set to {name}"
workflow = AgentWorkflow.from_tools_or_functions(
[set_name],
llm=llm,
system_prompt="You are a helpful assistant that can set a name.",
initial_state={"name": "unset"}
)
async def main():
ctx = Context(workflow)
# check if it knows a name before setting it
response = await workflow.run(user_msg="What's my name?", ctx=ctx)
print(str(response))
# set the name using a tool
response2 = await workflow.run(user_msg="My name is sansi", ctx=ctx)
print(str(response2))
# retrieve the value from the state directly
state = await ctx.get("state")
print("Name as stored in state: ", state["name"])
if __name__ == "__main__":
import asyncio
asyncio.run(main())
4. 关键代码解析
4.1 自定义工具定义
python
async def set_name(ctx: Context, name: str) -> str:
state = await ctx.get("state")
state["name"] = name
await ctx.set("state", state)
return f"Name set to {name}"
此函数定义了名称设定的具体逻辑。它接收上下文对象ctx和用户输入的名字name。通过ctx.get("state")获取当前上下文状态,将名字添加到状态字典中,再通过ctx.set("state", state)将更新后的状态保存回上下文。最后返回名称设定成功的提示信息,实现了用户名称在上下文中的存储和反馈。
4.2 工作流初始化
ini
workflow = AgentWorkflow.from_tools_or_functions(
[set_name],
llm=llm,
system_prompt="You are a helpful assistant that can set a name.",
initial_state={"name": "unset"}
)
这行代码使用AgentWorkflow.from_tools_or_functions方法创建工作流对象workflow。[set_name]指定了可使用的工具列表,这里只有自定义的set_name工具。llm=llm指定使用之前创建的 OpenAI 模型实例。system_prompt是系统提示,引导语言模型明白自己是一个能够设定名字的助手。initial_state={"name": "unset"}设置了初始状态,其中名字初始值为 "unset",为后续名称设定和查询提供了初始条件。
4.3 上下文操作与对话执行
ini
ctx = Context(workflow)
# 查询名字
response = await workflow.run(
user_msg="What's my name?",
ctx=ctx
)
print(str(response))
# 设置名字
response2 = await workflow.run(
user_msg="My name is sansi",
ctx=ctx
)
print(str(response2))
# 直接从状态中获取名字
state = await ctx.get("state")
print("Name as stored in state: ", state["name"])
首先创建上下文对象ctx。然后通过workflow.run方法处理用户输入,当用户询问 "我的名字是什么" 时,工作流调用相应工具和语言模型,根据初始状态返回未设定名字的提示。当用户输入 "我的名字是sansi"时,工作流调用set_name工具设定名字,并返回设定成功的提示。最后通过ctx.get("state")直接从上下文中获取存储的名字并打印,展示了上下文管理和对话执行的过程。
4.4 异步处理
python
async def main():
# 异步代码块
pass
if __name__ == "__main__":
import asyncio
asyncio.run(main())
async def main()定义了一个异步函数main,其中包含了与工作流交互的异步操作。通过await workflow.run(...)等方式,在处理用户输入时,异步执行工作流,避免阻塞主线程,提高程序执行效率,尤其是在处理与语言模型交互等可能耗时的操作时,能够提升系统的响应速度和并发处理能力。if name == "main":确保脚本直接运行时,通过asyncio.run(main())启动异步事件循环,执行main函数中的异步代码。
5. 发散练习
5.1 练习案例
在现有代码基础上,添加一个修改用户昵称的功能,用户可以通过自然语言指令 "将我的昵称修改为 [新昵称]" 来修改之前设定的名字,并且在修改成功后,再次查询名字时应返回新的昵称。
5.2 参考解决答案
- 首先定义修改昵称的函数:
python
async def modify_nickname(ctx: Context, new_nickname: str) -> str:
state = await ctx.get("state")
state["name"] = new_nickname
await ctx.set("state", state)
return f"Nickname modified to {new_nickname}"
- 然后将这个函数添加到工具列表中:
ini
workflow = AgentWorkflow.from_tools_or_functions(
[set_name, modify_nickname],
llm=llm,
system_prompt="You are a helpful assistant that can set and modify a name.",
initial_state={"name": "unset"}
)
- 完整代码如下:
python
from dotenv import load_dotenv
load_dotenv()
from llama_index.llms.openai import OpenAI
from llama_index.core.agent.workflow import AgentWorkflow
from llama_index.core.workflow import Context
llm = OpenAI(model="gpt-4o-mini")
async def set_name(ctx: Context, name: str) -> str:
state = await ctx.get("state")
state["name"] = name
await ctx.set("state", state)
return f"Name set to {name}"
async def modify_nickname(ctx: Context, new_nickname: str) -> str:
state = await ctx.get("state")
state["name"] = new_nickname
await ctx.set("state", state)
return f"Nickname modified to {new_nickname}"
workflow = AgentWorkflow.from_tools_or_functions(
[set_name, modify_nickname],
llm=llm,
system_prompt="You are a helpful assistant that can set and modify a name.",
initial_state={"name": "unset"}
)
async def main():
ctx = Context(workflow)
# check if it knows a name before setting it
response = await workflow.run(user_msg="What's my name?", ctx=ctx)
print(str(response))
# set the name using a tool
response2 = await workflow.run(user_msg="My name is sansi", ctx=ctx)
print(str(response2))
# modify the nickname
response3 = await workflow.run(user_msg="将我的昵称修改为凉拌三丝", ctx=ctx)
print(str(response3))
# retrieve the value from the state directly
state = await ctx.get("state")
print("Name as stored in state: ", state["name"])
if __name__ == "__main__":
import asyncio
asyncio.run(main())
在这个练习中,通过定义modify_nickname函数并将其集成到工作流工具列表中,实现了用户昵称的修改功能。同时,利用已有的上下文管理机制,确保修改后的昵称能够正确存储和查询,进一步强化了对上下文管理和工具集成知识点的理解和应用。
6. 案例总结
在本次案例中,我们深入探讨了 LlamaIndex 中状态存储与读取这个知识点。通过 AgentWorkflow 实现了语言模型与自定义工具的高效协作,使得系统能够准确处理用户的状态设定和查询请求。上下文管理确保了用户信息在多轮对话中的有效保存和使用,为用户提供连贯的交互体验。希望读者通过本次案例,对这些关键知识点有更深入的理解。在本系列教程的下一篇文章中,我们将继续探索 LlamaIndex 在更多复杂场景下的应用,挖掘其更多潜力,敬请期待!