جديد! (23 مايو 25) إذا كنت تفضل لغة بايثون، فاطلع على المنشور المصاحب
Tiny Agents in Python.
على مدى الأسابيع القليلة الماضية، كنت أتعمق في MCP (بروتوكول السياق النموذجي) لفهم سبب الضجة المحيطة به.
إن TL;DR الخاص بي هو أنه بسيط إلى حد ما، ولكنه لا يزال قويًا جدًا: MCP عبارة عن واجهة برمجة تطبيقات قياسية لكشف مجموعات من الأدوات التي يمكن ربطها بـ LLMs.
من السهل جدًا توسيع نطاق عميل الاستدلال – في HF، لدينا مجموعتان رسميتان من أدوات تطوير البرامج (SDK) للعملاء: @huggingface/inference في شبيبة، و huggingface_hub في Python – للعمل أيضًا كعميل MCP وربط الأدوات المتاحة من خوادم MCP باستدلال LLM.
ولكن أثناء القيام بذلك، جاء إدراكي الثاني:
بمجرد أن يكون لديك عميل MCP، يصبح الوكيل حرفيًا مجرد حلقة زمنية فوقه.
في هذه المقالة القصيرة، سوف أطلعك على كيفية تنفيذ ذلك في Typescript (JS)، وكيف يمكنك اعتماد MCP أيضًا وكيف سيجعل طريقة Agentic AI أسهل للمضي قدمًا.
![]()
كيفية تشغيل العرض التوضيحي الكامل
إذا كان لديك NodeJS (مع pnpm أو npm)، فقط قم بتشغيل هذا في المحطة:
npx @huggingface/mcp-client
أو إذا كنت تستخدم pnpm:
pnpx @huggingface/mcp-client
يؤدي هذا إلى تثبيت الحزمة الخاصة بي في مجلد مؤقت ثم تنفيذ الأمر الخاص بها.
سترى وكيلك البسيط يتصل بخادمين MCP مختلفين (يعملان محليًا)، ويقوم بتحميل أدواتهما، ثم يطالبك بإجراء محادثة.
افتراضيًا، يتصل وكيل المثال الخاص بنا بخادمي MCP التاليين:
- خادم نظام الملفات “الكنسي”، الذي يمكنه الوصول إلى سطح المكتب الخاص بك،
- وخادم Playwright MCP، الذي يعرف كيفية استخدام متصفح Chromium في وضع الحماية.
ملاحظة: هذا أمر غير بديهي بعض الشيء ولكن في الوقت الحالي، جميع خوادم MCP هي في الواقع عمليات محلية (على الرغم من أن الخوادم البعيدة ستتوفر قريبًا).
مدخلاتنا لهذا الفيديو الأول كانت:
اكتب قصيدة هايكو عن مجتمع Hugging Face واكتبها في ملف باسم “hf.txt” على سطح المكتب
الآن دعونا نجرب هذه المطالبة التي تتضمن بعض تصفح الويب:
قم بإجراء بحث على الويب عن موفري استدلال HF على Brave Search وافتح النتائج الثلاثة الأولى
النموذج الافتراضي والموفر
فيما يتعلق بزوج النموذج/المزود، يستخدم وكيلنا المثال افتراضيًا:
كل هذا قابل للتكوين من خلال متغيرات env! يرى:
const agent = new Agent({
provider: process.env.PROVIDER ?? "nebius",
model: process.env.MODEL_ID ?? "Qwen/Qwen2.5-72B-Instruct",
apiKey: process.env.HF_TOKEN,
servers: SERVERS,
});
أين يعيش الكود
يوجد رمز Tiny Agent في mcp-client الحزمة الفرعية لل huggingface.js mono-repo، وهو GitHub mono-repo الذي توجد فيه جميع مكتبات JS الخاصة بنا.
https://github.com/huggingface/huggingface.js/tree/main/packages/mcp-client
تستخدم قاعدة التعليمات البرمجية ميزات JS الحديثة (لا سيما المولدات غير المتزامنة) التي تجعل تنفيذ الأمور أسهل، وخاصة الأحداث غير المتزامنة مثل استجابات LLM. قد تحتاج إلى سؤال LLM عن ميزات JS هذه إذا لم تكن على دراية بها بعد.
الأساس لهذا: أداة تستدعي الدعم الأصلي في LLMs.
ما سيجعل هذه المدونة بأكملها سهلة للغاية هو أن المجموعة الأخيرة من حاملي شهادات LLM (المغلقة والمفتوحة على حد سواء) قد تم تدريبهم على استدعاء الوظائف، المعروف أيضًا باسم. استخدام الأداة.
يتم تعريف الأداة باسمها ووصفها وتمثيل JSONSchema لمعلماتها. بمعنى ما، فهو تمثيل غير شفاف لواجهة أي وظيفة، كما يُرى من الخارج (بمعنى، لا يهتم LLM بكيفية تنفيذ الوظيفة فعليًا).
const weatherTool = {
type: "function",
function: {
name: "get_weather",
description: "Get current temperature for a given location.",
parameters: {
type: "object",
properties: {
location: {
type: "string",
description: "City and country e.g. Bogotá, Colombia",
},
},
},
},
};
الوثائق الأساسية التي سأربطها هنا هي وظيفة استدعاء مستند OpenAI. (نعم… تحدد OpenAI إلى حد كبير معايير LLM للمجتمع بأكمله 😅).
تتيح لك محركات الاستدلال تمرير قائمة من الأدوات عند استدعاء LLM، ويكون LLM حرًا في استدعاء صفر أو واحدة أو أكثر من هذه الأدوات. كمطور، يمكنك تشغيل الأدوات وإدخال نتائجها مرة أخرى في LLM لمواصلة عملية الإنشاء.
لاحظ أنه في الواجهة الخلفية (على مستوى محرك الاستدلال)، يتم تمرير الأدوات ببساطة إلى النموذج في ملف بتنسيق خاص
chat_template، مثل أي رسالة أخرى، ثم يتم تحليلها من الاستجابة (باستخدام الرموز المميزة الخاصة بالنموذج) لعرضها على هيئة استدعاءات للأدوات. شاهد مثالاً في ملعب قالب الدردشة الخاص بنا.
تنفيذ عميل MCP أعلى InferenceClient
الآن بعد أن عرفنا ما هي الأداة في دورات LLM الحديثة، فلنقم بتنفيذ عميل MCP الفعلي.
المستند الرسمي الموجود على https://modelcontextprotocol.io/quickstart/client مكتوب بشكل جيد إلى حد ما. ما عليك سوى استبدال أي ذكر لعميل Anthropic SDK بأي SDK عميل آخر متوافق مع OpenAI. (يوجد أيضًا ملف llms.txt يمكنك إدخاله في برنامج LLM الذي تختاره لمساعدتك في البرمجة).
للتذكير، نحن نستخدم HF InferenceClient لعميل الاستدلال لدينا.
الكامل
McpClient.tsيوجد ملف الكود هنا إذا كنت تريد المتابعة باستخدام الكود الفعلي 🤓
ملكنا McpClient الطبقة لديها:
- عميل الاستدلال (يعمل مع أي مزود استدلال، و
huggingface/inferenceيدعم كلاً من نقاط النهاية البعيدة والمحلية) - مجموعة من جلسات عميل MCP، واحدة لكل خادم MCP متصل (نعم، نريد دعم خوادم متعددة)
- وقائمة بالأدوات المتاحة التي سيتم ملؤها من الخوادم المتصلة وإعادة تنسيقها قليلاً.
export class McpClient {
protected client: InferenceClient;
protected provider: string;
protected model: string;
private clients: Map<ToolName, Client> = new Map();
public readonly availableTools: ChatCompletionInputTool[] = [];
constructor({ provider, model, apiKey }: { provider: InferenceProvider; model: string; apiKey: string }) {
this.client = new InferenceClient(apiKey);
this.provider = provider;
this.model = model;
}
}
للاتصال بخادم MCP، المسؤول @modelcontextprotocol/sdk/client يوفر TypeScript SDK Client فئة مع أ listTools() طريقة:
async addMcpServer(server: StdioServerParameters): Promise<void> {
const transport = new StdioClientTransport({
...server,
env: { ...server.env, PATH: process.env.PATH ?? "" },
});
const mcp = new Client({ name: "@huggingface/mcp-client", version: packageVersion });
await mcp.connect(transport);
const toolsResult = await mcp.listTools();
debug(
"Connected to server with tools:",
toolsResult.tools.map(({ name }) => name)
);
for (const tool of toolsResult.tools) {
this.clients.set(tool.name, mcp);
}
this.availableTools.push(
...toolsResult.tools.map((tool) => {
return {
type: "function",
function: {
name: tool.name,
description: tool.description,
parameters: tool.inputSchema,
},
} satisfies ChatCompletionInputTool;
})
);
}
StdioServerParameters هي واجهة من MCP SDK تتيح لك إنشاء عملية محلية بسهولة: كما ذكرنا سابقًا، حاليًا، جميع خوادم MCP هي في الواقع عمليات محلية.
بالنسبة لكل خادم MCP نتصل به، نقوم بإعادة تنسيق قائمة الأدوات الخاصة به قليلاً ونضيفها إليها this.availableTools.
كيفية استخدام الأدوات
سهل، أنت تمر فقط this.availableTools لاستكمال دردشة LLM الخاصة بك، بالإضافة إلى مجموعة الرسائل المعتادة:
const stream = this.client.chatCompletionStream({
provider: this.provider,
model: this.model,
messages,
tools: this.availableTools,
tool_choice: "auto",
});
tool_choice: "auto" هي المعلمة التي تمررها إلى LLM لإنشاء استدعاءات أدوات صفرية أو واحدة أو متعددة.
عند تحليل المخرجات أو دفقها، سيقوم LLM بإنشاء بعض استدعاءات الأدوات (على سبيل المثال، اسم وظيفة وبعض الوسائط المشفرة بـ JSON)، والتي تحتاج (كمطور) إلى حسابها. مرة أخرى، يجعل عميل MCP SDK هذا الأمر سهلاً للغاية؛ لديها client.callTool() طريقة:
const toolName = toolCall.function.name;
const toolArgs = JSON.parse(toolCall.function.arguments);
const toolMessage: ChatCompletionInputMessageTool = {
role: "tool",
tool_call_id: toolCall.id,
content: "",
name: toolName,
};
const client = this.clients.get(toolName);
if (client) {
const result = await client.callTool({ name: toolName, arguments: toolArgs });
toolMessage.content = result.content[0].text;
} else {
toolMessage.content = `Error: No session found for tool: ${toolName}`;
}
أخيرًا ستضيف رسالة الأداة الناتجة إلى ملفك messages مجموعة والعودة إلى LLM.
وكيلنا المكون من 50 سطرًا من التعليمات البرمجية 🤯
الآن بعد أن أصبح لدينا عميل MCP قادر على الاتصال بخوادم MCP التعسفية للحصول على قوائم الأدوات وقادر على حقنها وتحليلها من استنتاج LLM، حسنًا… ما هو الوكيل؟
بمجرد أن يكون لديك عميل استدلالي مزود بمجموعة من الأدوات، يصبح الوكيل مجرد حلقة فوقه.
بمزيد من التفاصيل، الوكيل هو ببساطة مزيج من:
- موجه النظام
- عميل استدلال LLM
- عميل MCP لربط مجموعة من الأدوات به من مجموعة من خوادم MCP
- بعض تدفق التحكم الأساسي (انظر أدناه للحصول على حلقة while)
الكامل
Agent.tsملف التعليمات البرمجية هنا.
تقوم فئة الوكيل لدينا ببساطة بتوسيع McpClient:
export class Agent extends McpClient {
private readonly servers: StdioServerParameters[];
protected messages: ChatCompletionInputMessage[];
constructor({
provider,
model,
apiKey,
servers,
prompt,
}: {
provider: InferenceProvider;
model: string;
apiKey: string;
servers: StdioServerParameters[];
prompt?: string;
}) {
super({ provider, model, apiKey });
this.servers = servers;
this.messages = [
{
role: "system",
content: prompt ?? DEFAULT_SYSTEM_PROMPT,
},
];
}
}
افتراضيًا، نستخدم مطالبة نظام بسيطة جدًا مستوحاة من تلك المشتركة في دليل المطالبة GPT-4.1.
على الرغم من أن هذا يأتي من OpenAI 😈، إلا أن هذه الجملة على وجه الخصوص تنطبق على المزيد والمزيد من النماذج، المغلقة والمفتوحة على حد سواء:
نحن نشجع المطورين على استخدام حقل الأدوات حصريًا لتمرير الأدوات، بدلاً من إدخال أوصاف الأدوات يدويًا في الموجه الخاص بك وكتابة محلل منفصل لاستدعاءات الأدوات، كما أبلغ البعض عن القيام بذلك في الماضي.
وهذا يعني أننا لسنا بحاجة إلى تقديم قوائم منسقة بعناية لأمثلة استخدام الأداة في الموجه. ال tools: this.availableTools المعلمة كافية.
إن تحميل الأدوات على الوكيل هو حرفيًا مجرد الاتصال بخوادم MCP التي نريدها (بالتوازي لأنه من السهل جدًا القيام بذلك في JS):
async loadTools(): Promise<void> {
await Promise.all(this.servers.map((s) => this.addMcpServer(s)));
}
نضيف أداتين إضافيتين (خارج MCP) يمكن استخدامهما بواسطة LLM لتدفق التحكم الخاص بوكيلنا:
const taskCompletionTool: ChatCompletionInputTool = {
type: "function",
function: {
name: "task_complete",
description: "Call this tool when the task given by the user is complete",
parameters: {
type: "object",
properties: {},
},
},
};
const askQuestionTool: ChatCompletionInputTool = {
type: "function",
function: {
name: "ask_question",
description: "Ask a question to the user to get more info required to solve or clarify their problem.",
parameters: {
type: "object",
properties: {},
},
},
};
const exitLoopTools = [taskCompletionTool, askQuestionTool];
عند استدعاء أي من هذه الأدوات، سيقوم الوكيل بكسر حلقته وإعادة التحكم إلى المستخدم للحصول على إدخال جديد.
الحلقة الكاملة أثناء
ها لدينا حلقة كاملة.🎉
جوهر حلقة الوقت الرئيسية لوكيلنا هو أننا ببساطة نكرر مع LLM بالتناوب بين استدعاء الأداة وتزويدها بنتائج الأداة، ونحن نفعل ذلك حتى يبدأ LLM في الرد برسالتين غير أدوات على التوالي.
هذه هي الحلقة الكاملة أثناء:
let numOfTurns = 0;
let nextTurnShouldCallTools = true;
while (true) {
try {
yield* this.processSingleTurnWithTools(this.messages, {
exitLoopTools,
exitIfFirstChunkNoTool: numOfTurns > 0 && nextTurnShouldCallTools,
abortSignal: opts.abortSignal,
});
} catch (err) {
if (err instanceof Error && err.message === "AbortError") {
return;
}
throw err;
}
numOfTurns++;
const currentLast = this.messages.at(-1)!;
if (
currentLast.role === "tool" &&
currentLast.name &&
exitLoopTools.map((t) => t.function.name).includes(currentLast.name)
) {
return;
}
if (currentLast.role !== "tool" && numOfTurns > MAX_NUM_TURNS) {
return;
}
if (currentLast.role !== "tool" && nextTurnShouldCallTools) {
return;
}
if (currentLast.role === "tool") {
nextTurnShouldCallTools = false;
} else {
nextTurnShouldCallTools = true;
}
}
الخطوات التالية
هناك العديد من الخطوات التالية الرائعة المحتملة بمجرد أن يكون لديك عميل MCP قيد التشغيل وطريقة بسيطة لبناء العملاء 🔥
- تجربة مع نماذج أخرى
- تجربة مع كل مقدمو الاستدلال:
- Cerebras، Cohere، Fal، Fireworks، Hyperbolic، Nebius، Novita، Replicate، SambaNova، Together، إلخ.
- كل واحد منهم لديه تحسينات مختلفة لاستدعاء الوظائف (أيضًا اعتمادًا على الطراز) لذلك قد يختلف الأداء!
- خطاف LLMs المحلية باستخدام llama.cpp أو LM Studio
طلبات السحب والمساهمات هي موضع ترحيب! مرة أخرى، كل شيء هنا مفتوح المصدر! 💎❤️