تقييد نماذج LLM باستخدام إخراج مهيكل: Ollama، Qwen3 وPython أو Go

بضع طرق للحصول على إخراج منظم من Ollama

Page content

النماذج الكبيرة للغة (LLMs) قوية، ولكن في الإنتاج نادراً ما نريد فقرات حرة. بدلاً من ذلك، نريد بيانات قابلة للتنبؤ: خصائص، حقائق، أو كائنات منظمة يمكنك إدخالها في تطبيق. هذا هو مخرجات LLM المنظمة.

في بعض الوقت مضى أدخل Ollama دعم المخرجات المنظمة (إعلان)، مما يجعل من الممكن تقييد مخرجات النموذج لتتوافق مع مخطط JSON. هذا يفتح خطوط إنتاج بيانات متسقة لمهام مثل تدوين خصائص LLM، وتقييم النماذج، أو تلقين تكامل النظام.

الديوك في صف

في هذه المقالة، سنغطي:

  • ما هو المخرج المنظم ولماذا يهم
  • طريقة بسيطة للحصول على مخرج منظم من LLM
  • كيف تعمل ميزة جديدة من Ollama
  • أمثلة على استخراج قدرات LLM:

ما هو المخرج المنظم؟

بشكل طبيعي، تولد LLM النصوص الحرة:

“يدعم النموذج X التفكير بالتفكير التسلسلي، ويحتوي على نافذة سياقية بحجم 200K، ويتحدث الإنجليزية والصينية والإسبانية.”

هذا قابل للقراءة، لكنه صعب التحليل.

بدلاً من ذلك، مع المخرج المنظم نطلب نموذجًا صارمًا:

{
  "name": "Model X",
  "supports_thinking": true,
  "max_context_tokens": 200000,
  "languages": ["English", "Chinese", "Spanish"]
}

هذا JSON سهل التحقق منه، تخزينه في قاعدة بيانات، أو إدخاله في واجهة المستخدم.


طريقة بسيطة للحصول على مخرج منظم من LLM

أحيانًا تفهم LLM ما هو النموذج وتستطيع طلبها إرجاع النتيجة في JSON باستخدام نموذج معين. نموذج Qwen3 من علي بابا محسّن للتفكير والردود المنظمة. يمكنك إخباره صراحةً بـ الرد في JSON.

مثال 1: استخدام Qwen3 مع ollama في Python، طلب JSON مع نموذج

import json
import ollama

prompt = """
أنت مُستخرج بيانات منظمة.
ارجع JSON فقط.
النص: "إيلون ماسك يبلغ من العمر 53 عامًا ويقيم في أوتر."
النموذج: { "name": string, "age": int, "city": string }
"""

response = ollama.chat(model="qwen3", messages=[{"role": "user", "content": prompt}])
output = response['message']['content']

# تحليل JSON
try:
    data = json.loads(output)
    print(data)
except Exception as e:
    print("خطأ في تحليل JSON:", e)

الناتج:

{"name": "إيلون ماسك", "age": 53, "city": "أوتر"}

إجبار التحقق من النموذج باستخدام Pydantic

لتجنب النتائج غير المكتملة، يمكنك التحقق من نموذج Pydantic في Python.

from pydantic import BaseModel

class Person(BaseModel):
    name: str
    age: int
    city: str

# افترض أن 'output' هي سلسلة JSON من Qwen3
data = Person.model_validate_json(output)
print(data.name, data.age, data.city)

هذا يضمن أن الناتج يتوافق مع الهيكل المتوقع.


المخرج المنظم من Ollama

يسمح Ollama الآن بمرفق نموذج في معلمة format. يتم تقييد النموذج للرد فقط في JSON المتوافق مع النموذج (وثائق).

في Python، عادةً ما تعرّف نموذجك باستخدام Pydantic وترسله إلى Ollama كنموذج JSON.


مثال 2: استخراج معلومات خصائص LLM

لنفترض أن لديك نصًا وصفًا لخصائص LLM:

“يتمتع Qwen3 بدعم متعدد اللغات (الإنجليزية، الصينية، الفرنسية، الإسبانية، العربية). يسمح بالتفكير التسلسلي (التفكير التسلسلي). نافذة السياق هي 128K رموز.”

