Type something to search...
使用 Llama 3 构建 AI 代理

使用 Llama 3 构建 AI 代理

构建具有 Llama 3 函数调用能力的 AI 代理的综合指南

引言

想象一下你想买一些东西。你访问一个电子商务网站,使用搜索选项找到你想要的东西。也许你有多个物品要购买,因此这个过程并不是很高效。现在考虑这个场景:打开一个应用程序,用简单的英语描述你想要的东西,然后按下回车。你不必担心搜索和价格比较,因为应用程序会自动为你处理这些事情。很酷,对吧?这正是我们将在本教程中构建的内容。

让我们先看一些例子。

好的,让我们为这个应用程序注入活力。我们将使用Meta的Llama 3模型,具有函数调用能力。不过,这也可以使用3.1模型来实现。根据Meta的公告,3.1模型可以更有效地使用工具和函数。

这些是多语言的,具有显著更长的上下文长度128K,最先进的工具使用能力,以及整体更强的推理能力。

我将使用Groq Cloud,特别是他们的模型来撰写本文。这个应用程序的初始工作流程应由一个嵌入模型、一个检索器和两个主要工具组成,用于处理用户的购买兴趣和与成本相关的关注。总之,我们需要一些类似于下面图表中描述的内容。

现在我们需要使用LLM编排框架。为此,我选择我一直以来最喜欢的Haystack

好的,我们得到了我们需要的东西。让我们跳入实际工作吧!

加载和索引数据

由于我们有一个 RAG 流水线,应该将构建文档索引服务作为第一步。对于这个演示,我将使用 Haystack 提供的内存向量数据库。请注意,我们的向量数据库中的每个文档包含:

  • 内容 — 我们用它来执行相似性搜索
  • Id — 唯一标识符
  • 价格 — 产品价格
  • URL — 产品 URL

当我们的 RAG 流水线被调用时,内容字段用于向量搜索。所有其他字段作为元数据包含。保留这些元数据至关重要,因为它对前端呈现给用户是必不可少的。

让我们看看如何实现这一点。

from haystack import Pipeline, Document
from haystack.document_stores.in_memory import InMemoryDocumentStore
from haystack.components.writers import DocumentWriter
from haystack.components.embedders import SentenceTransformersDocumentEmbedder
from haystack.components.generators import OpenAIGenerator
from haystack.utils import Secret
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.components.builders import PromptBuilder
from haystack.components.embedders import SentenceTransformersTextEmbedder
from haystack.components.retrievers.in_memory import InMemoryEmbeddingRetriever
from haystack.dataclasses import ChatMessage
import pandas as pd

## Load product data from CSV
df = pd.read_csv("product_sample.csv")

## Initialize an in-memory document store
document_store = InMemoryDocumentStore()

## Convert the product data into Haystack Document objects
documents = [
    Document(
        content=item.product_name, 
        meta={
            "id": item.uniq_id, 
            "price": item.selling_price, 
            "url": item.product_url
        }
    ) for item in df.itertuples()
]

## Create a pipeline for indexing the documents
indexing_pipeline = Pipeline()

## Add a document embedder to the pipeline using Sentence Transformers model
indexing_pipeline.add_component(
    instance=SentenceTransformersDocumentEmbedder(model="sentence-transformers/all-MiniLM-L6-v2"), name="doc_embedder"
)

## Add a document writer to the pipeline to store documents in the document store
indexing_pipeline.add_component(instance=DocumentWriter(document_store=document_store), name="doc_writer")

## Connect the embedder's output to the writer's input
indexing_pipeline.connect("doc_embedder.documents", "doc_writer.documents")

## Run the indexing pipeline to process and store the documents
indexing_pipeline.run({"doc_embedder": {"documents": documents}})

很好,我们已完成 AI 代理应用程序的第一步。现在是时候构建产品识别工具了。为了更好地理解产品识别器的主要任务,让我们考虑下面的示例。

