# تحليل الاستجابة

أصبح من الشائع بشكل متزايد أن تولّد نماذج المحادثة مخرجات منظمة، بدلاً من مجرد سلسلة رد واحدة. على سبيل المثال، قد تُصدر [نموذج الاستدلال](https://huggingface.co/reasoning-course) سلسلة تفكير تحتوي على مسار استدلالها، بينما قد يُصدر نموذج [استدعاء الأدوات](./chat_extras) أسماء الدوال ووسائطها.

المشكلة في المخرجات المهيكلة، ومع ذلك، أن مخرجات نماذج اللغة الكبيرة (LLMs) ليست مهيكلة بطبيعتها. تقبل واجهات برمجة تطبيقات LLMs عادةً وتعيد قواميس رسائل (message dicts) بمفاتيح مثل `role` و`content` و`reasoning_content`، لكن داخليًا، تستمر نماذج اللغة الكبيرة في الواقع في توليد تسلسل واحد من الرموز (tokens). نستخدم طبقة وسيطة (glue layer) لربط واجهة برمجة التطبيقات الموجهة للمستخدم بتدفق الرموز الفعلي للنموذج. لتحويل المدخلات إلى تدفق رموز، نستخدم [`chat_templates`](./chat_templating)، وهي موضحة في مستندات أخرى. يتناول هذا المستند النصف الآخر من تلك الطبقة الوسيطة: **Response templates**، وهو النظام الذي يعيد تحويل الرموز المولّدة من النموذج إلى قاموس استجابة مهيكلة.

بعدة طرق، تؤدي قوالب الاستجابة العملية العكسية لقوالب المحادثة. ففي قوالب المحادثة، تُدخل قائمة من الرسائل، فتحصل على رموز جاهزة للإدخال إلى النموذج. أما في قوالب الاستجابة، فتُدخل رموز المخرجات الخام من النموذج، فتحصل على رسالة منظمة. وكما هو الحال مع قوالب المحادثة، تتيح قوالب الاستجابة للمستخدمين تجاهل التفاصيل المعقدة المتعلقة بالصيغ المحددة ورموز التحكم التي يتوقعها النموذج، والاعتماد على واجهة برمجية موحدة تعتمد على قواميس الرسائل وتعمل مع أي نموذج.

أفضل طريقة لفهم قوالب الاستجابة هي رؤيتها قيد العمل. نقطة الدخول الرئيسية هي طريقة [parse_response()](/docs/transformers/main/ar/internal/tokenization_utils#transformers.PreTrainedTokenizerBase.parse_response)، والتي تقبل تسلسلاً واحداً أو دفعة:

```python
from transformers import AutoModelForCausalLM, AutoTokenizer

checkpoint = "HuggingFaceTB/SmolLM3-3B"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
model = AutoModelForCausalLM.from_pretrained(checkpoint, dtype="auto", device_map="auto")

messages = [{"role": "user", "content": "Summarize the end of the Cold War, very briefly."}]
input_ids = tokenizer.apply_chat_template(messages, add_generation_prompt=True, return_tensors="pt")["input_ids"].to(model.device)
outputs = model.generate(input_ids, max_new_tokens=1024)[0, input_ids.shape[1]:]
out_text = tokenizer.decode(outputs)
print(tokenizer.parse_response(out_text, prefix=input_ids[0]))
# Outputs a structured dict: {"role": "assistant", "reasoning_content": "...", "content": "..."}
```

عندما يحتوي مُجزِّئ الرموز على `response_template`، فإن طريقة `parse_response` تحوّل رسالة الإخراج إلى قاموس منظم بشكل نظيف، جاهز للإضافة إلى المحادثة. لاحظ أنه يجب علينا تمرير `prefix` (رموز الاستدعاء) إلى هذه الطريقة أيضاً. والسبب في ذلك أن العديد من قوالب المحادثة تبدأ الرسائل أو تفتح كتل التفكير قبل السماح للنموذج ببدء استجابته، وبالتالي يحتاج مُحلِّلنا إلى رؤية الاستدعاء لفهم الرسالة. يتم تجاهل كل ما يسبق الجولة الأخيرة من البادئة؛ فنحن نحلل رسالة واحدة في كل مرة. نحتاج فقط إلى البادئة للتأكد من أننا نرى الرسالة الأخيرة بأكملها، ولا نفوت أي حقول مُعبَّأة مسبقاً!

بما أن غياب البادئة قد يؤدي إلى تحليل خاطئ بصمت لرسالة مُعبّأة مسبقًا، فإن `prefix` إلزامي. يؤدي حذفه إلى رفع خطأ. في الحالة النادرة التي تكون فيها متأكدًا من أن التوليد يحتوي بالفعل على الرسالة الكاملة ولا يُحتاج إلى سياق بادئة، مرّر `prefix=""` (أو قائمة فارغة من معرّفات الرموز) للخروج من ذلك صراحةً.

إذا لم يكن مُجزِّئ الرموز (tokenizer) مُهيَّأً بقالب استجابة، فسيُطلق `parse_response` خطأً. نحن نعمل على إضافة قوالب لمزيد من النماذج بأسرع ما يمكن!

## تحليل الاستجابة البث

في المثال أعلاه، نقوم بتحليل استجابة النموذج دفعة واحدة بعد اكتمال التوليد. ومع ذلك، قد نرغب في كثير من الأحيان في تحليل الرسائل الجزئية أثناء توليدها، لا سيما في التطبيقات الموجهة للمستخدمين حيث لا نريد عرض صفحة ثابتة لمدة دقيقة أو دقيقتين حتى ينتهي النموذج من عمله.

عندما ترغب في التحليل التدفقي، استدعِ `tokenizer.get_response_parser()`، الذي يعيد `ResponseParser`. كما في `parse_response`، مرّر موجه المحادثة كـ `prefix=` حتى يعرف المُحلِّل أي أجزاء من الرسالة تم ملؤها مسبقًا بواسطة قالب المحادثة. الكائن المُعاد هو مُحلِّل ذو حالة يمكنك إمداده بالنص أثناء توليده من قبل النموذج:

```python
parser = tokenizer.get_response_parser(prefix=input_ids[0])
for event in parser.initial_events:
    render(event)  # Display the partial message to the user however you want to
for chunk in model_output:
    for event in parser.feed(chunk):
        render(event)
message, final_events = parser.finalize()
for event in final_events:
    render(event)
```

إذا تضمن الطلب أدوات، فمررها أيضاً (`get_response_parser(..., tools=tools)`). تُنمَّط حُجج استدعاء الأدوات من مخطط JSON Schema الخاص بالأداة المستدعية عند إغلاق كل منطقة، بحيث يرى مستهلكو البث الحُجج المنمَّطة وفق المخطط عند `region_close` بدلاً من ذلك بعد `finalize()` فقط. راجع [تنميط حُجج استدعاء الأدوات](#typing-tool-call-arguments) للتفاصيل.

سيصدر المُحلّل **أحداثاً** كنص مع تدفق النص الناتج عن عملية التوليد. وهذا يُبيّن المنطقة التي يجري توليدها حالياً. عند اكتمال المنطقة، ستُصدر في حدث منفصل مع المحتوى المُحلَّل بالكامل. وفي نهاية التوليد، تُفرغ طريقة `finalize()` أي نص متبقٍ وتصدر أي أحداث نهائية، بالإضافة إلى قاموس الرسالة الكامل.

لاحظ أنه على الرغم من أن `parse_response` يمكنه قبول دفعات (batches)، فإن التحليل المتدفق يكون دائماً تسلسلاً واحداً: يتتبع كل `ResponseParser` حالة توليد واحد. إذا كنت ترغب في بثّ عدة عمليات توليد في وقت واحد، أنشئ `ResponseParser` واحداً لكل تسلسل.

## بث الأحداث

كل حدث تحليل مُبَثّ هو قاموس (dict) بمفتاح `type`. هناك ثلاثة أنواع:

| النوع           | الوصف                                                                         | المحتويات                                                                                                                        |
|----------------|-------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------|
| `region_open`  | يشير إلى أن النموذج بدأ منطقة جديدة، مثل `content` أو `reasoning_content`. | `field` (str): اسم الحقل.                                                                                                  |
| `region_chunk` | مقطع نصي للمنطقة الحالية.                                             | `field` (str): اسم الحقل. `text` (str): المقطع الجديد. `dirty` (bool): `True` إذا كان المقطع نصًا خامًا يحتاج إلى تحليل. |
| `region_close` | يشير إلى أن منطقة ما قد انتهت، وأن المفتاح أصبح الآن نهائيًا.                | `field` (str): اسم الحقل. `value` (any): القيمة المُحلَّلة بالكامل للمنطقة                                             |

تُصدر أحداث `region_chunk` لكل منطقة عند وصول البايتات، مما يتيح لواجهة المستخدم البثية عرض التقدم حتى للمناطق المهيكلة. بالنسبة للمناطق المشابهة للنص (`text`، `int`، `float`، `bool`)، تُعلَّم المقاطع بـ `dirty=False`: فكل مقطع هو بالفعل جزء من القيمة النهائية (باستثناء المسافات البادئة التي تُحذف عند الإغلاق). أما بالنسبة للمناطق المهيكلة، مثل استدعاءات الأدوات بتنسيق JSON، فتُعلَّم المقاطع بـ `dirty=True`. وهذا يعني أن النص هو المحتوى الخام غير المُحلَّل؛ فهو آمن للعرض التدريجي، لكن القيمة *المُحلَّلة* (قاموس، قائمة، إلخ) لا تصل إلا في حدث `region_close` المقابل. في كلتا الحالتين، تحمل القيمة النهائية للم منطقة دائمًا عبر `region_close`، لذا يمكن للمستهلكين الذين لا يهتمون بالعرض الوسيط تجاهل أحداث `region_chunk` ببساطة.

إذا كتب بادئة المحادثة `prefix` أي شيء في الرسالة (على سبيل المثال، إذا فتحت القالب كتلة تفكير، أو بدأ ملء مسبق للمساعد استجابةً قبل التسليم إلى النموذج)، فإن محلل الصيغة يعرض تلك الأحداث على شكل `parser.initial_events`، وهو قائمة يمكنك إعادة تشغيلها في عارضك قبل إمداد أي مخرجات من النموذج. المناطق التي فُتحت *و* أُغلقت داخل البادئة تنتج تسلسلاً كاملاً من `region_open` / `region_chunk` / `region_close`، وتقع قيمتها المُحلَّلة في قاموس المخرجات، تماماً كما لو كان النموذج نفسه قد كتبها.

قد يبدو تدفق الأحداث النموذجي على النحو التالي:

```python
{"type": "region_open",  "field": "reasoning_content"}
{"type": "region_chunk", "field": "reasoning_content", "text": "I should ", "dirty": False}
{"type": "region_chunk", "field": "reasoning_content", "text": "greet the user", "dirty": False}
{"type": "region_close", "field": "reasoning_content", "value": "I should greet the user"}
{"type": "region_open",  "field": "tool_calls"}
{"type": "region_chunk", "field": "tool_calls", "text": '{"name": "greet_user", ', "dirty": True}
{"type": "region_chunk", "field": "tool_calls", "text": '"arguments": {"greeting": "Hi!"}}', "dirty": True}
{"type": "region_close", "field": "tool_calls", "value": {"type": "function", "function": {"name": "greet_user", "arguments": {"greeting": "Hi!"}}}}
```

لاحظ كيف يُصدر `reasoning_content` مع `dirty=False`، لأن حقولاً مثل `reasoning_content` و`content` تكون عادةً مجرد نص خام. وهذا يعني أنه يمكنك التعامل مع المقاطع كـ "إخراج جزئي" صالح. ومع ذلك، يُعلَّم `tool_calls` بأنه `dirty` لأن النص الخام يحتاج إلى تنظيف كبير - فغالباً ما تحتاج استدعاءات الأدوات إلى التحليل كـ JSON أو صيغة أخرى ثم إعادة هيكلتها لتوليد قاموس استدعاء الأداة النهائي. ونتيجةً لذلك، يبدو الإخراج النهائي لهذه المناطق مختلفاً جداً، جداً عن النص الخام. لن يحدث هذا التحليل النهائي إلا عند الوصول إلى `region_close`. الأمر متروك لك فيما تريد فعله بمقاطع `dirty` حتى ذلك الحين - يمكنك عرضها كما هي لإظهار الإخراج "الخام" للمستخدم، أو يمكنك ببساطة الانتظار حتى تحصل على شيء نظيف لعرضه.

بهذا نكون قد استعرضنا معظم ما تحتاج إلى معرفته لاستخدام قوالب الاستجابة. يركّز بقية هذا المستند على تفاصيل نظام التحليل الداخلي وكيفية كتابة قوالب الاستجابة. وهذا القسم مخصّص في الغالب للمطوّرين ومطوّري النماذج. يمكن لمعظم الأشخاص التوقف هنا بأمان!

## متقدم: كتابة قالب استجابة

أفضل طريقة لفهم كيفية كتابة قالب استجابة هي اختيار مثال محدد. فيما يلي شكل الرد الخام الذي قد يُنتجه `SmolLM`:

```txt
<think>
I should greet the user
</think>

<tool_call>{"name": "greet_user", "arguments": {"greeting": "Hi!"}}</tool_call>
```

عند تحليل هذا المخرج في صيغة قاموس الرسائل القياسية، يجب أن يبدو على النحو التالي:

```json
{
    "role": "assistant",
    "reasoning_content": "I should greet the user",
    "tool_calls": [
        {"type": "function", "function": {"name": "greet_user", "arguments": {"greeting": "Hi!"}}}
    ]
}
```

وهذه هي القالب الذي يحلله. لا تدع الأمر يرهبك - فمعظمه واضح إلى حد كبير!

```python
{
    "defaults": {"role": "assistant"},
    "start_anchor": "<|im_start|>assistant\n",
    "fields": {
        "reasoning_content": {"open": "<think>", "close": "</think>", "content": "text"},
        "tool_calls": {
            "open": "<tool_call>",
            "close": "</tool_call>",
            "repeats": True,
            "content": "json",
            "transform": {"type": "function", "function": "{content}"},
        },
        "content": {
            "close": "<|im_end|>",
            "content": "text",
        },
    },
}
```

في جوهره، يحدد القالب **الحقول** و**الفاصلات**. يتوافق كل حقل مع مفتاح في قاموس المخرجات. كما تتضمن الحقول معلومات حول تحليل النص الموجود داخل فواصلها. هناك نقطة دقيقة: لا يحتوي حقل `content` على `open`، لأنه في SmolLM (وبعض النماذج الأخرى) لا يُشار إليه برمز خاص. بدلاً من ذلك، يُخزَّن `content` في المساحة بعد المناطق الأخرى، ولكن قبل نهاية التسلسل. في قالبنا، نمثل هذا كحقل **ضمني / متبقٍ** يلتقط أي نص لم يتم ادعاؤه بواسطة منطقة أخرى.

يجب أن تتطابق أسماء الحقول مع المفاتيح التي يقرأها قالب المحادثة الخاص بالنموذج، بحيث يمكن إضافة رسالة محللة إلى المحادثة وعرضها مرة أخرى. على وجه الخصوص، سمِّ حقل الاستدلال `reasoning_content`. هذا هو المفتاح القياسي للاستدلال في قواميس الرسائل، وهو المفتاح الذي تقرأ منه معظم قوالب المحادثة الاستدلال.

بالإضافة إلى `fields`، يدعم القالب مفتاحين على المستوى الأعلى:

- `defaults` (اختياري) — قاموس بقيم مُعبّأ بها مسبقًا في المخرجات (مثل `{"role": "assistant"}`). تُحتفظ دائمًا بالمفاتيح هنا في المخرجات المُحلَّلة، حتى لو لم تكتب أي حقل إليها؛ أما المفاتيح الأخرى فتُستبعد عندما لا يلتقط حقلها أي شيء.
- `start_anchor` (str) / `start_anchor_pattern` (str regex) — يحدد موضع بداية رسالة المساعد الحالية داخل محادثة. عند تمرير `prefix=` إلى `parse_response` أو `get_response_parser`، يقوم المُحلِّل بقصّ البادئة من اليمين بعد **آخر** ظهور لهذا المرساة قبل معالجتها، حتى لا تتلوث حالة الرسالة الحالية بالدوريات السابقة في محادثة متعددة الأدوار. تُطبَّق المرساة على `prefix` فقط، ولا تُطبَّق أبدًا على الاستجابة/التوليد الذي تحلّله — فبعض الصيغ تعيد إصدارها بشكل مشروع في منتصف الرسالة (تبدأ مخرجات gpt-oss harmony كل قناة بـ `<|start|>assistant`)، لذا فإن قصّ الاستجابة بعد المرساة سيؤدي إلى حذف استدعاءات النموذج الخاصة بالاستدلال والأدوات. ولهذا السبب، لا يكفي التوليد وحده للحماية من تسرّب التاريخ: مرّر المحادثة كـ `prefix=`. بالنسبة للنماذج ذات نمط ChatML، تكون المرساة عادةً `"<|im_start|>assistant\n"`. يجب تعيين واحد فقط من `start_anchor` أو `start_anchor_pattern`.

على سبيل المثال، بالنظر إلى هذا البادئ متعدد الأدوار (لاحظ الدورين **للوكيل**):

```txt
<|im_start|>user
Hi<|im_end|>
<|im_start|>assistant
Hello!<|im_end|>
<|im_start|>user
Again?<|im_end|>
<|im_start|>assistant
```

يقوم المُحلِّل بقصّ كل ما يسبق **آخر** `<|im_start|>assistant\n`، متجاهلاً الدور السابق `"Hello!"`. القاعدة هي أن _كل شيء عدا الدور الأخير للمساعد يُستبعد دائماً._

كما هو الحال مع قوالب المحادثة، تُخزَّن قوالب الاستجابة كخصائص لمجزّئ النص (tokenizer) وتُحفظ مع المجزّئ. وعلى عكس قوالب المحادثة، نحفظها داخل `tokenizer_config.json` وليس كملف منفصل، لأن صيغتها تتناسب بشكل طبيعي مع JSON، على عكس سكربت Jinja الخاص بقالب المحادثة.

```python
tokenizer.response_template = template
tokenizer.save_pretrained(...)  # Written as a key in tokenizer_config.json
```

## متقدم: مرجع واجهة برمجة التطبيقات للحقول

يدعم كل حقل عدة مفاتيح. يمكننا تقسيمها إلى نوعين. أولاً، هناك المفاتيح التي تحدد كيفية التقاط الحقل:

| المفتاح             | النوع               | الغرض                                                                                       |
|-----------------|--------------------|-----------------------------------------------------------------------------------------------|
| `open`          | str or list[str]   | سلسلة نصية حرفية تفتح هذه المنطقة. قائمة من السلاسل النصية تعني "المطابقة مع أيٍّ من هذه".          |
| `open_pattern`  | str (regex)        | بديل `open` باستخدام التعبيرات النمطية. تصبح المجموعات المسماة متغيرات التقاط متاحة لـ `transform`.  |
| `close`         | str or list[str]   | سلسلة نصية حرفية (أو قائمة من السلاسل النصية) تُغلق هذه المنطقة. اتركها فارغة للتشغيل حتى نهاية التدفق.    |
| `close_pattern` | str (regex)        | بديل `close` باستخدام تعبيرات نمطية. تصبح المجموعات المسماة متغيرات التقاط متاحة لـ`transform`. |
| `repeats`       | bool               | إذا كانت القيمة صحيحة، يكون الحقل قائمة ويُلحق كل تطابق. الافتراضي `false`.                         |
| `join`          | str                | مع `repeats`، يتم دمج التطابقات في سلسلة واحدة باستخدام هذا الفاصل بدلاً من قائمة.    |
| `optional`      | bool               | If false and the region never matches, we raise an error. Default `true`.                     |

يجب أن يحتوي الحقل على **إما** `open` أو `open_pattern`، وليس كلاهما معاً، وينطبق الأمر نفسه على `close` و`close_pattern`.

قد يُستغنى عن `close`/`close_pattern` في حقل ما، وفي هذه الحالة يبقى المجال مفتوحاً حتى نهاية النص المولّد. وهذا مفيد لحقل نهائي يمتد حتى نهاية الرسالة.

الحقل الذي لا يحتوي على **أيٍّ من** `open` أو `open_pattern` هو الحقل **الضمني**: فهو نشط في أي وقت لا يكون فيه أي نطاق صريح مفتوحاً، لذا يلتقط النص المتبقي. لا يمكن أن يكون هناك أكثر من حقل ضمني واحد. يُستخدم هذا غالباً عندما لا يحتوي `content` على وسوم رموز خاصة، بل يُكتب كنص عادي بعد الحقول الأخرى.

بالإضافة إلى علامات الفتح والإغلاق، يمكنك أيضاً تحديد `repeats`، مما يشير إلى أن الحقل هو قائمة وأن علامات الفاصل يمكن أن تتطابق عدة مرات. وهذا شائع جداً في استدعاء الأدوات بالتوازي، عندما يصدر النموذج عدة استدعاءات للأدوات في وقت واحد:

```python
'<tool_call>{"name": "a", ...}</tool_call><tool_call>{"name": "b", ...}</tool_call>'
# Returns `"tool_calls": [{... "a" ...}, {... "b" ...}]` in a template with repeats: true
```

لكن ليس كل حقل متكرر ينبغي أن يتحول إلى قائمة. فبعض النماذج تُخرج عدة كتل من `reasoning_content` أو `content` في رسالة واحدة، والمخرج الطبيعي لذلك هو نص واحد مدموج. عند تعيين `join` (وهو فاصل نصي، وغالبًا ما يكون `""`) يتم تحويل حقل `repeats` من جمع قائمة إلى دمج تطابقاته:

```python
field = {"reasoning_content": {"open": "<think>", "close": "</think>", "repeats": True, "join": "\n"}}
input = "<think>first</think>...<think>second</think>"
# Returns: {"reasoning_content": "first\nsecond"}
```

يجب أن يُحلَّل كل تطابق لحقل `join` إلى نص. عند البث، لا يزال `region_close` يحمل القيمة الخاصة بكل كتلة؛ ولا يظهر الفاصل إلا في قاموس الرسالة النهائي.

أخيرًا، يمكنك تحديد `optional: false` للحقول التي يجب أن تكون موجودة. إذا كان مثل هذا الحقل مفقودًا، فسنُطلق خطأً بدلاً من مجرد إرجاع قاموس رسالة دون تضمينه.

عند انتهاء التوليد، سيتم إغلاق أي مناطق مفتوحة وتثبيتها، حتى لو لم يُرَ فاصل الإغلاق الخاص بها.

### تحليل محتوى حقل

بمجرد أن نحدد كيفية التقاط الحقل، نحتاج أيضًا إلى تحديد كيفية تحليل النص الخام داخل ذلك الالتقاط. هناك أربعة مفاتيح تتحكم في ذلك:

| المفتاح              | النوع         | الغرض                                                                                  |
|------------------|--------------|------------------------------------------------------------------------------------------|
| `content`        | str          | نوع المحتوى داخل هذه المنطقة. القيمة الافتراضية هي `"text"`. لكل نوع محلّل خاص به. |
| `content_args`   | dict         | الحُجج المراد تمريرها إلى محلّل المحتوى لهذه المنطقة.                            |
| `transform`      | dict/list    | قالب اختياري بعد التحليل يعيد تشكيل الجسم المُحلَّل (انظر **Transform**).          |
| `transform_each` | bool         | إذا كانت القيمة صحيحة، يجب أن يكون المحتوى المُحلَّل قائمة، ويُطبَّق `transform` على كل عنصر على حدة.       |

المفتاح الأول (والأهم) هو `content`. يشير هذا إلى نوع محتوى الحقل، وهو ما يحدد المُحلِّل الذي سيُستخدم لتحويل النص الخام المُلتقط في الحقل إلى المخرجات النهائية. تُستخدم `content_args` لضبط المُحلِّل، مما يتيح لنا دعم مختلف خصوصيات التنسيق دون الحاجة إلى كتابة مخصصة. سنلقي نظرة على كل نوع من أنواع المُحلِّلات وحواملها بالتتابع.

#### الأنواع الأساسية
`text` و`int` و`float` و`bool` هي الأنواع الأساسية. جميع أنواع المحتوى هذه تزيل المسافات البيضاء فقط، ثم تقوم بتحويل النوع البسيط إذا لزم الأمر. ولا تحتوي على أي `content_args`، باستثناء `text` الذي يدعم الحجة `strip`، والتي تزيل المسافات البيضاء من بداية النص الملتقط ونهايته، وقيمتها الافتراضية هي `true`.

```python
field = {"count": {"open": "<n>", "close": "</n>", "content": "int"}}
input = "<n> 42 </n>"
# Returns: {"count": 42}
```

#### json

يقوم محلل `json` بتحليل النص المُلتقط بصيغة JSON. وهو الأداة الأساسية لمعالجة حُجج استدعاء الأدوات وأي شيء آخر يحتوي على بنية متداخلة. كما يقبل مجموعة من `content_args` الاختيارية للتعامل مع الطرق المختلفة التي تشوّه بها النماذج صيغة JSON في الاستخدام الفعلي:

- `unquoted_keys` (bool، الافتراضي `false`): فعّل هذا الخيار عندما تكون أسماء المفاتيح خاماً بدلاً من أن تكون محاطة بعلامات اقتباس (مثال: `{name: "foo"}`). مفيد للنماذج التي تُخرج كائنات بأسلوب Javascript بدلاً من JSON الصارم.
- `string_delims` (قائمة من أزواج `[open, close]`، اختيارية): للنماذج التي تغلف القيم النصية بأحرف فاصلة مخصصة بدلاً من `"..."`. يحدد كل زوج علامة بداية وعلامة نهاية.
- `allow_non_json` (bool، القيمة الافتراضية `false`): إذا فشل التحليل، يُعاد النص الخام بعد تنظيفه بدلاً من رفع استثناء. مفيد كحل بديل للحقول التي يُصدر النموذج فيها *عادةً* صيغة JSON، لكنه أحياناً ينتقل إلى نص عادي.

يوجد كلٌّ من `unquoted_keys` و`string_delims` للتعامل مع النماذج التي تُخرج بيانات غير قياسية، وهي شبه JSON لكنها ليست كذلك تمامًا، لذا لن تحتاج إليها إلا مع عدد قليل من النماذج.

```python
field = {"args": {"open": "<args>", "close": "</args>", "content": "json", "content_args": {"unquoted_keys": True}}}
input = '<args>{city: "London"}</args>'
# Returns: {"args": {"city": "London"}}
```

#### xml-inline

يُستخدم مُحلّل `xml-inline` للمناطق المكوّنة من تسلسل مسطّح من وسوم تشبه وسوم XML، حيث يتحوّل كل وسم إلى عنصر واحد في قاموس. وغالباً ما يُستخدم داخل حقل `tool_calls` للنماذج التي تُصدر كل حجة كوسم مستقل بدلاً من كتلة JSON:

- `tag_pattern` (str، **مطلوب**): تعبير نمطي (regex) يطابق وسمًا واحدًا. يجب أن يحتوي على مجموعات مسماة `key` (مفتاح القاموس الناتج) و`value` (النص الخام الذي يصبح قيمة القاموس).
- `value_parser` (dict, اختياري): محلل محتوى متداخل يُطبَّق على كل `value` تم التقاطه. وهو قاموس يحتوي على `name` (المحلل، مثل `"json"` أو `"int"`) و`args` اختياري (أي `content_args` الخاصة به). إذا تم حذفه، تبقى القيم كنصوص خام.
- `merge_duplicates` (bool، الافتراضي `false`): عند ظهور المفتاح نفسه عدة مرات، يتم جمع القيم في قائمة بدلاً من السماح للمطابقات اللاحقة بتجاوز السابقة.

على سبيل المثال، تُصدر Qwen3 كل حجة لاستدعاء الأداة كوسم `<parameter>` مستقل، ونحللها على النحو التالي:

```python
"tool_calls": {
    "open_pattern": r"<tool_call>\s*<function=(?P<name>\w+)>",
    "close": "</tool_call>",
    "repeats": True,
    "content": "xml-inline",
    "content_args": {
        "tag_pattern": r"<parameter=(?P<key>\w+)>\s*(?P<value>.*?)\s*</parameter>",
        "value_parser": {"name": "json", "args": {"allow_non_json": True}},
    },
    "transform": {"type": "function", "function": {"name": "{name}", "arguments": "{content}"}},
}
```

لاحظ `value_parser` المتداخل: يُمرَّر كل قيمة معيار عبر مُحلِّل `json` (مع `allow_non_json` حتى تمر النصوص العادية كما هي). عند تغذية حقل `tool_calls` أعلاه بهذا المدخل:

```python
input = "<tool_call><function=get_weather><parameter=city>London</parameter><parameter=units>celsius</parameter></function></tool_call>"
# Returns: {"tool_calls": [{"type": "function", "function": {"name": "get_weather", "arguments": {"city": "London", "units": "celsius"}}}]}
```

#### kv-lines

يتعامل محلل `kv-lines` مع أزواج `key: value` مفصولة بالسطر (تخيل بيانات وصفية تشبه YAML أو ملفات `.env`). يصبح كل سطر عنصراً واحداً في القامس الناتج. جميع الحجج اختيارية:

- `line_sep` (str، الافتراضي `"\n"`): الفاصل بين الأزواج.
- `kv_sep` (str، الافتراضي `":"`): الفاصل بين المفتاح وقيمته داخل سطر واحد. يُستخدم أول ظهور فقط كنقطة فصل، لذا قد تحتوي القيم نفسها على الفاصل.
- `strip` (bool، الافتراضي `true`): إزالة المسافات البيضاء المحيطة من كل مفتاح وقيمة.
- `value_parser` (dict, اختياري): محلل محتوى متداخل يُطبَّق على كل قيمة، بنفس تنسيق `{"name": ..., "args": ...}` المستخدم في `xml-inline`. إذا تم تجاهله، تبقى القيم كنصوص خام.

تُتجاهل الأسطر الفارغة أو التي لا تحتوي على `kv_sep` بصمت، لذا يُسمح بحدوث أسطر فارغة زائدة في المنطقة الملتقطة.

```python
field = {"metadata": {"open": "<meta>", "close": "</meta>", "content": "kv-lines"}}
input = "<meta>name: alice\nage: 30</meta>"
# Returns: {"metadata": {"name": "alice", "age": "30"}}
```

ملاحظة `age` يحافظ على `"30"` كنص؛ أضف `value_parser` بقيمة `{"name": "int"}` لتحويله إلى `30`.

### نمذجة حُجج استدعاء الأدوات

في بعض الأحيان، قد يقوم محلل الاستجابة بتحليل مخرجات النموذج بالنوع الخاطئ. على سبيل المثال، قد يحلل الرقم العشري `1.5` على أنه "1.50". يمكن أن يسبب هذا مشاكل في استدعاء الأدوات، إذا كانت الأدوات تتوقع حجة بنوع معين لكنها تستلمها بنوع آخر. لتجنب ذلك، يمكنك تمرير `tools` الخاصة بالطلب إلى [parse_response()](/docs/transformers/main/ar/internal/tokenization_utils#transformers.PreTrainedTokenizerBase.parse_response) أو `ResponseParser` لتحويلها باستخدام `parameters` في مخطط JSON الخاص بكل أداة. تُقبل الأدوات بنفس الصيغة المستخدمة في [apply_chat_template()](/docs/transformers/main/ar/internal/tokenization_utils#transformers.PreTrainedTokenizerBase.apply_chat_template): مخططات JSON، أو دوال Python مع تلميحات أنواع ووثائق تُحوَّل تلقائيًا إلى مخططات.

```python
tools = [{
    "type": "function",
    "function": {
        "name": "set_alarm",
        "parameters": {
            "type": "object",
            "properties": {
                "hour": {"type": "integer"},
                "enabled": {"type": "boolean"},
                "label": {"type": "string"},
            },
        },
    },
}]
message = parse_response(model_out, template, prefix="", tools=tools)
# message["tool_calls"][0]["function"]["arguments"] ==
# {"hour": 7, "enabled": True, "label": "wake up"}
```

### تحويل

في معظم الحقول، يكون المفتاح `transform` غير ضروري. يُستخدم عندما يحتاج الجسم المُحلَّل إلى إعادة تشكيله ليصبح المخرجات النهائية، أو عندما يجب دمج المعلومات من الفواصل في النتيجة. ويظهر هذا المفتاح بشكل شائع في حقول `tool_calls`، نظراً لأن هذه الحقول غالباً ما تكون ذات بنية معقدة.

`transform` هو **قالب**: وهو قاموس (أو قائمة) يصف شكل المخرجات، حيث يتم استبدال أي نص على شكل `"{name}"` بالقيمة المقابلة. يمكن الوصول إلى القيم من `content` (جسم المنطقة المُحلَّل) ومن أي مجموعات مُسماة تم التقاطها بواسطة `open_pattern` / `close_pattern`. من الاستخدامات الشائعة جداً تغليف قاموس استدعاء الأداة داخل قاموس خارجي يحتوي على مفتاح `function`، حيث أن هذه العناصر جزء من صيغة استدعاء الأداة القياسية لدينا:

```python
"tool_calls": {
    "open": "<tool_call>",
    "close": "</tool_call>",
    "repeats": True,
    "content": "json",
    "transform": {"type": "function", "function": "{content}"},
},
```

لذا فإن هذا المخرج الخام:

```txt
<tool_call>{"name": "greet_user", "arguments": {"greeting": "Hi!"}}</tool_call>
```

becomes (ملاحظة: `repeats: True` يجعل `tool_calls` قائمة):

```json
[{"type": "function", "function": {"name": "greet_user", "arguments": {"greeting": "Hi!"}}}]
```

يعيد موضع محدد على مستوى السلسلة الكاملة مثل `"{content}"` القيمة المسترجعة مع الحفاظ على نوعها — لذا في المثال أعلاه، يُدرج القاموس JSON المُحلَّل مباشرةً كقيمة لـ `function`. يجب أن يكون الموضع هو السلسلة بأكملها: لا يُسمح بخلط النص مع المواضع (`"abc {name} def"`). فهي ليست سلاسل f-strings!

يمكن أن تتخذ العلامات الوهمية أيضًا مسارًا منقّطًا مثل `"{content.args}"`، حيث يبحث عن `content` ثم ينزل إلى مفتاح `args` في القاموس المُحلَّل. هذه هي الطريقة التي تعيد بها تشكيل أجسام استدعاءات الأدوات التي لا تتطابق أسماء مفاتيحها مع صيغتنا القياسية. على سبيل المثال، يحتاج النموذج الذي يُصدر `{"name": ..., "args": ...}` إلى إعادة تسمية `args` إلى `arguments`:

```python
"tool_calls": {
    "open": "<tool>",
    "close": "</tool>",
    "repeats": True,
    "content": "json",
    "transform": {"type": "function", "function": {"name": "{content.name}", "arguments": "{content.args}"}},
},
```

تُسبّب المسار الذي لا يمكن حله (وهنا، استدعاء أداة يفتقر جسمه إلى `args`) في رفع خطأ بدلاً من إصدار استدعاء أداة غير صالح.

يتميز `transform` بمرونة كبيرة، وهو أمر ضروري عندما يكون تنسيق مخرجات النموذج مختلفًا بشكل كبير عن واجهة برمجة التطبيقات القياسية لدينا. يُعد GPT-OSS مثالًا جيدًا على ذلك، إذ يُضمّن اسم الدالة في ترويسة القناة بدلاً من جسم JSON، لذا يجب التقاطه بمجموعة مُسماة في `open_pattern` ودمجه مع `content` داخل التحويل. تصبح جميع المجموعات المُسماة في `open_pattern` و`close_pattern` متاحة كمتغيرات إلى جانب `content`:

```python
"tool_calls": {
    "open_pattern": r"<\|channel\|>commentary to=functions\.(?P<name>\w+).*?<\|message\|>",
    "close": "<|call|>",
    "repeats": True,
    "content": "json",
    "transform": {"type": "function", "function": {"name": "{name}", "arguments": "{content}"}},
},
```

يوجد اسم الدالة في ترويسة القناة وليس في جسم JSON، لذا فإن هذا:

```txt
<|channel|>commentary to=functions.get_current_weather <|constrain|>json<|message|>{"location": "San Francisco, CA"}<|call|>
```

يصبح:

```json
[{"type": "function", "function": {"name": "get_current_weather", "arguments": {"location": "San Francisco, CA"}}}]
```

في بعض الأحيان يكون المحتوى المُحلَّل لحقلٍ ما *هو نفسه* قائمة من السجلات، وتريد إعادة تشكيل كل عنصر منها. يُعد قالب Cohere مثالاً جيداً على ذلك: فهو يُخرج جميع استدعاءات الأدوات داخل مصفوفة JSON واحدة، لذا نضبط `transform_each: True` لتطبيق التحويل على كل عنصر على حدة. يتم تفكيك مفاتيح كل عنصر في المصفوفة إلى نطاق القالب، لذا يبحث `"{tool_name}"` عن `tool_name` في العنصر الحالي:

```python
"tool_calls": {
    "open": "<|START_ACTION|>",
    "close": "<|END_ACTION|>",
    "content": "json",
    "transform_each": True,
    "transform": {"type": "function", "function": {"name": "{tool_name}", "arguments": "{parameters}"}},
},
```

سيؤدي هذا إلى تحويل مخرجات على النحو التالي:

```json
[
    {"tool_name": "greet_user", "parameters": {"greeting": "Hi!"}},
    {"tool_name": "search", "parameters": {"query": "weather tomorrow"}}
]
```

إلى مخرجات على هذا النحو، والتي تتوافق مع واجهة برمجة التطبيقات القياسية لدينا:

```json
[
    {"type": "function", "function": {"name": "greet_user", "arguments": {"greeting": "Hi!"}}},
    {"type": "function", "function": {"name": "search", "arguments": {"query": "weather tomorrow"}}}
]
```

لا يُستلزم علم `transform_each` إلا عندما يكون `content` قائمة بالفعل؛ أما في الحالة الأكثر شيوعاً حيث يساهم كل تطابق بعنصر واحد (وتتراكم العناصر بواسطة `repeats: True`)، فإن التحويل يُطبَّق على كل عنصر افتراضياً.

## مطوّرو الإطارات: قابلية نقل تعبيرات النمط

`open_pattern` و`close_pattern` و`start_anchor_pattern` هي نصوص تعبيرات نمطية (regex). بالنسبة لمعظم المستخدمين، وحتى لمعظم مؤلفي النماذج، لا ينبغي أن يكون هذا مشكلة، ولكن إذا كنت مطوّرًا تكتب تنفيذًا لتحليل الاستجابات بلغة أخرى، فيجب أن تكون على دراية بتفاصيل التنفيذ لدينا. هذا القسم مخصص لكل من اضطر إلى تنفيذ محلّل Jinja كامل لجعل قوالب المحادثة غير Python تعمل - نأمل أنه إذا اتبعت الإرشادات البسيطة أدناه، فستكون قوالب الاستجابة أقل إزعاجًا بكثير:

- نستخدم وحدة [`regex`](https://pypi.org/project/regex/) في Python لجميع تعبيرات النمط المستخدمة في تحليل المحادثات. وبما أن جميع السلاسل النصية في Python3 هي من نوع unicode، فإن **جميع تطابقات تعبيرات النمط لدينا تدعم unicode**. وهذا يؤثر بشكل خاص على الأحرف الشائعة مثل `\w`. تأكد من ضبط أعلام unicode ذات الصلة في محركك.
- نجمع جميع تعبيرات النمط المنتظم مع تفعيل `re.DOTALL` وتعطيل `re.MULTILINE`، لذا يطابق `.` الرمز `\n` بينما يطابق `^` و`$` بداية/نهاية المدخلات بأكملها فقط، وليس نهايات الأسطر.
- نستخدم صيغة `(?P<name>...)` للمجموعات المسماة. تختلف صيغة مجموعات الالتقاط المسماة بشكل كبير في تطبيقات regex الأخرى، لذا قد تحتاج إلى البحث عن هذا النمط في تعبيرات regex وإعادة كتابته ليتوافق مع تطبيقك المحلي.
- نستخدم المطابقة الجزئية للتعبيرات النمطية (regex) لتحديد متى يمكننا إصدار البيانات. وهذا ضروري لأن نهاية المنطقة قد تتكون من عدة رموز (tokens)، ولا نريد إصدار فاصل نهاية المنطقة، لذا نحتفظ بالرموز حتى نتأكد من أنها داخل المنطقة وليست جزءًا من الحد الفاصل. وهذا يعني أنه كلما كانت هناك مطابقة جزئية لتعبير نمطي لنهاية منطقة، نحتفظ بالبيانات حتى يتطابق التعبير النمطي أو يفشل. إذا لم يدعم محرك التعبيرات النمطية الخاص بك المطابقة الجزئية، يمكنك ما زال تنفيذ قوالب الاستجابة، لكن قد تحتاج إلى إيجاد حل آخر لهذه المشكلة. إحدى البسيطة هي الاحتفاظ بهذه المناطق / إصدارها كـ`dirty` حتى ترى فاصل النهاية بشكل قاطع.
- قد لا يدعم محرك التعبيرات النمطية الخاص بك (في حالات نادرة) عمليات الفحص المسبق والمسبق مثل `(?!...)`. وعلى الرغم من أن هذه العمليات لا تُستخدم بشكل شائع في قوالب الاستجابة، إلا أنها قد تظهر، ونحن ندعمها! قد تحتاج إلى إلقاء خطأ في تلك الحالات، أو استخراج عمليات الفحص يدوياً وفرضها في الكود الخاص بك عندما يجد محرك التعبيرات النمطية تطابقاً محتملاً.
- الميزات المتقدمة الأخرى مثل المراجع الخلفية (backreferences) والمجموعات الذرية (atomic groups) والمعدِّلات الحصرية (possessive quantifiers) والتكرار الذاتي (recursion) وما إلى ذلك، لا تُستخدم عادةً في قوالب الاستجابة. سنحاول تثبيط همة مطوّري النماذج عن استخدامها، لذا يمكنك على الأرجح تجاهلها بأمان.

