في الجزء الأول من سلسلة الدروس التعليمية هذه، قدمنا وكلاء الذكاء الاصطناعي، وهي برامج مستقلة تقوم بتنفيذ المهام واتخاذ القرارات والتواصل مع الآخرين.
يُنفِّذ الوكلاء الإجراءات من خلال الأدوات. قد لا تعمل إحدى الأدوات من المحاولة الأولى، أو قد يتطلَّب الأمر تفعيل أدوات متعددة بالتتابع. يجب أن يكون الوكلاء قادرين على تنظيم المهام في تسلسل منطقي وتغيير استراتيجياتهم في بيئة ديناميكية.
ببساطة، يجب أن يكون هيكل العميل متينًا، وأن يكون سلوكه موثوقًا. الطريقة الأكثر شيوعًا لتحقيق ذلك هي من خلال:
- التكرارات – تكرار إجراء معين عدة مرات، غالبًا مع تغييرات أو تحسينات طفيفة في كل دورة. في كل مرة، قد يُعيد الوكيل النظر في خطوات معينة لتحسين مخرجاته أو الوصول إلى حل مثالي.
- السلاسل – سلسلة من الإجراءات المترابطة في تسلسل. كل خطوة في السلسلة تعتمد على سابقتها، ويصبح ناتج أحد الإجراءات مدخلاً للإجراء التالي.
في هذا الدرس، سأوضح كيفية استخدام التكرارات والسلاسل للوكلاء. سأقدم بعض أكواد بايثون المفيدة التي يمكن تطبيقها بسهولة في حالات مماثلة (انسخ، الصق، نفّذ)، وسأشرح كل سطر من الكود مع التعليقات لتكرار هذا المثال.
الإعداد
يرجى الرجوع إلى الجزء 1 لإعداد Ollama و LLM الرئيسي.
import ollama
llm = "qwen2.5"
سنستخدم واجهات برمجة التطبيقات العامة الخاصة بـ YahooFinance مع مكتبة Python (pip install yfinance==0.2.55) لتنزيل البيانات المالية.
import yfinance as yf
stock = "MSFT"
yf.Ticker(ticker=stock).history(period='5d') #1d,5d,1mo,3mo,6mo,1y,2y,5y,10y,ytd,max
دعونا ندرج ذلك في الأداة.
import matplotlib.pyplot as plt
def get_stock(ticker:str, period:str, col:str):
data = yf.Ticker(ticker=ticker).history(period=period)
if len(data) > 0:
data[col].plot(color="black", legend=True, xlabel='', title=f"{ticker.upper()} ({period})").grid()
plt.show()
return 'ok'
else:
return 'no'
tool_get_stock = {'type':'function', 'function':{
'name': 'get_stock',
'description': 'Download stock data',
'parameters': {'type': 'object',
'required': ['ticker','period','col'],
'properties': {
'ticker': {'type':'str', 'description':'the ticker symbol of the stock.'},
'period': {'type':'str', 'description':"for 1 month input '1mo', for 6 months input '6mo', for 1 year input '1y'. Use '1y' if not specified."},
'col': {'type':'str', 'description':"one of 'Open','High','Low','Close','Volume'. Use 'Close' if not specified."},
}}}}
## test
get_stock(ticker="msft", period="1y", col="Close")
علاوة على ذلك، من خلال أخذ الكود من المقالة السابقة كمرجع، سأكتب دالة عامة لمعالجة استجابة النموذج، مثل عندما يريد العميل استخدام أداة أو عندما يقوم فقط بإرجاع النص.
def use_tool(agent_res:dict, dic_tools:dict) -> dict:
## use tool
if "tool_calls" in agent_res["message"].keys():
for tool in agent_res["message"]["tool_calls"]:
t_name, t_inputs = tool["function"]["name"], tool["function"]["arguments"]
if f := dic_tools.get(t_name):
### calling tool
print('🔧 >', f"\x1b[1;31m{t_name} -> Inputs: {t_inputs}\x1b[0m")
### tool output
t_output = f(**tool["function"]["arguments"])
print(t_output)
### final res
res = t_output
else:
print('🤬 >', f"\x1b[1;31m{t_name} -> NotFound\x1b[0m")
## don't use tool
if agent_res['message']['content'] != '':
res = agent_res["message"]["content"]
t_name, t_inputs = '', ''
return {'res':res, 'tool_used':t_name, 'inputs_used':t_inputs}
لنبدأ محادثة سريعة مع وكيلنا. سأستخدم الآن توجيهًا عامًا وبسيطًا.
prompt = '''You are a financial analyst, assist the user using your available tools.'''
messages = [{"role":"system", "content":prompt}]
dic_tools = {'get_stock':get_stock}
while True:
## user input
try:
q = input('🙂 >')
except EOFError:
break
if q == "quit":
break
if q.strip() == "":
continue
messages.append( {"role":"user", "content":q} )
## model
agent_res = ollama.chat(model=llm, messages=messages,
tools=[tool_get_stock])
dic_res = use_tool(agent_res, dic_tools)
res, tool_used, inputs_used = dic_res["res"], dic_res["tool_used"], dic_res["inputs_used"]
## final response
print("👽 >", f"\x1b[1;30m{res}\x1b[0m")
messages.append( {"role":"assistant", "content":res} )
كما ترون، بدأتُ بسؤال “سهل”. يعرف LLM مسبقًا أن رمز سهم مايكروسوفت هو MSFT، ولذلك تمكّن الوكيل من تفعيل الأداة بالإدخالات الصحيحة. ولكن ماذا لو سألتُ سؤالًا قد لا يكون مُدرجًا في قاعدة بيانات LLM؟
يبدو أن LLM لا يعلم أن فيسبوك غيّر اسمه إلى META، لذا استخدم الأداة بمدخلات خاطئة. سأمكّن الوكيل من تجربة إجراء ما عدة مرات من خلال التكرارات.
التكرارات
التكرارات هي تكرار عملية ما حتى استيفاء شرط معين. يمكننا السماح للوكيل بالمحاولة عددًا محددًا من المرات، ولكن يجب إعلامه بأن المعلمات السابقة لم تنجح، وذلك بإضافة التفاصيل في سجل الرسائل.
max_i, i = 3, 0
while res == 'no' and i < max_i:
comment = f'''I used tool '{tool_used}' with inputs {inputs_used}. But it didn't work, so I must try again with different inputs'''
messages.append( {"role":"assistant", "content":comment} )
agent_res = ollama.chat(model=llm, messages=messages,
tools=[tool_get_stock])
dic_res = use_tool(agent_res, dic_tools)
res, tool_used, inputs_used = dic_res["res"], dic_res["tool_used"], dic_res["inputs_used"]
i += 1
if i == max_i:
res = f'I tried {i} times but something is wrong'
## final response
print("👽 >", f"\x1b[1;30m{res}\x1b[0m")
messages.append( {"role":"assistant", "content":res} )
حاول الوكيل ثلاث مرات باستخدام مدخلات مختلفة، لكنه لم يجد حلاً لوجود فجوة في قاعدة بيانات LLM. في هذه الحالة، احتاج النموذج إلى تدخل بشري لفهم كيفية استخدام الأداة.
بعد ذلك، سنعمل على تمكين الوكيل من ملء فجوة المعرفة بنفسه.
السلاسل
تشير السلسلة إلى سلسلة خطية من الإجراءات، حيث يُستخدم ناتج إحدى الخطوات كمدخل للخطوة التالية. في هذا المثال، سأضيف أداة أخرى يمكن للوكيل استخدامها في حال فشل الأولى.
يمكننا استخدام أداة البحث على الويب من المقالة السابقة.
from langchain_community.tools import DuckDuckGoSearchResults
def search_web(query:str) -> str:
return DuckDuckGoSearchResults(backend="news").run(query)
tool_search_web = {'type':'function', 'function':{
'name': 'search_web',
'description': 'Search the web',
'parameters': {'type': 'object',
'required': ['query'],
'properties': {
'query': {'type':'str', 'description':'the topic or subject to search on the web'},
}}}}
## test
search_web(query="facebook stock")
حتى الآن، كنتُ أستخدم دائمًا توجيهات عامة جدًا نظرًا لبساطة المهام نسبيًا. الآن، أريد التأكد من أن العميل يفهم كيفية استخدام الأدوات بالترتيب الصحيح، لذلك سأكتب توجيهًا مناسبًا. إليك كيفية كتابة التوجيه:
- هدف الوكيل
- ما يجب أن يعيده (أي التنسيق والمحتوى)
- أي تحذيرات ذات صلة قد تؤثر على الناتج
- تفريغ السياق
prompt = '''
[GOAL] You are a financial analyst, assist the user using your available tools.
[RETURN] You must return the stock data that the user asks for.
[WARNINGS] In order to retrieve stock data, you need to know the ticker symbol of the company.
[CONTEXT] First ALWAYS try to use the tool 'get_stock'.
If it doesn't work, you can use the tool 'search_web' and search 'company name stock'.
Get information about the stock and deduct what is the right ticker symbol of the company.
Then, you can use AGAIN the tool 'get_stock' with the ticker you got using the previous tool.
'''
يمكننا ببساطة إضافة السلسلة إلى حلقة التكرار الموجودة لدينا بالفعل. هذه المرة، يمتلك العميل أداتين، وعندما تفشل الأولى، يمكن للنموذج أن يقرر ما إذا كان سيعيد المحاولة أم سيستخدم الثانية. بعد ذلك، إذا استُخدمت الأداة الثانية، يجب على العميل معالجة المخرجات ومعرفة المدخلات الصحيحة للأداة الأولى التي فشلت في البداية.
max_i, i = 3, 0
while res in ['no',''] and i < max_i:
comment = f'''I used tool '{tool_used}' with inputs {inputs_used}. But it didn't work, so I must try a different way.'''
messages.append( {"role":"assistant", "content":comment} )
agent_res = ollama.chat(model=llm, messages=messages,
tools=[tool_get_stock, tool_search_web])
dic_res = use_tool(agent_res, dic_tools)
res, tool_used, inputs_used = dic_res["res"], dic_res["tool_used"], dic_res["inputs_used"]
## chain: output of previous tool = input of next tool
if tool_used == 'search_web':
query = q+". You must return just the compay ticker.\nContext: "+res
llm_res = ollama.generate(model=llm, prompt=query)["response"]
messages.append( {"role":"user", "content":f"try ticker: {llm_res}"} )
print("👽 >", f"\x1b[1;30mI can try with {llm_res}\x1b[0m")
agent_res = ollama.chat(model=llm, messages=messages, tools=[tool_get_stock])
dic_res = use_tool(agent_res, dic_tools)
res, tool_used, inputs_used = dic_res["res"], dic_res["tool_used"], dic_res["inputs_used"]
i += 1 if i == max_i: res = f'I tried {i} times but something is wrong'
## final response
print("👽 >", f"\x1b[1;30m{res}\x1b[0m")
messages.append( {"role":"assistant", "content":res} )
كما هو متوقع، حاول العميل استخدام الأداة الأولى بمدخلات خاطئة، ولكن بدلًا من تكرار نفس الإجراء السابق، قرر استخدام الأداة الثانية. من خلال استخدام المعلومات، يُفترض أن يفهم الحل دون الحاجة إلى تدخل بشري.
باختصار، حاول الذكاء الاصطناعي القيام بعمل ما، لكنه فشل بسبب نقص في قاعدة معارفه. لذا، فعّل أدوات لسدّ هذا النقص وتقديم النتائج التي طلبها المستخدم… وهذا هو جوهر وكلاء الذكاء الاصطناعي.
تناولت هذه المقالة طرقًا أكثر تنظيمًا لجعل الوكلاء أكثر موثوقية، باستخدام التكرارات والسلاسل. باستخدام هذه العناصر الأساسية، ستكون جاهزًا للبدء بتطوير وكلاءك الخاصين لحالات استخدام مختلفة.
اكتشاف المزيد من بايثون العربي
اشترك للحصول على أحدث التدوينات المرسلة إلى بريدك الإلكتروني.