用户查询:我想买一双露营靴、一台炭烤炉和一个 Google 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 和查询分析器管道。现在是时候将其转换为工具了。为此,我们可以使用常规的函数声明,如下所示。为代理创建工具就像创建一个 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"
#execute function
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_friendly_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 = items
        
        budget_friendly_options[category] = lowest_price_item
    
    return budget_friendly_options

好的,让我们专注于这个应用程序最关键的方面,即使代理根据需要使用这些功能。正如我们之前所讨论的,这可以通过模型特定的工具架构来实现。因此,我们需要找到特定于所选模型的工具架构。幸运的是,它在模型卡中提到 这里。我们需要调整它以适应我们的用例。

完成聊天模板

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 data is a dictionary where each key is a category name, and its value is either a single dictionary with 'price', 'name', and 'url' keys or a list of such dictionaries; example: {'category1': [{'price': 10.5, 'name': 'item1', 'url': 'http://example.com/item1'}, {'price': 8.99, 'name': 'item2', 'url': 'http://example.com/item2'}], 'category2': {'price': 15.0, 'name': 'item3', 'url': 'http://example.com/item3'}}"
                }
            },
            "required": ["selected_product_details"]
        }
    }
</tools><|eot_id|><|start_header_id|>user<|end_header_id|>

I need to buy a crossbow<|eot_id|><|start_header_id|>assistant<|end_header_id|>

<tool_call>
{"id":"call_deok","name":"product_identifier_func","arguments":{"query":"I need to buy a crossbow"}}
</tool_call><|eot_id|><|start_header_id|>tool<|end_header_id|>

