简介
构想一下,你想买点物品。于是,你访问某个电子商务网站并经常使用搜查选项查找所需内容。兴许你有很多物品要买,所以这个环节不是很有效。如今,请思考一下这样一个场景:关上一个运行程序,用繁难的英语形容一下你想要的物品,而后按下回车键。你不用担忧搜查和多少钱比拟,由于运行程序会智能为你处置了。很酷,对吧?这正是咱们将在本文中要构建的指标程序。
上方,先让咱们先看一些例子。
用户同时恳求多个产品
用户咨询他/她能做出的最划算的购置
接上去,让咱们为这个运行程序增强一些性能。咱们将经常使用Meta公司开发的具备函数调用性能的Llama 3开源模型。不过,本文示例程序也可以经常使用此模型的3.1版原本成功。依据Meta公司的通告(),3.1版本模型可以更有效地经常使用工具和函数。
【留意】这些模型是允许多言语的,具备128K的更长的高低文长度和最先进的工具经常使用才干和全体更强的推理才干。
我将在本文示例开发中经常使用Groq云平台,特意是他们在本文中的大数据模型。此运行程序的初始上班流程包括一个嵌入模型、一个检索器,还有两个关键工具,用于处置用户购置兴味和与老本关系的疑问。总之,咱们要求相似于下图所述的组件内容。
示例运行程序架构图
如今,咱们必定要在开发当选用经常使用一个LLM组件框架。为此,我选用了我不时最青睐的消费级开源AI平台框架Haystack()。
预备好了咱们要求的内容后,接上去就让咱们开局投入关键的开发上班吧!
加载和索引数据
由于咱们本示例程序中经常使用了一个RAG管道,咱们首先构建一个文档索引服务,将经常使用Haystack提供的内存向量数据库。请留意,咱们矢量数据库中的每个文档都蕴含如下字段:
当调用咱们的RAG管道时,Content字段用于向量搜查。一切其余字段都作为元数据蕴含在矢量数据库中。留意,保留这些元数据是至关关键的,由于它们在用户的前端演示中至关关键。
接上去,让咱们看看如何成功这一点。
from haystack import Pipeline, Documentfrom haystack.document_stores.in_memory import InMemoryDocumentStorefrom haystack.components.writers import DocumentWriterfrom haystack.components.embedders import SentenceTransformersDocumentEmbedderfrom haystack.components.generators import OpenAIGeneratorfrom haystack.utils import Secretfrom haystack.components.generators.chat import OpenAIChatGeneratorfrom haystack.components.builders import PromptBuilderfrom haystack.components.embedders import SentenceTransformersTextEmbedderfrom haystack.components.retrievers.in_memory import InMemoryEmbeddingRetrieverfrom haystack.dataclasses import ChatMessageimport pandas as pd#从CSV加载产品数据df = pd.read_csv("product_sample.csv")#初始化内存中的文档存储区document_store = InMemoryDocumentStore()#将产品数据转换为Haystack文档对象documents = [Document(content=item.product_name,meta={"id": item.uniq_id,"price": item.selling_price,"url": item.product_url}) for item in df.itertuples()]#创立一个用于编制文档索引的管道indexing_pipeline = Pipeline()#经常使用句子转换器模型向管道中参与一个文档嵌入器indexing_pipeline.add_component(instance=SentenceTransformersDocumentEmbedder(model="sentence-transformers/all-MiniLM-L6-v2"),)# 向管道中参与文档写入器,以便在文档存储区中存储文档indexing_pipeline.add_component(instance=DocumentWriter(document_store=document_store),)#将嵌入器的输入衔接到写入器的输入indexing_pipeline.connect("doc_embedder.documents", "doc_writer.documents")#运转索引管道来处置和存储文档indexing_pipeline.run({"doc_embedder": {"documents": documents}})
太好了,咱们曾经成功了AI代理运行程序的第一步。如今,是时刻构建产品标识符函数工具了。为了更好地理解产品标识符的关键义务,让咱们思考上方的示例。
用户查问内容如下:
英语原文: I want to buy a camping boot, a charcoal and google pixel 9 back cover.
中文意思:我想买一双露营靴、一块木炭和谷歌pixel 9手机外壳。
如今,先让咱们了解一下产品标识符函数的理想化上班流程。
产品标识函数上班流程
首先,咱们要求创立一个工具来剖析用户查问并识别用户感兴味的产品。咱们可以经常使用上方的代码片段构建这样一个工具。
构建用户查问剖析器
template = """Understand the user query and list of products the user is interested in and return product names as list.You should always return a Python list. Do not return any explanation.Examples:Question: I am interested in camping boots, charcoal and disposable rain jacket.Answer: ["camping_boots","charcoal","disposable_rain_jacket"]Question: Need a laptop, wireless mouse, and noise-cancelling headphones for work.Answer: ["laptop","wireless_mouse","noise_cancelling_headphones"]Question: {{ question }}Answer:"""product_identifier = Pipeline()product_identifier.add_component("prompt_builder", PromptBuilder(template=template))product_identifier.add_component("llm", generator())product_identifier.connect("prompt_builder", "llm")
好了,如今咱们曾经成功了第一个函数的一半,如今是时刻经过参与RAG管道来成功该函数了。
产品标识性能上班流程
创立RAG管道
template = """Return product name, price, and url as a python dictionary.You should always return a Python dictionary with keys price, name and url for single product.You should always return a Python list of dictionaries with keys price, name and url for multiple products.Do not return any explanation.Legitimate Response Schema:{"price": "float", "name": "string", "url": "string"}Legitimate Response Schema for multiple products:[{"price": "float", "name": "string", "url": "string"},{"price": "float", "name": "string", "url": "string"}]Context:{% for document in documents %}product_price: {{ document.meta['price'] }}product_url: {{ document.meta['url'] }}product_id: {{ document.meta['id'] }}product_name: {{ document.content }}{% endfor %}Question: {{ question }}Answer:"""rag_pipe = Pipeline()rag_pipe.add_component("embedder", SentenceTransformersTextEmbedder(model="sentence-transformers/all-MiniLM-L6-v2"))rag_pipe.add_component("retriever", InMemoryEmbeddingRetriever(document_store=document_store, top_k=5))rag_pipe.add_component("prompt_builder", PromptBuilder(template=template))rag_pipe.add_component("llm", generator())rag_pipe.connect("embedder.embedding", "retriever.query_embedding")rag_pipe.connect("retriever", "prompt_builder.documents")rag_pipe.connect("prompt_builder", "llm")
口头上方的代码之后,咱们就成功了RAG和查问剖析管道的构建。如今是时刻把它转换成一个工具了。为此,咱们可以经常使用惯例函数申明,如下所示。为AI代理创立工具就像创立Python函数一样。假设你有相似于上方这样的疑问:
代理如何调用这个函数?
处置打算很繁难:应用特定于模型的工具形式。当然,咱们将在稍后的步骤中参与该形式。如今,是时刻创立一个同时经常使用查问剖析器和RAG管道的包装器函数了。
还是先让咱们来明白一下这个函数的指标。
指标1:识别用户感兴味的一切产品,并将其作为列表前往。
指标2:关于每个已识别的产品,从数据库中检索最多五个产品及其元数据。
成功产品标识符函数
def product_identifier_func(query: str):"""依据给定的查问来标识产品,并检索每个已标识的产品的关系详细信息。参数:query (str): 用于标识产品的查问字符串。前往值:dict: 一个字典,其中键是产品称号,值是每个产品的详细信息。假设没有找到产品,则前往“No product found”。"""product_understanding = product_identifier.run({"prompt_builder": {"question": query}})try:product_list = literal_eval(product_understanding["llm"]["replies"][0])except:return "No product found"results = {}for product in product_list:response = rag_pipe.run({"embedder": {"text": product}, "prompt_builder": {"question": product}})try:results[product] = literal_eval(response["llm"]["replies"][0])except:results[product] = {}return results
产品标识函数上班流程
至此,咱们成功了代理的第一个工具的构建。如今,先让咱们来看看它能否按预期上班。
query = "I want crossbow and woodstock puzzle"#口头函数product_identifier_func(query)# {'crossbow': {'name': 'DB Longboards CoreFlex Crossbow 41" Bamboo Fiberglass '#'Longboard Complete',#'price': 237.68,#'url': 'https://www.amazon.com/DB-Longboards-CoreFlex-Fiberglass-Longboard/dp/B07KMVJJK7'},#'woodstock_puzzle': {'name': 'Woodstock- Collage 500 pc Puzzle',#'price': 17.49,#'url': 'https://www.amazon.com/Woodstock-Collage-500-pc-Puzzle/dp/B07MX21WWX'}}
成功了!但是,值得留意的是这里前往的输入形式。上方给出输入的总体形式架构。
{"product_key": {"name": "string","price": "float","url": "string"}}
这正是咱们倡导RAG管道中生成的形式。下一步,让咱们构建一个名为find_budget_friend_option的可选工具函数。
def find_budget_friendly_option(selected_product_details):"""为每一类产品找到最经济友好的选用。参数:selected_product_details (dict): 一个字典,其中的键是产品类别和值是列表的产品细节。每个产品的细节都应该是一个蕴含一个“price”键的字典。前往结果:dict: 一个字典,其中键是产品类别,值是每个类别的最经济友好的产品详细信息。"""budget_friendly_options = {}for category, items in selected_product_details.items():if isinstance(items, list):lowest_price_item = min(items, key=lambda x: x['price'])else:lowest_price_item = itemsbudget_friendly_options[category] = lowest_price_itemreturn budget_friendly_options
让咱们关注这个运行程序最关键的方面,也就是,让AI代理能够依据要求经常使用这些性能。正如咱们之前所探讨的,这可以经过特定于模型的工具形式来成功。因此,咱们要求定位特定于所选模型的工具形式。幸运的是,Groq模型库()中提到了这一点。咱们仅要求把它调整一下,以顺应咱们的经常使用场景即可。
最终确定聊天模板
chat_template = '''<|start_header_id|>system<|end_header_id|>You are a function calling AI model. You are provided with function signatures within <tools></tools> XML tags. You may call one or more functions to assist with the user query. Don't make assumptions about what values to plug into functions. For each function call return a json object with function name and arguments within <tool_call></tool_call> XML tags as follows:<tool_call>{"name": <function-name>,"arguments": <args-dict>}</tool_call>Here are the available tools:<tools>{"name": "product_identifier_func","description": "To understand user interested products and its details","parameters": {"type": "object","properties": {"query": {"type": "string","description": "The query to use in the search. Infer this from the user's message. It should be a question or a statement"}},"required": ["query"]}},{"name": "find_budget_friendly_option","description": "Get the most cost-friendly option. If selected_product_details has morethan one key this should return most cost-friendly options","parameters": {"type": "object","properties": {"selected_product_details": {"type": "dict","description": "Input>
如今,只剩下几步了。在做任何事件之前,还是先让咱们来测试一下咱们的代理。
##测试代理messages = [ChatMessage.from_system(chat_template),ChatMessage.from_user("I need to buy a crossbow for my child and Pokémon for myself."),]chat_generator = get_chat_generator()response = chat_generator.run(messages=messages)pprint(response)## 照应结果{'replies': [ChatMessage(content='<tool_call>\n''{"id": 0, "name": "product_identifier_func", ''"arguments": {"query": "I need to buy a ''crossbow for my child"}}\n''</tool_call>\n''<tool_call>\n''{"id": 1, "name": "product_identifier_func", ''"arguments": {"query": "I need to buy a ''Pokemon for myself"}}\n''</tool_call>',role=<ChatRole.ASSISTANT: 'assistant'>,name=None,meta={'finish_reason': 'stop','index': 0,'model': 'llama3-groq-70b-8192-tool-use-preview','usage': {'completion_time': 0.217823967,'completion_tokens': 70,'prompt_time': 0.041348261,'prompt_tokens': 561,'total_time': 0.259172228,'total_tokens': 631}})]}
至此,咱们曾经成功了大约90%的上班。
上班凑近序幕。
在上述照应结果中,你或许曾经留意到XML标签<tool_call>蕴含了工具调用。因此,咱们要求开发一种机制来提取tool_call对象。
def extract_tool_calls(tool_calls_str):json_objects = re.findall(r'<tool_call>(.*?)</tool_call>', tool_calls_str, re.DOTALL)result_list = [json.loads(obj) for obj in json_objects]return result_listavailable_functions = {"product_identifier_func": product_identifier_func,"find_budget_friendly_option": find_budget_friendly_option}
成功此步骤后,当代理调用工具时,咱们可以间接访问代理的照应。如今惟一悬而未决的是失掉工具调用对象并相应地口头函数。让咱们也把这一局部成功。
messages.append(ChatMessage.from_user(message))response = chat_generator.run(messages=messages)if response and "<tool_call>" in response["replies"][0].content:function_calls = extract_tool_calls(response["replies"][0].content)for function_call in function_calls:# 解析函数调用信息function_name = function_call["name"]function_args = function_call["arguments"]#找到相应的函数并用给定的参数调用它function_to_call = available_functions[function_name]function_response = function_to_call(**function_args)# 经常使用`ChatMessage.from_function`在信息列表中附加函数照应messages.append(ChatMessage.from_function(content=json.dumps(function_response), name=function_name))response = chat_generator.run(messages=messages)
如今,是时刻将前面的每个组件衔接在一同,从而构建一个完整的聊天运行程序了。为此,我选用经常使用弱小的开源的深度学习模型可视化工具Gradio。
import gradio as grmessages = [ChatMessage.from_system(chat_template)]chat_generator = get_chat_generator()def chatbot_with_fc(message, messages):messages.append(ChatMessage.from_user(message))response = chat_generator.run(messages=messages)while True:if response and "<tool_call>" in response["replies"][0].content:function_calls = extract_tool_calls(response["replies"][0].content)for function_call in function_calls:#解析函数调用信息function_name = function_call["name"]function_args = function_call["arguments"]#找到相应的函数并用给定的参数调用它function_to_call = available_functions[function_name]function_response = function_to_call(**function_args)# 经常使用`ChatMessage.from_function`在信息列表中附加函数照应messages.append(ChatMessage.from_function(content=json.dumps(function_response), name=function_name))response = chat_generator.run(messages=messages)# 活期对话else:messages.append(response["replies"][0])breakreturn response["replies"][0].contentdef chatbot_interface(user_input, state):response_content = chatbot_with_fc(user_input, state)return response_content, statewith gr.Blocks() as demo:gr.Markdown("# AI Purchase Assistant")gr.Markdown("Ask me about products you want to buy!")state = gr.State(value=messages)with gr.Row():user_input = gr.Textbox(label="Your message:")response_output = gr.Markdown(label="Response:")user_input.submit(chatbot_interface, [user_input, state], [response_output, state])gr.Button("Send").click(chatbot_interface, [user_input, state], [response_output, state])demo.launch()
就是这么繁难!至此,咱们曾经成功构建了一个基于Llama 3模型的人工智能代理程序,它自身具备函数调用性能。你可以从GitHub仓库()访问其完整的源代码。
此外,你可以经过Kaggle链接()访问本文中经常使用的数据集。
论断
演绎来看,在构建基于人工智能代理的系统程序时,关键的是要思考成功义务所需的期间以及每个义务所经常使用的API调用(令牌)的数量。这方面开发面临的一个关键应战是缩小系统中的幻觉,这也是一个十分生动的钻研畛域。因此,构建LLM和代理系统没有固定的规定。开发团队有必要耐烦和策略性地布局上班,以确保人工智能代理LLM反常运转。
最后,除非另有说明,本文中一切图片均由作者自己提供。
参考资料
译者引见
朱先忠,社区编辑,专家博客、讲师,潍坊一所高校计算机老师,自在编程界老兵一枚。
原文题目:Using Llama 3 for Building AI Agents,作者:Ransaka Ravihara