تريد بيانات منظمة:

from pydantic import BaseModel
from typing import List
from ollama import chat

class LLMFeatures(BaseModel):
    name: str
    supports_thinking: bool
    max_context_tokens: int
    languages: List[str]

prompt = """
تحليل الوصف التالي وارجع خصائص النموذج في JSON فقط.
وصف النموذج:
'Qwen3 يتمتع بدعم متعدد اللغات (الإنجليزية، الصينية، الفرنسية، الإسبانية، العربية).
يسمح بالتفكير التسلسلي (التفكير التسلسلي).
نافذة السياق هي 128K رموز.'
"""

resp = chat(
    model="qwen3",
    messages=[{"role": "user", "content": prompt}],
    format=LLMFeatures.model_json_schema(),
    options={"temperature": 0},
)

print(resp.message.content)

الناتج المحتمل:

{
  "name": "Qwen3",
  "supports_thinking": true,
  "max_context_tokens": 128000,
  "languages": ["الإنجليزية", "الصينية", "الفرنسية", "الإسبانية", "العربية"]
}

مثال 3: مقارنة نماذج متعددة

أدخل وصفًا لعدد من النماذج واستخرجها إلى شكل منظم:

from typing import List

class ModelComparison(BaseModel):
    models: List[LLMFeatures]

prompt = """
استخرج خصائص كل نموذج إلى JSON.

1. Llama 3.1 يدعم التفكير. نافذة السياق هي 128K. اللغات: الإنجليزية فقط.
2. GPT-4 Turbo يدعم التفكير. نافذة السياق هي 128K. اللغات: الإنجليزية، اليابانية.
3. Qwen3 يدعم التفكير. نافذة السياق هي 128K. اللغات: الإنجليزية، الصينية، الفرنسية، الإسبانية، العربية.
"""

resp = chat(
    model="qwen3",
    messages=[{"role": "user", "content": prompt}],
    format=ModelComparison.model_json_schema(),
    options={"temperature": 0},
)

print(resp.message.content)

الناتج:

{
  "models": [
    {
      "name": "Llama 3.1",
      "supports_thinking": true,
      "max_context_tokens": 128000,
      "languages": ["الإنجليزية"]
    },
    {
      "name": "GPT-4 Turbo",
      "supports_thinking": true,
      "max_context_tokens": 128000,
      "languages": ["الإنجليزية", "اليابانية"]
    },
    {
      "name": "Qwen3",
      "supports_thinking": true,
      "max_context_tokens": 128000,
      "languages": ["الإنجليزية", "الصينية", "الفرنسية", "الإسبانية", "العربية"]
    }
  ]
}

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


مثال 4: اكتشاف الثغرات تلقائيًا

يمكنك حتى السماح بقيم null عندما يكون الحقل مفقودًا:

from typing import Optional

class FlexibleLLMFeatures(BaseModel):
    name: str
    supports_thinking: Optional[bool]
    max_context_tokens: Optional[int]
    languages: Optional[List[str]]

هذا يضمن أن نموذجك يظل صحيحًا حتى لو كانت بعض المعلومات غير معروفة.


الفوائد، التحذيرات والممارسات الجيدة

استخدام المخرج المنظم عبر Ollama (أو أي نظام يدعمه) يوفر العديد من المزايا - لكنه أيضًا يحتوي على بعض التحذيرات.

الفوائد

  • ضمانات أقوى: يتم طلب النموذج للتوافق مع مخطط JSON بدلًا من النصوص الحرة.
  • تحليل أسهل: يمكنك تحميل json.loads مباشرة أو التحقق من Pydantic / Zod بدلًا من التعبيرات العادية أو الاستنتاجات.
  • التطور بناءً على النموذج: يمكنك إصدار نماذجك، إضافة الحقول (مع القيم الافتراضية)، والحفاظ على التوافق الخلفي.
  • التوافق: تنتظر الأنظمة التالية بيانات منظمة.
  • الحتمية (أفضل مع درجة حرارة منخفضة): عندما تكون درجة الحرارة منخفضة (مثلاً 0)، يكون النموذج أكثر احتمالًا للالتزام الصارم بالنموذج. توصي وثائق Ollama بذلك.

