DeepSeek Harness: تطوير أداتك الأولى: defineTool

آخر تحديث: 2026-08-31

الأدوات هي يداه الوكيل — تعريف أداة يُعطي الوكيل قدرة جديدة. defineTool هو DSL تعريف الأدوات في DSH، يستخدم نهجاً تصريحياً لوصف اسم الأداة ومُعاملاتها ومنطق تنفيذها، حتى يفهم الوكيل متى وكيف يستدعي أداتك.

💡 نصيحة: جوهر defineTool هو "جعل LLM يفهم أداتك" — name و description للـ LLM، parameters تصف صيغة الإدخال، و execute هو التنفيذ الفعلي. اكتب أوصافاً جيدة حتى يختار الوكيل أداتك في السيناريوهات الصحيحة.

📋 المتطلبات المسبقة: أكمل 14-inject.md، تفهم حقن الاعتماديات

1. ما ستتعلمه

خط أنابيب الأدوات


2. صيغة defineTool DSL

(1) ▶ مثال 1

TYPESCRIPT
import { defineTool } from '@deepseek-ai/dsh'

export default defineTool({
  name: 'tool_name',
  description: 'What this tool does',
  parameters: {
    type: 'object',
    properties: { /* ... */ },
    required: [ /* ... */ ]
  },
  async execute(params, ctx) {
    // Tool logic
    return result
  }
})

(2) وصف الحقول

الحقل النوع مطلوب الوصف
name string المُعرّف الفريد للأداة، أحرف صغيرة + شرطات سفلية
description string وصف وظيفة الأداة، ليقرأه LLM
parameters JSON Schema تعريف المُعاملات
execute function منطق التنفيذ

(3) طرق التصدير

defineTool يُعيد كائن تعريف أداة يمكن استخدامه مباشرة كتصدير افتراضي:

TYPESCRIPT
// الطريقة 1: التصدير الافتراضي
export default defineTool({ ... })

// الطريقة 2: التصدير المُسمّى
export const myTool = defineTool({ ... })

3. name و description

(1) اصطلاح التسمية

أسماء الأدوات تستخدم صيغة snake_case:

TYPESCRIPT
name: 'file_count'        // ✅
name: 'fileCount'         // ❌ غير مُوصى به
name: 'FileCount'         // ❌ غير مُوصى به
name: 'file-count'        // ❌ غير مُوصى به

(2) كتابة الوصف

الوصف هو الأساس لاختيار LLM للأداة. النقاط الأساسية:

TYPESCRIPT
// ❌ وصف سيء
description: 'A tool for testing'

// ✅ وصف جيد
description: 'Count the number of files in a directory. Returns total count and breakdown by file extension. Use when user asks about file statistics or directory contents.'

(3) الوصف متعدد اللغات

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


4. تعريف مُعاملات JSON Schema

(1) الهيكل الأساسي

المُعاملات تتبع مواصفات JSON Schema:

TYPESCRIPT
parameters: {
  type: 'object',
  properties: {
    param_name: {
      type: 'string',
      description: 'Parameter description'
    }
  },
  required: ['param_name']
}

(2) الأنواع المدعومة

نوع JSON Schema نوع TypeScript الوصف
string string سلسلة
number number عدد
integer number عدد صحيح
boolean boolean منطقي
object object كائن متداخل
array array مصفوفة

(3) مُعاملات السلسلة

TYPESCRIPT
properties: {
  path: {
    type: 'string',
    description: 'Directory path to count files in'
  },
  pattern: {
    type: 'string',
    description: 'Glob pattern to filter files (e.g. *.ts)',
    default: '*'
  }
}

(4) مُعاملات التعداد

TYPESCRIPT
properties: {
  sort_by: {
    type: 'string',
    enum: ['name', 'size', 'date'],
    description: 'Sort criteria'
  }
}

(5) مُعاملات المصفوفة

TYPESCRIPT
properties: {
  extensions: {
    type: 'array',
    items: { type: 'string' },
    description: 'File extensions to include (e.g. [".ts", ".js"])'
  }
}

(6) الكائنات المتداخلة

TYPESCRIPT
properties: {
  options: {
    type: 'object',
    properties: {
      recursive: { type: 'boolean', default: false },
      includeHidden: { type: 'boolean', default: false }
    }
  }
}

(7) حقل required

TYPESCRIPT
required: ['path']          // path مطلوب
// pattern له قيمة افتراضية، غير مطلوب