<tool_response>
{"id":"call_deok","result":{'crossbow': {'price': 237.68,'name': 'crossbow','url': 'https://www.amazon.com/crossbow/dp/B07KMVJJK7'}}}
</tool_response><|eot_id|><|start_header_id|>assistant<|end_header_id|>
'''
现在只剩下几个步骤。在做任何事情之前,让我们测试一下我们的代理。


```python
### 测试代理
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)

### 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_list

available_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:
        # Parse function calling information
        function_name = function_call["name"]
        function_args = function_call["arguments"]

        # Find the corresponding function and call it with the given arguments
        function_to_call = available_functions[function_name]
        function_response = function_to_call(**function_args)

        # Append function response to the messages list using `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 gr

messages = [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:
                # Parse function calling information
                function_name = function_call["name"]
                function_args = function_call["arguments"]

                # Find the corresponding function and call it with the given arguments
                function_to_call = available_functions[function_name]
                function_response = function_to_call(**function_args)

                # Append function response to the messages list using `ChatMessage.from_function`
                messages.append(ChatMessage.from_function(content=json.dumps(function_response), name=function_name))
                response = chat_generator.run(messages=messages)

        # Regular Conversation
        else:
            messages.append(response["replies"][0])
            break
    return response["replies"][0].content


def chatbot_interface(user_input, state):
    response_content = chatbot_with_fc(user_input, state)
    return response_content, state

with gr.Blocks() as demo:
    gr.Markdown("# AI 购买助手")
    gr.Markdown("问我关于您想购买的产品!")
    
    state = gr.State(value=messages)
    
    with gr.Row():
        user_input = gr.Textbox(label="您的消息:")
        response_output = gr.Markdown(label="回复:")
    
    user_input.submit(chatbot_interface, [user_input, state], [response_output, state])
    gr.Button("发送").click(chatbot_interface, [user_input, state], [response_output, state])


demo.launch()

就这样!我们构建了基于Llama 3的AI代理🤖,具备函数调用能力。您可以从这个GitHub仓库访问完整代码。感谢您的阅读。

通过这个Kaggle链接(在CC0:公共领域下)可以访问本文使用的数据集。

结论

在构建基于AI代理的系统时,考虑完成任务所需的时间和每个任务使用的API调用(令牌)数量非常重要。一个主要的挑战是减少系统中的幻觉,这是一个活跃的研究领域。因此,构建LLM和代理系统没有固定的规则。必须耐心而有策略地工作,以确保AI代理,即LLM,正常运行。

除非另有说明,所有图片均由作者提供。

参考:

https://docs.together.ai/docs/llama-3-function-calling

Related Posts

使用 ChatGPT 搜索网络功能的 10 种创意方法

使用 ChatGPT 搜索网络功能的 10 种创意方法

例如,提示和输出 你知道可以使用 ChatGPT 的“搜索网络”功能来完成许多任务,而不仅仅是基本的网络搜索吗? 对于那些不知道的人,ChatGPT 新的“搜索网络”功能提供实时信息。 截至撰写此帖时,该功能仅对使用 ChatGPT 4o 和 4o-mini 的付费会员开放。 ![](https://images.weserv.nl/?url=https://cdn-im

阅读更多
在人工智能和技术领域保持领先地位的 10 项必学技能 📚

在人工智能和技术领域保持领先地位的 10 项必学技能 📚

在人工智能和科技这样一个动态的行业中,保持领先意味着不断提升你的技能。无论你是希望深入了解人工智能模型性能、掌握数据分析,还是希望通过人工智能转变传统领域如法律,这些课程都是你成功的捷径。以下是一个精心策划的高价值课程列表,可以助力你的职业发展,并让你始终处于创新的前沿。 1. 生成性人工智能简介课程: [生成性人工智能简介](https://genai.works

阅读更多
10 个强大的 Perplexity AI 提示,让您的营销任务自动化

10 个强大的 Perplexity AI 提示,让您的营销任务自动化

在当今快速变化的数字世界中,营销人员总是在寻找更智能的方法来简化他们的工作。想象一下,有一个个人助理可以为您创建受众档案,建议营销策略,甚至为您撰写广告文案。这听起来像是一个梦想? 多亏了像 Perplexity 这样的 AI 工具,这个梦想现在成为现实。通过正确的提示,您可以将 AI 转变为您的 个人营销助理。在本文中,我将分享 10 个强大的提示,帮助您自动

阅读更多
10+ 面向 UI/UX 设计师的顶级 ChatGPT 提示

10+ 面向 UI/UX 设计师的顶级 ChatGPT 提示

人工智能技术,如机器学习、自然语言处理和数据分析,正在重新定义传统设计方法。从自动化重复任务到实现个性化用户体验,人工智能使设计师能够更加专注于战略思维和创造力。随着这一趋势的不断增长,UI/UX 设计师越来越多地采用 AI 驱动的工具来促进他们的工作。利用人工智能不仅能提供基于数据的洞察,还为满足多样化用户需求的创新设计解决方案开辟了机会。 1. 用户角色开发 目的

阅读更多
在几分钟内完成数月工作的 100 种人工智能工具

在几分钟内完成数月工作的 100 种人工智能工具

人工智能(AI)的快速发展改变了企业的运作方式,使人们能够在短短几分钟内完成曾经需要几周或几个月的任务。从内容创作到网站设计,AI工具帮助专业人士节省时间,提高生产力,专注于创造力。以下是按功能分类的100个AI工具的全面列表,以及它们在现实世界中的使用实例。 1. 研究工具 研究可能耗时,但人工智能工具使查找、分析和组织数据变得更加容易。**ChatGPT, Cop

阅读更多
你从未知道的 17 个令人惊叹的 GitHub 仓库

你从未知道的 17 个令人惊叹的 GitHub 仓库

Github 隐藏的宝石!! 立即收藏的代码库 学习编程相对简单,但掌握编写更好代码的艺术要困难得多。GitHub 是开发者的宝藏,那里“金子”是其他人分享的精心编写的代码。通过探索 GitHub,您可以发现如何编写更清晰的代码,理解高质量代码的样子,并学习成为更熟练开发者的基本步骤。 1. notwaldorf/emoji-translate *谁需

阅读更多