التحذيرات والمخاطر

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

الممارسات الجيدة والنصائح (مأخوذة من مدونة Ollama + الخبرة)

  • استخدم Pydantic (Python) أو Zod (JavaScript) لتحديد نماذجك وتحديث JSON تلقائيًا. هذا يتجنب الأخطاء اليدوية.
  • اشطب دائمًا تعليمات مثل “ارجع فقط JSON” أو “لا تشمل التعليقات أو النصوص الإضافية” في مطالبتك.
  • استخدم درجة حرارة = 0 (أو منخفضة جدًا) لتقليل العشوائية وزيادة الالتزام بالنموذج. توصي وثائق Ollama بالحتمية.
  • التحقق من JSON وربما الانتقال (مثلاً إعادة المحاولة أو تنظيفها) عند فشل تحليل JSON أو فشل التحقق من النموذج.
  • ابدأ بنموذج بسيط، ثم توسّع تدريجيًا. لا تتعقيد في البداية.
  • اشطب تعليمات مساعدة ولكن محدودة: مثلاً، إذا لم يمكن للنموذج ملء حقل مطلوب، ارجع بـ null بدلًا من حذفه (إذا سمح نموذجك بذلك).

مثال Go 1: استخراج خصائص LLM

هناك برنامج Go بسيط Go يطلب من Qwen3 مخرجًا منظمًا حول خصائص LLM.

package main

import (
	"context"
	"encoding/json"
	"fmt"
	"log"

	"github.com/ollama/ollama/api"
)

type LLMFeatures struct {
	Name             string   `json:"name"`
	SupportsThinking bool     `json:"supports_thinking"`
	MaxContextTokens int      `json:"max_context_tokens"`
	Languages        []string `json:"languages"`
}

func main() {
	client, err := api.ClientFromEnvironment()
	if err != nil {
		log.Fatal(err)
	}

	prompt := `
  تحليل الوصف التالي وارجع خصائص النموذج في JSON فقط.
  الوصف:
  "Qwen3 يتمتع بدعم متعدد اللغات (الإنجليزية، الصينية، الفرنسية، الإسبانية، العربية).
  يسمح بالتفكير التسلسلي (التفكير التسلسلي).
  نافذة السياق هي 128K رموز."
  `

	// تحديد مخطط JSON للمخرج المنظم
	formatSchema := map[string]any{
		"type": "object",
		"properties": map[string]any{
			"name": map[string]string{
				"type": "string",
			},
			"supports_thinking": map[string]string{
				"type": "boolean",
			},
			"max_context_tokens": map[string]string{
				"type": "integer",
			},
			"languages": map[string]any{
				"type": "array",
				"items": map[string]string{
					"type": "string",
				},
			},
		},
		"required": []string{"name", "supports_thinking", "max_context_tokens", "languages"},
	}

	// تحويل النموذج إلى JSON
	formatJSON, err := json.Marshal(formatSchema)
	if err != nil {
		log.Fatal("فشل تحويل النموذج:", err)
	}

	req := &api.GenerateRequest{
		Model:   "qwen3:8b",
		Prompt:  prompt,
		Format:  formatJSON,
		Options: map[string]any{"temperature": 0},
	}

	var features LLMFeatures
	var rawResponse string
	err = client.Generate(context.Background(), req, func(response api.GenerateResponse) error {
		// تراكم المحتوى أثناء البث
		rawResponse += response.Response

		// فقط تحليل عند اكتمال الاستجابة
		if response.Done {
			if err := json.Unmarshal([]byte(rawResponse), &features); err != nil {
				return fmt.Errorf("خطأ في تحليل JSON: %v", err)
			}
		}
		return nil
	})
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("الهيكل المُحلل: %+v\n", features)
}

لคอมيل وتشغيل هذا المثال من برنامج Go - لنفترض أن لدينا ملف main.go في مجلد ollama-struct، نحتاج إلى تنفيذ داخل هذا المجلد:

# تهيئة المودول
go mod init ollama-struct
# سحب جميع الاعتماديات
go mod tidy
# بناء وتشغيل
go build -o ollama-struct main.go
./ollama-struct

مثال الناتج

الهيكل المُحلل: {Name:Qwen3 SupportsThinking:true MaxContextTokens:128000 Languages:[الإنجليزية الصينية الفرنسية الإسبانية العربية]}

مثال Go 2: مقارنة نماذج متعددة

يمكنك توسيع هذا لاستخراج قائمة من النماذج للمقارنة.

  type ModelComparison struct {
		Models []LLMFeatures `json:"models"`
	}

	prompt = `
	استخراج خصائص من الوصف التالي للنماذج وارجعها كـ JSON:

	1. PaLM 2: هذا النموذج لديه قدرات تفكير محدودة ويتركز على فهم اللغة الأساسية. يدعم نافذة سياقية بحجم 8,000 رموز. يدعم اللغة الإنجليزية فقط.
	2. LLaMA 2: هذا النموذج لديه قدرات تفكير معتدلة ويمكنه التعامل مع بعض المهام المنطقية. يمكنه معالجة ما يصل إلى 4,000 رموز في سياقه. يدعم اللغات الإنجليزية، الإسبانية، واللغة الإيطالية.
	3. Codex: هذا النموذج لديه قدرات تفكير قوية بشكل خاص في البرمجة وتحليل الكود. لديه نافذة سياقية بحجم 16,000 رموز. يدعم اللغات الإنجليزية، بايثون، جافاسكربت، وجافا.

	ارجع كائن JSON مع مصفوفة "models" تحتوي على جميع النماذج.
	`

	// تحديد مخطط JSON للمقارنة بين النماذج
	comparisonSchema := map[string]any{
		"type": "object",
		"properties": map[string]any{
			"models": map[string]any{
				"type": "array",
				"items": map[string]any{
					"type": "object",
					"properties": map[string]any{
						"name": map[string]string{
							"type": "string",
						},
						"supports_thinking": map[string]string{
							"type": "boolean",
						},
						"max_context_tokens": map[string]string{
							"type": "integer",
						},
						"languages": map[string]any{
							"type": "array",
							"items": map[string]string{
								"type": "string",
							},
						},
					},
					"required": []string{"name", "supports_thinking", "max_context_tokens", "languages"},
				},
			},
		},
		"required": []string{"models"},
	}

	// تحويل النموذج إلى JSON
	comparisonFormatJSON, err := json.Marshal(comparisonSchema)
	if err != nil {
		log.Fatal("فشل تحويل مخطط المقارنة:", err)
	}

	req = &api.GenerateRequest{
		Model:   "qwen3:8b",
		Prompt:  prompt,
		Format:  comparisonFormatJSON,
		Options: map[string]any{"temperature": 0},
	}

	var comp ModelComparison
	var comparisonResponse string
	err = client.Generate(context.Background(), req, func(response api.GenerateResponse) error {
		// تراكم المحتوى أثناء البث
		comparisonResponse += response.Response

		// فقط تحليل عند اكتمال الاستجابة
		if response.Done {
			if err := json.Unmarshal([]byte(comparisonResponse), &comp); err != nil {
				return fmt.Errorf("خطأ في تحليل JSON: %v", err)
			}
		}
		return nil
	})
	if err != nil {
		log.Fatal(err)
	}

	for _, m := range comp.Models {
		fmt.Printf("%s: Context=%d, Languages=%v\n", m.Name, m.MaxContext_tokens, m.Languages)
	}

مثال الناتج

PaLM 2: Context=8000, Languages=[الإنجليزية]
LLaMA 2: Context=4000, Languages=[الإنجليزية الإسبانية الإيطالية]
Codex: Context=16000, Languages=[الإنجليزية بايثون جافاسكربت جافا]

بالمناسبة، qwen3:4b يعمل جيدًا في هذه الأمثلة، بنفس طريقة qwen3:8b.