5. تنفيذ دالة execute

(1) توقيع الدالة

TYPESCRIPT
async execute(params: Params, ctx: Context): Promise<Result>

(2) ▶ مثال 2

TYPESCRIPT
async execute({ path }, ctx) {
  const count = await countFiles(path)
  return { count, path }
}

(3) الوصول للخدمات

داخل execute، يمكنك الوصول للخدمات المُسجّلة عبر ctx:

TYPESCRIPT
export const inject = ['fs']

export default defineTool({
  name: 'file_count',
  // ...
  async execute({ path }, ctx) {
    const files = await ctx.fs.readdir(path)
    return { count: files.length }
  }
})

(4) معالجة الأخطاء

TYPESCRIPT
async execute({ path }, ctx) {
  try {
    const files = await ctx.fs.readdir(path)
    return { count: files.length, path }
  } catch (error) {
    return {
      error: true,
      message: `Failed to read directory: ${error.message}`
    }
  }
}

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

(5) صيغة القيمة المُعادة

يُوصى بإعادة كائنات مهيكلة:

TYPESCRIPT
// ✅ إعادة مهيكلة
return {
  count: 42,
  path: '/home/alice/project',
  breakdown: {
    typescript: 28,
    javascript: 10,
    other: 4
  }
}

// ❌ إعادة نص عادي
return 'Found 42 files in /home/alice/project'

الإعادة المهيكلة تتيح للوكيل استخدام النتائج برمجياً، بدلاً من عرض النص فقط.


6. تسجيل الأداة في ctx.tools

(1) التسجيل في إضافة

الأدوات المُعرّفة بـ defineTool تحتاج للتسجيل في ctx.tools عبر إضافة:

TYPESCRIPT
import { Context } from '@deepseek-ai/cordis'
import { defineTool } from '@deepseek-ai/dsh'

const fileCountTool = defineTool({
  name: 'file_count',
  description: 'Count files in a directory',
  parameters: {
    type: 'object',
    properties: {
      path: { type: 'string', description: 'Directory path' }
    },
    required: ['path']
  },
  async execute({ path }, ctx) {
    const files = await ctx.fs.readdir(path)
    return { count: files.length }
  }
})

export const name = 'tool-file-count'
export const inject = ['tools', 'fs']

export function apply(ctx: Context) {
  ctx.tools.register(fileCountTool)
}

(2) تسجيل أدوات متعددة

TYPESCRIPT
export function apply(ctx: Context) {
  ctx.tools.register(fileCountTool)
  ctx.tools.register(fileSizeTool)
  ctx.tools.register(fileSearchTool)
}

(3) التسجيل الديناميكي

TYPESCRIPT
export function apply(ctx: Context) {
  const tools = [fileCountTool, fileSizeTool]
  
  for (const tool of tools) {
    ctx.tools.register(tool)
    ctx.logger.info(`registered tool: ${tool.name}`)
  }
}

7. عرض الأداة في واجهة الويب

(1) العرض التلقائي

الأدوات المُسجّلة في ctx.tools تظهر تلقائياً في قائمة الأدوات بواجهة الويب:

TEXT 📖 للعرض فقط
┌─────────────────────────────────────┐
│ 🔧 Tools                            │
├─────────────────────────────────────┤
│ file_count  │ Count files in a dir  │
│ file_size   │ Get file size info    │
│ file_search │ Search for files      │
└─────────────────────────────────────┘

(2) عرض name و description

(3) عرض استدعاء الوكيل

عندما يستدعي الوكيل أداتك، واجهة الويب تعرض:

TEXT 📖 للعرض فقط
🤖 Agent:
🔧 Using tool: file_count
  → path: /home/alice/project
  
  Result: { count: 42, path: "/home/alice/project" }

▶ مثال 8: أداة عدّ الملفات

اجمع كل المعرفة في إضافة أداة كاملة:

TYPESCRIPT
import { Context } from '@deepseek-ai/cordis'
import { defineTool } from '@deepseek-ai/dsh'

export const name = 'tool-file-count'
export const inject = ['tools', 'fs']

