ربات چت خود را به عاملی تبدیل کنید که می تواند با API های خارجی تعامل داشته باشد
فراخوانی تابع چیز جدیدی نیست. در جولای 2023، OpenAI Function Calling را برای مدلهای GPT خود معرفی کرد، ویژگی که اکنون توسط رقبا به کار گرفته شده است. Google’s Gemini API اخیراً از آن پشتیبانی کرده و Anthropic آن را در Claude ادغام کرده است. فراخوانی تابع برای مدلهای زبان بزرگ (LLM) ضروری است و قابلیتهای آنها را افزایش میدهد. یادگیری این تکنیک حتی مفیدتر است!
با در نظر گرفتن این موضوع، من قصد دارم یک آموزش جامع بنویسم که فراتر از مقدمه های اولیه، توابع فراخوانی را پوشش دهد (در حال حاضر آموزش های زیادی برای این کار وجود دارد). تمرکز بر اجرای عملی، ساخت یک عامل هوش مصنوعی کاملاً مستقل و ادغام آن با Streamlit برای یک رابط ChatGPT خواهد بود. اگرچه OpenAI برای نمایش استفاده می شود، این آموزش را می توان به راحتی برای سایر LLM هایی که از فراخوانی عملکرد پشتیبانی می کنند، مانند Gemini، تطبیق داد.
فراخوانی توابع به توسعه دهندگان این امکان را می دهد که توابع را توصیف کنند (با نام مستعار ابزارها، شما می توانید آنها را به عنوان اقداماتی برای مدل در نظر بگیرید، مانند انجام یک محاسبه یا سفارش) و مدل را به طور هوشمندانه ای انتخاب کند که یک شی JSON حاوی آرگومان هایی را برای فراخوانی آن ها تولید کند. کارکرد. به عبارت ساده تر، اجازه می دهد:
- تصمیم گیری مستقل: مدل ها می توانند هوشمندانه ابزارهایی را برای پاسخ به سوالات انتخاب کنند.
- تجزیه و تحلیل قابل اعتماد: پاسخ ها در قالب JSON هستند، به جای پاسخ معمولی تر شبیه به گفتگو. ممکن است در نگاه اول زیاد به نظر نرسد، اما این چیزی است که به LLM اجازه میدهد به سیستمهای خارجی متصل شود، مثلاً از طریق یک API با ورودیهای ساختاریافته.
احتمالات زیادی را نشان می دهد:
- دستیاران هوش مصنوعی مستقل: رباتها میتوانند با سیستمهای داخلی برای کارهایی مانند سفارشهای مشتری و برگرداندن، علاوه بر ارائه پاسخ به سوالات، تعامل داشته باشند.
- دستیاران تحقیق شخصی: بگویید اگر قصد سفر خود را دارید، دستیاران می توانند در وب جستجو کنند، محتوا را مرور کنند، گزینه ها را مقایسه کنند و نتایج را در اکسل خلاصه کنند.
- دستورات صوتی اینترنت اشیا: مدلها میتوانند دستگاهها را کنترل کنند یا اقداماتی را بر اساس اهداف شناساییشده، مانند تنظیم دمای AC پیشنهاد دهند.
با قرض گرفتن از مستندات فراخوانی تابع Gemini، فراخوانی تابع ساختار زیر را دارد که در OpenAI به همین صورت عمل می کند.
- درخواست مشکلات کاربر با برنامه
- برنامه اعلان ارائه شده توسط کاربر و اعلان عملکرد را ارسال می کند، که توضیحی از ابزار(هایی) است که مدل می تواند استفاده کند.
- بر اساس اعلان تابع، مدل ابزار مورد استفاده و پارامترهای پرس و جو مربوطه را پیشنهاد می کند. توجه داشته باشید که مدل فقط ابزار و پارامترهای پیشنهادی را بدون فراخوانی توابع خروجی میدهد.
- & 5. بر اساس پاسخ، برنامه کاربردی API مناسب را فراخوانی می کند
6. و 7. پاسخ API به مدل بازگردانده می شود تا یک پاسخ قابل خواندن توسط انسان به دست آید.
8. برنامه پاسخ نهایی را به کاربر برمی گرداند، سپس از 1 تکرار کنید.
این ممکن است گیج کننده به نظر برسد، اما این مفهوم با یک مثال به طور مفصل توضیح داده خواهد شد
قبل از ورود به کد، چند کلمه در مورد معماری برنامه آزمایشی
پاسخ
در اینجا ما یک دستیار برای گردشگرانی که از یک هتل بازدید می کنند می سازیم. دستیار به ابزارهای زیر دسترسی دارد که به آن امکان دسترسی به برنامه های خارجی را می دهد.
get_items
،purchase_item
: از طریق API به کاتالوگ محصول ذخیره شده در پایگاه داده متصل شوید تا لیستی از اقلام را بازیابی کنید و بر اساس آن خرید انجام دهید.rag_pipeline_func
: با Retrieval Augmented Generation (RAG) به مخزن اسناد متصل شوید تا اطلاعات را از متون بدون ساختار بازیابی کنید، به عنوان مثال. بروشورهای هتل
پشته فنی
حالا بیایید شروع کنیم!
آماده سازی
برای شبیه سازی کد من به Github بروید. مطالب زیر را می توانید در function_calling_demo
نوت بوک.
لطفا یک محیط مجازی ایجاد و فعال کنید pip install -r requirements.txt
برای نصب پکیج های لازم
مقداردهی اولیه
ابتدا به OpenRouter متصل می شویم. استفاده جایگزین از اصل OpenAIChatGenerator
بدون رونویسی api_base_url
همچنین به شرط داشتن کلید OpenAI API کار خواهد کرد
import os
from dotenv import load_dotenv
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.utils import Secret
from haystack.dataclasses import ChatMessage
from haystack.components.generators.utils import print_streaming_chunk# Set your API key as environment variable before executing this
load_dotenv()
OPENROUTER_API_KEY = os.environ.get('OPENROUTER_API_KEY')
chat_generator = OpenAIChatGenerator(api_key=Secret.from_env_var("OPENROUTER_API_KEY"),
api_base_url="https://openrouter.ai/api/v1",
model="openai/gpt-4-turbo-preview",
streaming_callback=print_streaming_chunk)
سپس تست می کنیم که آیا می تواند chat_generator
با موفقیت فراخوانی شود
chat_generator.run(messages=[ChatMessage.from_user("Return this text: 'test'")])
---------- The response should look like this ----------
{'replies': [ChatMessage(content="'test'", role=<ChatRole.ASSISTANT: 'assistant'>, name=None, meta={'model': 'openai/gpt-4-turbo-preview', 'index': 0, 'finish_reason': 'stop', 'usage': {}})]}
مرحله 1: یک فروشگاه داده ایجاد کنید
در اینجا ما بین برنامه خود و دو منبع داده ارتباط برقرار می کنیم: فروشگاه اسناد برای متون بدون ساختار و پایگاه داده برنامه از طریق API
اسناد فهرست با خط لوله
ما نمونه متون را در documents
برای مدل برای انجام بازیابی نسل افزوده (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# Sample documents
documents = [
Document(content="Coffee shop opens at 9am and closes at 5pm."),
Document(content="Gym room opens at 6am and closes at 10pm.")
]
# Create the document store
document_store = InMemoryDocumentStore()
# Create a pipeline to turn the texts into embeddings and store them in the document store
indexing_pipeline = Pipeline()
indexing_pipeline.add_component(
"doc_embedder", SentenceTransformersDocumentEmbedder(model="sentence-transformers/all-MiniLM-L6-v2")
)
indexing_pipeline.add_component("doc_writer", DocumentWriter(document_store=document_store))
indexing_pipeline.connect("doc_embedder.documents", "doc_writer.documents")
indexing_pipeline.run({"doc_embedder": {"documents": documents}})
باید خروجی مربوط به documents
ما به عنوان نمونه ایجاد کردیم
{'doc_writer': {'documents_written': 2}}
سرور API را مستقر کنید
یک سرور API ساخته شده با Flask در زیر ایجاد می شود db_api.py
برای اتصال به SQLite لطفا با اجرا آن را بچرخانید python db_api.py
در ترمینال شما
همچنین توجه داشته باشید که برخی از داده های اولیه اضافه شده است db_api.py
مرحله 2: توابع را تعریف کنید
در اینجا ما توابع واقعی را برای فراخوانی مدل آماده می کنیم بعد از فراخوانی یک تابع (مرحله 4-5 همانطور که در توضیح داده شد ساختار فراخوانی تابع)
عملکرد RAG
یعنی در rag_pipeline_func
. این برای این است که مدل با جستجو در متون ذخیره شده در Document Store پاسخ دهد. ابتدا استخراج RAG را به عنوان یک نقاله انبار کاه تعریف می کنیم
from haystack.components.embedders import SentenceTransformersTextEmbedder
from haystack.components.retrievers.in_memory import InMemoryEmbeddingRetriever
from haystack.components.builders import PromptBuilder
from haystack.components.generators import OpenAIGeneratortemplate = """
Answer the questions based on the given context.
Context:
{% for document in documents %}
{{ 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))
rag_pipe.add_component("prompt_builder", PromptBuilder(template=template))
# Note to llm: We are using OpenAIGenerator, not the OpenAIChatGenerator, because the latter only accepts List[str] as input and cannot accept prompt_builder's str output
rag_pipe.add_component("llm", OpenAIGenerator(api_key=Secret.from_env_var("OPENROUTER_API_KEY"),
api_base_url="https://openrouter.ai/api/v1",
model="openai/gpt-4-turbo-preview"))
rag_pipe.connect("embedder.embedding", "retriever.query_embedding")
rag_pipe.connect("retriever", "prompt_builder.documents")
rag_pipe.connect("prompt_builder", "llm")
تست کنید آیا این ویژگی کار می کند
query = “When does the coffee shop open?”
rag_pipe.run({"embedder": {"text": query}, "prompt_builder": {"question": query}})
این باید نتیجه زیر را ایجاد کند. اطلاع replies
که مدل داده شده از نمونه اسنادی است که قبلا ارائه کردیم
{'llm': {'replies': ['The coffee shop opens at 9am.'],
'meta': [{'model': 'openai/gpt-4-turbo-preview',
'index': 0,
'finish_reason': 'stop',
'usage': {'completion_tokens': 9,
'prompt_tokens': 60,
'total_tokens': 69,
'total_cost': 0.00087}}]}}
سپس می توانیم ورق بزنیم rag_pipe
در تابعی که فراهم می کند replies
فقط بدون افزودن هیچ جزئیات دیگری
def rag_pipeline_func(query: str):
result = rag_pipe.run({"embedder": {"text": query}, "prompt_builder": {"question": query}})return {"reply": result["llm"]["replies"][0]}
تماس های API
تعیین می کنیم get_items
و purchase_item
توابع برای تعامل با پایگاه داده
# Flask's default local URL, change it if necessary
db_base_url="http://127.0.0.1:5000"# Use requests to get the data from the database
import requests
import json
# get_categories is supplied as part of the prompt, it is not used as a tool
def get_categories():
response = requests.get(f'{db_base_url}/category')
data = response.json()
return data
def get_items(ids=None,categories=None):
params = {
'id': ids,
'category': categories,
}
response = requests.get(f'{db_base_url}/item', params=params)
data = response.json()
return data
def purchase_item(id,quantity):
headers = {
'Content-type':'application/json',
'Accept':'application/json'
}
data = {
'id': id,
'quantity': quantity,
}
response = requests.post(f'{db_base_url}/item/purchase', json=data, headers=headers)
return response.json()
لیست ابزار را تعریف کنید
اکنون که توابع را تعریف کردیم، باید به مدل اجازه دهیم آن توابع را شناسایی کند و با ارائه توضیحاتی به آنها آموزش دهیم که چگونه از آنها استفاده کنند.
از آنجایی که ما در اینجا از OpenAI استفاده می کنیم، tools
در زیر فرمت شده است و از فرمت مورد نیاز Open AI پیروی می کند
tools = [
{
"type": "function",
"function": {
"name": "get_items",
"description": "Get a list of items from the database",
"parameters": {
"type": "object",
"properties": {
"ids": {
"type": "string",
"description": "Comma separated list of item ids to fetch",
},
"categories": {
"type": "string",
"description": "Comma separated list of item categories to fetch",
},
},
"required": [],
},
}
},
{
"type": "function",
"function": {
"name": "purchase_item",
"description": "Purchase a particular item",
"parameters": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "The given product ID, product name is not accepted here. Please obtain the product ID from the database first.",
},
"quantity": {
"type": "integer",
"description": "Number of items to purchase",
},
},
"required": [],
},
}
},
{
"type": "function",
"function": {
"name": "rag_pipeline_func",
"description": "Get information from hotel brochure",
"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"],
},
},
}
]
مرحله 3: همه را کنار هم قرار دهید
ما اکنون داده هایی را داریم که برای آزمایش فراخوانی تابع نیاز داریم! در اینجا ما چند کار را انجام می دهیم:
- اعلان مدل اولیه را ارائه دهید تا زمینه ای به آن ارائه شود
- یک نمونه پیام تولید شده توسط کاربر ارائه دهید
- مهمتر از همه، ما لیست ابزار را به ژنراتور چت ارسال می کنیم
tools
# 1. Initial prompt
context = f"""You are an assistant to tourists visiting a hotel.
You have access to a database of items (which includes {get_categories()}) that tourists can buy, you also have access to the hotel's brochure.
If the tourist's question cannot be answered from the database, you can refer to the brochure.
If the tourist's question cannot be answered from the brochure, you can ask the tourist to ask the hotel staff.
"""
messages = [
ChatMessage.from_system(context),
# 2. Sample message from user
ChatMessage.from_user("Can I buy a coffee?"),
]# 3. Passing the tools list and invoke the chat generator
response = chat_generator.run(messages=messages, generation_kwargs= {"tools": tools})
response
---------- Response ----------
{'replies': [ChatMessage(content="[{"index": 0, "id": "call_AkTWoiJzx5uJSgKW0WAI1yBB", "function": {"arguments": "{\\"categories\\":\\"Food and beverages\\"}", "name": "get_items"}, "type": "function"}]", role=<ChatRole.ASSISTANT: 'assistant'>, name=None, meta={'model': 'openai/gpt-4-turbo-preview', 'index': 0, 'finish_reason': 'tool_calls', 'usage': {}})]}
حالا بیایید جواب را بررسی کنیم. توجه کنید که چگونه فراخوانی تابع هم تابع انتخاب شده توسط مدل و هم آرگومان های فراخوانی تابع انتخاب شده را برمی گرداند.
function_call = json.loads(response["replies"][0].content)[0]
function_name = function_call["function"]["name"]
function_args = json.loads(function_call["function"]["arguments"])
print("Function Name:", function_name)
print("Function Arguments:", function_args)
---------- Response ----------
Function Name: get_items
Function Arguments: {‘categories’: ‘Food and beverages’}
هنگامی که با سؤال دیگری ارائه می شود، مدل از ابزار دیگری استفاده می کند که مناسب تر است
# Another question
messages.append(ChatMessage.from_user("Where's the coffee shop?"))# Invoke the chat generator, and passing the tools list
response = chat_generator.run(messages=messages, generation_kwargs= {"tools": tools})
function_call = json.loads(response["replies"][0].content)[0]
function_name = function_call["function"]["name"]
function_args = json.loads(function_call["function"]["arguments"])
print("Function Name:", function_name)
print("Function Arguments:", function_args)
---------- Response ----------
Function Name: rag_pipeline_func
Function Arguments: {'query': "Where's the coffee shop?"}
دوباره، توجه کنید که هیچ تابع واقعی در اینجا فراخوانی نمی شود، این همان کاری است که ما در مرحله بعد انجام خواهیم داد!
تابع را فراخوانی کنید
سپس می توانیم آرگومان ها را به تابع انتخاب شده ارسال کنیم
## Find the correspoding function and call it with the given arguments
available_functions = {"get_items": get_items, "purchase_item": purchase_item,"rag_pipeline_func": rag_pipeline_func}
function_to_call = available_functions[function_name]
function_response = function_to_call(**function_args)
print("Function Response:", function_response)
---------- Response ----------
Function Response: {'reply': 'The provided context does not specify a physical location for the coffee shop, only its operating hours. Therefore, I cannot determine where the coffee shop is located based on the given information.'}
پاسخ از rag_pipeline_func
سپس می توان با افزودن زیر به عنوان زمینه چت ارسال کرد messages
تا مدل جواب نهایی را بدهد
messages.append(ChatMessage.from_function(content=json.dumps(function_response), name=function_name))
response = chat_generator.run(messages=messages)
response_msg = response["replies"][0]print(response_msg.content)
---------- Response ----------
For the location of the coffee shop within the hotel, I recommend asking the hotel staff directly. They will be able to guide you to it accurately.
ما اکنون حلقه چت را تکمیل کرده ایم!
مرحله 4: به یک چت تعاملی تبدیل شوید
کد بالا نشان می دهد که چگونه فراخوانی تابع را می توان انجام داد، اما ما می خواهیم با تبدیل آن به یک چت تعاملی یک گام جلوتر برویم.
در اینجا من دو روش برای انجام این کار را نشان می دهم، از روش ابتدایی تر input()
که دیالوگ را در خود دفترچه چاپ می کند تا رندر شود توسط یک جریان روشن شده است تا به آن یک رابط کاربری مانند ChatGPT بدهید
input()
چرخه
کد از آموزش Haystack کپی شده است و به ما امکان می دهد مدل را به سرعت آزمایش کنیم. توجه: این برنامه برای نشان دادن ایده فراخوانی توابع ایجاد شده است و قرار نیست کاملاً پایدار باشد، به عنوان مثال. برای حفظ نظم چند مورد به طور همزمان، بدون توهم و غیره.
import json
from haystack.dataclasses import ChatMessage, ChatRoleresponse = None
messages = [
ChatMessage.from_system(context)
]
while True:
# if OpenAI response is a tool call
if response and response["replies"][0].meta["finish_reason"] == "tool_calls":
function_calls = json.loads(response["replies"][0].content)
for function_call in function_calls:
## Parse function calling information
function_name = function_call["function"]["name"]
function_args = json.loads(function_call["function"]["arguments"])
## Find the correspoding 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))
# Regular Conversation
else:
# Append assistant messages to the messages list
if not messages[-1].is_from(ChatRole.SYSTEM):
messages.append(response["replies"][0])
user_input = input("ENTER YOUR MESSAGE 👇 INFO: Type 'exit' or 'quit' to stop\n")
if user_input.lower() == "exit" or user_input.lower() == "quit":
break
else:
messages.append(ChatMessage.from_user(user_input))
response = chat_generator.run(messages=messages, generation_kwargs={"tools": tools})
اگرچه کار می کند، اما ممکن است بخواهیم چیزی داشته باشیم که زیباتر به نظر برسد.
رابط روشن
Streamlit اسکریپت های داده را به برنامه های کاربردی وب قابل اشتراک گذاری تبدیل می کند که یک رابط کاربری تمیز برای برنامه ما فراهم می کند. کد نشان داده شده در بالا در یک برنامه Streamlit در زیر تطبیق داده شده است streamlit
پوشه در مخزن من
شما می توانید آن را راه اندازی کنید:
- اگر قبلاً این کار را نکردهاید، سرور API را با آن مستقر کنید
python db_api.py
- OPENROUTER_API_KEY را به عنوان یک متغیر محیطی تنظیم کنید، به عنوان مثال
export OPENROUTER_API_KEY = ‘@REPLACE WITH YOUR API KEY’
با فرض اینکه در لینوکس هستید / با git bash در حال اجرا هستید - هدایت به
streamlit
پوشه در ترمینال باcd streamlit
- Streamlit را با
streamlit run app.py
. یک تب جدید باید به طور خودکار در مرورگر شما که برنامه را اجرا می کند ایجاد شود
این در مورد آن است! امیدوارم از این مقاله لذت ببرید.
*مگر اینکه خلاف آن ذکر شده باشد، همه تصاویر توسط نویسنده هستند