وكلاء الذكاء الاصطناعي لمعالجة السلاسل الزمنية وإطارات البيانات الكبيرة
بناء من الصفر باستخدام Python و Ollama فقط (بدون وحدة معالجة الرسومات GPU، بدون مفتاح API)
مقدمة
الوكلاء (Agents) هم أنظمة ذكاء اصطناعي مدعومة بنماذج لغوية كبيرة (LLMs)، قادرة على التفكير في أهدافها واتخاذ الإجراءات لتحقيق هدف نهائي. وهي مصممة ليس فقط للاستجابة للاستعلامات، ولكن لتنظيم سلسلة من العمليات، بما في ذلك معالجة البيانات (مثل dataframes والسلاسل الزمنية). تتيح هذه القدرة العديد من التطبيقات الواقعية لإضفاء الطابع الديمقراطي على الوصول إلى تحليل البيانات، مثل أتمتة التقارير، والاستعلامات بدون تعليمات برمجية، والدعم في تنظيف البيانات ومعالجتها.
يمكن للوكلاء (Agents) التفاعل مع dataframes بطريقتين مختلفتين:
- عن طريق اللغة الطبيعية – يقرأ النموذج اللغوي الكبير (LLM) الجدول كسلسلة نصية ويحاول فهمه بناءً على قاعدة معارفه.
- عن طريق إنشاء وتنفيذ التعليمات البرمجية – يقوم الوكيل (Agent) بتنشيط الأدوات لمعالجة مجموعة البيانات ككائن.
من خلال الجمع بين قوة معالجة اللغة الطبيعية (NLP) ودقة تنفيذ التعليمات البرمجية، تمكّن وكلاء الذكاء الاصطناعي (AI Agents) نطاقًا أوسع من المستخدمين من التفاعل مع مجموعات البيانات المعقدة واستخلاص رؤى قيمة.
في هذا البرنامج التعليمي، سأوضح كيفية معالجة dataframes والسلاسل الزمنية باستخدام وكلاء الذكاء الاصطناعي (AI Agents). سأقدم بعض أكواد Python المفيدة التي يمكن تطبيقها بسهولة في حالات مماثلة أخرى (فقط انسخ والصق وشغل)، وسأشرح كل سطر من التعليمات البرمجية مع التعليقات حتى تتمكن من تكرار هذا المثال (رابط إلى الكود الكامل في نهاية المقال).
الإعداد
لنبدأ بإعداد Ollama (pip install ollama==0.4.7
)، وهي مكتبة تسمح للمستخدمين بتشغيل نماذج لغوية كبيرة مفتوحة المصدر محليًا، دون الحاجة إلى خدمات سحابية، مما يمنح مزيدًا من التحكم في خصوصية البيانات والأداء. نظرًا لتشغيلها محليًا، لا تغادر أي بيانات محادثة جهازك.
أولاً، تحتاج إلى تنزيل Ollama من الموقع الإلكتروني.
بعد ذلك، في موجه الأوامر الخاص بجهازك، استخدم الأمر لتنزيل النموذج اللغوي الكبير (LLM) الذي اخترته. سأستخدم Qwen الخاص بـ Alibaba، لأنه ذكي وخفيف على حد سواء.
بعد اكتمال التنزيل، يمكنك الانتقال إلى Python والبدء في كتابة التعليمات البرمجية.
import ollama
llm = "qwen2.5"
لنجرب النموذج اللغوي الكبير:
stream = ollama.generate(model=llm, prompt='''what time is it?''', stream=True)
for chunk in stream:
print(chunk['response'], end='', flush=True)
السلاسل الزمنية
السلسلة الزمنية هي تسلسل لنقاط البيانات المقاسة على مدى فترة من الزمن، وغالبًا ما تستخدم للتحليل والتنبؤ. تتيح لنا رؤية كيف تتغير المتغيرات بمرور الوقت، وتستخدم لتحديد الاتجاهات والأنماط الموسمية. تعتبر السلاسل الزمنية أداة قوية في التحليل الإحصائي والتنبؤ.
سأقوم بإنشاء مجموعة بيانات سلاسل زمنية وهمية لاستخدامها كمثال.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
## create data
np.random.seed(1) #<--for reproducibility
length = 30
ts = pd.DataFrame(data=np.random.randint(low=0, high=15, size=length),
columns=['y'],
index=pd.date_range(start='2023-01-01', freq='MS', periods=length).strftime('%Y-%m'))
## plot
ts.plot(kind="bar", figsize=(10,3), legend=False, color="black").grid(axis='y')
عادةً، تحتوي مجموعات بيانات السلاسل الزمنية على هيكل بسيط حقًا مع المتغير الرئيسي كعمود والوقت كفهرس.
قبل تحويله إلى سلسلة نصية، أريد التأكد من وضع كل شيء تحت عمود، حتى لا نفقد أي جزء من المعلومات.
dtf = ts.reset_index().rename(columns={"index":"date"})
dtf.head()
ثم، يجب تغيير نوع البيانات من DataFrame إلى قاموس.
data = dtf.to_dict(orient='records')
data[0:5]
أخيرًا، من قاموس إلى سلسلة نصية.
str_data = "\n".join([str(row) for row in data])
str_data
الآن بعد أن أصبح لدينا سلسلة نصية، يمكن إدراجها في موجه يمكن لأي نموذج لغوي معالجته. عندما تلصق مجموعة بيانات في موجه، يقرأ نموذج اللغة الكبير (LLM) البيانات كنص عادي، ولكنه لا يزال بإمكانه فهم الهيكل والمعنى بناءً على الأنماط التي شوهدت أثناء التدريب.
prompt = f'''
Analyze this dataset, it contains monthly sales data of an online retail product:
{str_data}
'''
يمكننا بسهولة بدء محادثة مع نموذج اللغة الكبير (LLM). يرجى ملاحظة أنه في الوقت الحالي، هذا ليس وكيلًا لأنه لا يمتلك أي أداة، فنحن نستخدم فقط النموذج اللغوي. على الرغم من أنه لا يعالج الأرقام مثل الكمبيوتر، إلا أن نموذج اللغة الكبير (LLM) يمكنه التعرف على أسماء الأعمدة والأنماط المستندة إلى الوقت والاتجاهات والقيم المتطرفة، خاصة مع مجموعات البيانات الأصغر. يمكنه محاكاة التحليل وشرح النتائج، لكنه لن يجري حسابات دقيقة بشكل مستقل، لأنه لا ينفذ التعليمات البرمجية مثل الوكيل.
messages = [{"role":"system", "content":prompt}]
while True:
## User
q = input('
>')
if q == "quit":
break
messages.append( {"role":"user", "content":q} )
## Model
agent_res = ollama.chat(model=llm, messages=messages, tools=[])
res = agent_res["message"]["content"]
## Response
print("
>", f"\x1b[1;30m{res}\x1b[0m")
messages.append( {"role":"assistant", "content":res} )
يتعرف نموذج اللغة الكبير (LLM) على الأرقام ويفهم السياق العام، بنفس الطريقة التي قد يفهم بها وصفة أو سطرًا من التعليمات البرمجية.
كما ترون، فإن استخدام نماذج اللغة الكبيرة (LLMs) لتحليل السلاسل الزمنية أمر رائع للحصول على رؤى سريعة ومحادثات.
الوكيل (Agent)
تتفوق نماذج اللغات الكبيرة (LLMs) في توليد الأفكار واستكشاف المفاهيم الأولية، بينما يمكن للوكيل (Agent) تنفيذ التعليمات البرمجية. لذلك، يمكنه التعامل مع مهام أكثر تعقيدًا مثل الرسوم البيانية، والتنبؤ، واكتشاف الحالات الشاذة. لذا، دعونا ننشئ الأدوات (Tools).
في بعض الأحيان، قد يكون التعامل مع “الإجابة النهائية” كأداة (Tool) أكثر فعالية. على سبيل المثال، إذا كان الوكيل (Agent) يقوم بإجراءات متعددة لإنشاء نتائج وسيطة، فيمكن اعتبار الإجابة النهائية بمثابة الأداة (Tool) التي تدمج كل هذه المعلومات في استجابة متماسكة. من خلال تصميمها بهذه الطريقة، يمكنك الحصول على مزيد من التخصيص والتحكم في النتائج.
def final_answer(text:str) -> str:
return text
tool_final_answer = {'type':'function', 'function':{
'name': 'final_answer',
'description': 'Returns a natural language response to the user',
'parameters': {'type': 'object',
'required': ['text'],
'properties': {'text': {'type':'str', 'description':'natural language response'}}
}}}
final_answer(text="hi")
ثم، أداة (Tool) الترميز.
import io
import contextlib
def code_exec(code:str) -> str:
output = io.StringIO()
with contextlib.redirect_stdout(output):
try:
exec(code)
except Exception as e:
print(f"Error: {e}")
return output.getvalue()
tool_code_exec = {'type':'function', 'function':{
'name': 'code_exec',
'description': 'Execute python code. Use always the function print() to get the output.',
'parameters': {'type': 'object',
'required': ['code'],
'properties': {
'code': {'type':'str', 'description':'code to execute'},
}}}}
code_exec("from datetime import datetime; print(datetime.now().strftime('%H:%M'))")
علاوة على ذلك، سأضيف بعض وظائف الأدوات المساعدة (utils functions) لاستخدام الأداة (Tool) ولتشغيل الوكيل (Agent).
dic_tools = {"final_answer":final_answer, "code_exec":code_exec}
# Utils
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}
عندما يحاول الوكيل (Agent) حل مهمة ما، أريده أن يتتبع الأدوات (Tools) التي تم استخدامها، والمدخلات التي جربها، والنتائج التي حصل عليها. يجب أن تتوقف العملية فقط عندما يكون النموذج جاهزًا لتقديم الإجابة النهائية.
فيما يتعلق بأداة (Tool) الترميز، لاحظت أن الوكلاء (Agents) يميلون إلى إعادة إنشاء إطار البيانات (dataframe) في كل خطوة. لذلك سأستخدم تعزيز الذاكرة (memory reinforcement) لتذكير النموذج بأن مجموعة البيانات موجودة بالفعل. وهي حيلة شائعة تستخدم للحصول على السلوك المطلوب. في النهاية، تساعدك تقنيات تعزيز الذاكرة (memory reinforcements) في الحصول على تفاعلات أكثر جدوى وفعالية.
# Start a chat
messages = [{"role":"system", "content":prompt}]
memory = '''
The dataset already exists and it's called 'dtf', don't create a new one.
'''
while True:
## User
q = input('
>')
if q == "quit":
break
messages.append( {"role":"user", "content":q} )
## Memory
messages.append( {"role":"user", "content":memory} )
## Model
available_tools = {"final_answer":tool_final_answer, "code_exec":tool_code_exec}
res = run_agent(llm, messages, available_tools)
## Response
print("
>", f"\x1b[1;30m{res}\x1b[0m")
messages.append( {"role":"assistant", "content":res} )
يعد إنشاء مخطط (plot) أمرًا لا يمكن لنموذج اللغات الكبيرة (LLM) وحده القيام به. ولكن ضع في اعتبارك أنه حتى إذا كان بإمكان الوكلاء (Agents) إنشاء صور، فلا يمكنهم رؤيتها، لأن المحرك لا يزال في النهاية نموذجًا لغويًا. لذلك، المستخدم هو الوحيد الذي يتصور المخطط (plot).
يستخدم الوكيل (Agent) مكتبة statsmodels لتدريب نموذج والتنبؤ بالسلاسل الزمنية.
التعامل مع إطارات البيانات الكبيرة (Large Dataframes)
تتميز نماذج اللغات الكبيرة (LLMs) بذاكرة محدودة، مما يقيّد كمية المعلومات التي يمكنها معالجتها في وقت واحد. حتى أكثر النماذج تطوراً لديها حدود للرموز (Token Limits) (بضع مئات من الصفحات النصية). بالإضافة إلى ذلك، لا تحتفظ نماذج اللغات الكبيرة (LLMs) بالذاكرة عبر الجلسات ما لم يتم دمج نظام استرجاع. عملياً، للعمل بفعالية مع إطارات البيانات الكبيرة، غالباً ما يستخدم المطورون استراتيجيات مثل التقطيع (Chunking)، والاسترجاع المعزز بالتوليد (RAG)، وقواعد بيانات المتجهات، وتلخيص المحتوى قبل إدخاله إلى النموذج.
لنقم بإنشاء مجموعة بيانات كبيرة للعب بها.
import random
import string
length = 1000
dtf = pd.DataFrame(data={
'Id': [''.join(random.choices(string.ascii_letters, k=5)) for _ in range(length)],
'Age': np.random.randint(low=18, high=80, size=length),
'Score': np.random.uniform(low=50, high=100, size=length).round(1),
'Status': np.random.choice(['Active','Inactive','Pending'], size=length)
})
dtf.tail()
سأضيف أداة البحث عبر الويب (Web-Searching Tool)، بحيث تكتسب الذكاء الاصطناعي ذو الأغراض العامة، مع القدرة على تنفيذ كود Python والبحث في الإنترنت، الوصول إلى جميع المعارف المتاحة ويمكنه اتخاذ قرارات تستند إلى البيانات.
في Python، أسهل طريقة لإنشاء أداة بحث عبر الويب هي باستخدام المتصفح الخاص الشهير DuckDuckGo (pip install duckduckgo-search==6.3.5
). يمكنك استخدام المكتبة الأصلية مباشرة أو استيراد غلاف LangChain (pip install langchain-community==0.3.17
).
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'},
}}}}
search_web(query="nvidia")
إجمالاً، لدى الوكيل (Agent) الآن 3 أدوات.
dic_tools = {'final_answer':final_answer,
'search_web':search_web,
'code_exec':code_exec}
نظراً لأنني لا أستطيع إضافة إطار البيانات الكامل في المطالبة (Prompt)، سأقوم بتغذية الصفوف الـ 10 الأولى فقط حتى يتمكن نموذج اللغات الكبيرة (LLM) من فهم السياق العام لمجموعة البيانات. بالإضافة إلى ذلك، سأحدد مكان العثور على مجموعة البيانات الكاملة.
str_data = "\n".join([str(row) for row in dtf.head(10).to_dict(orient='records')])
prompt = f'''
You are a Data Analyst, you will be given a task to solve as best you can.
You have access to the following tools:
- tool 'final_answer' to return a text response.
- tool 'code_exec' to execute Python code.
- tool 'search_web' to search for information on the internet.
If you use the 'code_exec' tool, remember to always use the function print() to get the output.
The dataset already exists and it's called 'dtf', don't create a new one.
This dataset contains credit score for each customer of the bank. Here's the first rows:
{str_data}
'''
أخيراً، يمكننا تشغيل الوكيل (Agent).
messages = [{"role":"system", "content":prompt}]
memory = '''
The dataset already exists and it's called 'dtf', don't create a new one.
'''
while True:
## User
q = input('
>')
if q == "quit":
break
messages.append( {"role":"user", "content":q} )
## Memory
messages.append( {"role":"user", "content":memory} )
## Model
available_tools = {"final_answer":tool_final_answer, "code_exec":tool_code_exec, "search_web":tool_search_web}
res = run_agent(llm, messages, available_tools)
## Response
print("
>", f"\x1b[1;30m{res}\x1b[0m")
messages.append( {"role":"assistant", "content":res} )
في هذا التفاعل، استخدم الوكيل (Agent) أداة الترميز (Coding Tool) بشكل صحيح. الآن، أريد أن أجعله يستخدم الأداة الأخرى أيضاً.
أخيراً، أحتاج إلى أن يقوم الوكيل (Agent) بتجميع كل أجزاء المعلومات التي تم الحصول عليها حتى الآن من هذه الدردشة.
الخلاصة
كانت هذه المقالة بمثابة دليل تعليمي لتوضيح كيفية بناء وكلاء (Agents) من الصفر لمعالجة السلاسل الزمنية وإطارات البيانات الكبيرة (large dataframes). لقد غطينا الطريقتين اللتين يمكن للنماذج التفاعل بهما مع البيانات: من خلال اللغة الطبيعية، حيث يفسر نموذج اللغة الكبير (LLM) الجدول كسلسلة باستخدام قاعدة معارفه، وعن طريق إنشاء وتنفيذ التعليمات البرمجية، والاستفادة من الأدوات لمعالجة مجموعة البيانات ككائن.
الكود الكامل لهذه المقالة: GitHub
آمل أن تكونوا قد استمتعتم بها! لا تترددوا في الاتصال بي لطرح الأسئلة والملاحظات، أو لمشاركة مشاريعكم الشيقة.