const fileCountTool = defineTool({
  name: 'file_count',
  description: 'Count the number of files in a directory. Returns total count and breakdown by file extension. Use when user asks about file statistics or directory contents.',
  parameters: {
    type: 'object',
    properties: {
      path: {
        type: 'string',
        description: 'Absolute or relative path to the directory'
      },
      recursive: {
        type: 'boolean',
        description: 'Whether to count files in subdirectories',
        default: false
      },
      extensions: {
        type: 'array',
        items: { type: 'string' },
        description: 'Filter by file extensions (e.g. [".ts", ".js"]). Count all if omitted.'
      }
    },
    required: ['path']
  },
  async execute({ path, recursive, extensions }, ctx) {
    try {
      const entries = recursive
        ? await ctx.fs.readdirRecursive(path)
        : await ctx.fs.readdir(path)

      let files = entries.filter(e => !e.isDirectory)

      if (extensions && extensions.length > 0) {
        files = files.filter(f =>
          extensions.some(ext => f.name.endsWith(ext))
        )
      }

      const breakdown: Record<string, number> = {}
      for (const f of files) {
        const ext = f.name.includes('.')
          ? '.' + f.name.split('.').pop()
          : '(no extension)'
        breakdown[ext] = (breakdown[ext] || 0) + 1
      }

      return {
        count: files.length,
        path,
        recursive,
        breakdown
      }
    } catch (error: any) {
      return {
        error: true,
        message: `Failed to count files: ${error.message}`
      }
    }
  }
})

export function apply(ctx: Context) {
  ctx.tools.register(fileCountTool)
  ctx.logger.info('file_count tool registered')
}

ابدأ وتحقق:

BASH
# التسجيل في cordis.yml
pnpm dsh web --patch

# الاختبار في واجهة الويب
# 👤 Alice: How many files are in the /home/alice/project directory?
# 🤖 Agent: 🔧 file_count → { count: 42, breakdown: { ".ts": 28, ".js": 10, ".json": 4 } }

❓ أسئلة شائعة

س ما الفرق بين defineTool و ctx.tools.register؟
ج defineTool هو DSL يُنشئ كائن تعريف أداة. ctx.tools.register هو طريقة التسجيل التي تُضيف التعريف لخدمة الأدوات. يعملان معاً: أولاً defineTool يُعرّف، ثم register يُسجّل.
س هل يمكن تكرار أسماء الأدوات؟
ج لا. الأداة المُسجّلة لاحقاً بنفس الاسم تُستبدل السابقة. إذا احتجت أدوات بنفس الاسم للتعايش، استخدم عزل النطاق (راجع 20-scope.md).
س هل يمكن تجاهل description في المُعاملات؟
ج تقنياً نعم، لكن غير مُوصى به بشدة. أوصاف المُعاملات تساعد LLM على فهم معناها؛ غيابها يؤدي لتمرير مُعاملات خاطئة من الوكيل.
س هل يمكن لـ execute إعادة بيانات متدفقة؟
ج حالياً execute في defineTool يدعم إعادة النتائج الكاملة فقط. المخرجات المتدفقة تتحقق عبر بروتوكول StreamChunk لمحول LLM (راجع 25-stream-error.md).
س هل يمكن للأداة استدعاء أدوات أخرى؟
ج نعم، عبر ctx.tools.execute('other_tool', params). لكن انتبه لتجنب الاستدعاءات الدائرية.
س كيف أُصحح تحليل مُعاملات الأداة؟
ج سجّل المُعاملات في بداية execute: typescript async execute(params, ctx) { ctx.logger.info('params:', JSON.stringify(params)) // ... }

📖 ملخص


📝 تمارين

1. ⭐ أساسي: استخدم defineTool لإنشاء أداة current_time تقبل مُعامل منطقة زمنية اختياري (الافتراضي UTC) وتُعيد سلسلة الوقت الحالي. سجّلها في DSH واجعل الوكيل يستدعيها بنجاح.

2. ⭐⭐ متوسط: وسّع أداة file_count بإضافة مُعاملات min_size و max_size (بالبايت) للتصفية حسب نطاق حجم الملف. الاختبار: عدّ الملفات في /tmp الأكبر من 1KB والأصغر من 1MB.

3. ⭐⭐⭐ تحدٍ: أنشئ أداة code_stats تعدّ أسطر الكود في دليل محدد. المُعاملات: path (مسار الدليل)، languages (مصفوفة تصفية اللغات). أعد إجمالي الأسطر، أسطر كل لغة، الأسطر الفارغة، أسطر التعليقات. استخدم regex للتمييز بين أسطر الكود / الأسطر الفارغة / أسطر التعليقات.

Web-Tutorial.com

فريق Web-Tutorial التقني

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

100%