الممارسات الجيدة للمطورين في Go

  • ضع درجة حرارة 0 للالتزام الأقصى بالنموذج.
  • تحقق من json.Unmarshal واتبع المخطط إذا فشل تحليل JSON.
  • احتفظ بالنموذج بسيطًا - قد تسبب الهيكل JSON المعقد أو المتكرر مشاكل.
  • اسمح بالحقول الاختيارية (استخدم omitempty في علامات Go) إذا كنت تتوقع بيانات مفقودة.
  • أضف محاولات إعادة إذا أصدر النموذج أحيانًا JSON غير صحيح.

مثال كامل - رسم مخطط باستخدام مواصفات LLM (خطوة بخطوة: من JSON منظم إلى جداول مقارنة)

llm-chart

  1. تحديد نموذج للبيانات التي تريدها

استخدم Pydantic حتى تتمكن من (أ) إنشاء مخطط JSON لـ Ollama و (ب) التحقق من استجابة النموذج.

from pydantic import BaseModel
from typing import List, Optional

class LLMFeatures(BaseModel):
    name: str
    supports_thinking: bool
    max_context_tokens: int
    languages: List[str]
  1. اطلب من Ollama إرجاع فقط JSON في هذا الشكل

مرر النموذج في format= وخفض درجة الحرارة للحصول على حتمية.

from ollama import chat

prompt = """
استخراج خصائص كل نموذج. ارجع JSON فقط مطابقًا للنموذج.
1) Qwen3 يدعم التفكير التسلسلي؛ 128K سياق؛ الإنجليزية، الصينية، الفرنسية، الإسبانية، العربية.
2) Llama 3.1 يدعم التفكير التسلسلي؛ 128K سياق؛ الإنجليزية.
3) GPT-4 Turbo يدعم التفكير التسلسلي؛ 128K سياق؛ الإنجليزية، اليابانية.
"""

resp = chat(
    model="qwen3",
    messages=[{"role": "user", "content": prompt}],
    format={"type": "array", "items": LLMFeatures.model_json_schema()},
    options={"temperature": 0}
)

raw_json = resp.message.content  # قائمة JSON من LLMFeatures
  1. التحقق والتوحيد

تحقق دائمًا قبل استخدامه في الإنتاج.

from pydantic import TypeAdapter

adapter = TypeAdapter(list[LLMFeatures])
models = adapter.validate_json(raw_json)  # -> list[LLMFeatures]
  1. إنشاء جدول مقارنة (pandas)

حوّل كائناتك المُحقق منها إلى DataFrame يمكنك ترتيبها وفلترها وتصديرها.

import pandas as pd

df = pd.DataFrame([m.model_dump() for m in models])
df["languages_count"] = df["languages"].apply(len)
df["languages"] = df["languages"].apply(lambda xs: ", ".join(xs))

# إعادة ترتيب الأعمدة لتحسين القابلية للقراءة
df = df[["name", "supports_thinking", "max_context_tokens", "languages_count", "languages"]]

# حفظ كـ CSV للاستخدام المستقبلي
df.to_csv("llm_feature_comparison.csv", index=False)
  1. (اختياري) رؤية بسيطة

الرسوم البيانية البسيطة تساعدك على ملاحظة الفروقات بين النماذج بسرعة.

import matplotlib.pyplot as plt

plt.figure()
plt.bar(df["name"], df["max_context_tokens"])
plt.title("نافذة السياق القصوى حسب النموذج (رموز)")
plt.xlabel("النموذج")
plt.ylabel("رموز السياق القصوى")
plt.xticks(rotation=20, ha="right")
plt.tight_layout()
plt.savefig("max_context_window.png")

TL;DR

مع دعم Ollama الجديد للمخرج المنظم، يمكنك معاملة LLMs ليس فقط كـ chatbots بل كـ محركات استخراج البيانات.

أظهرت الأمثلة أعلاه كيفية استخراج تلقائيًا بيانات منظمة حول خصائص LLM مثل دعم التفكير، حجم نافذة السياق، واللغات المدعومة - مهام كانت ستتطلب تحليلًا هشًا في السابق.

سواء كنت تبني قائمة نماذج LLM، أو لوحة تحكم تقييم، أو مساعد بحثي ذكاء اصطناعي، فإن المخرجات المنظمة تجعل التكامل سلسًا، موثوقًا، ومستعدًا للإنتاج.

روابط مفيدة