DeepSeek Harness: تطوير أداتك الأولى: defineTool
آخر تحديث: 2026-08-31
الأدوات هي يداه الوكيل — تعريف أداة يُعطي الوكيل قدرة جديدة. defineTool هو DSL تعريف الأدوات في DSH، يستخدم نهجاً تصريحياً لوصف اسم الأداة ومُعاملاتها ومنطق تنفيذها، حتى يفهم الوكيل متى وكيف يستدعي أداتك.
📋 المتطلبات المسبقة: أكمل 14-inject.md، تفهم حقن الاعتماديات
1. ما ستتعلمه
- صيغة defineTool DSL
- تصريحات name/description/parameters
- تعريفات مُعاملات JSON Schema
- تنفيذ دالة execute
- تسجيل الأداة في ctx.tools
- عرض الأداة في واجهة الويب
- مثال كامل: أداة عدّ الملفات
2. صيغة defineTool DSL
(1) ▶ مثال 1
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 يُعيد كائن تعريف أداة يمكن استخدامه مباشرة كتصدير افتراضي:
// الطريقة 1: التصدير الافتراضي
export default defineTool({ ... })
// الطريقة 2: التصدير المُسمّى
export const myTool = defineTool({ ... })
3. name و description
(1) اصطلاح التسمية
أسماء الأدوات تستخدم صيغة snake_case:
name: 'file_count' // ✅
name: 'fileCount' // ❌ غير مُوصى به
name: 'FileCount' // ❌ غير مُوصى به
name: 'file-count' // ❌ غير مُوصى به
(2) كتابة الوصف
الوصف هو الأساس لاختيار LLM للأداة. النقاط الأساسية:
- اشرح ماذا تفعله الأداة
- اشرح متى يجب استخدامها
- تجنب أوصاف بلا معنى مثل "أداة اختبار" أو "أداة مثال"
// ❌ وصف سيء
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:
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) مُعاملات السلسلة
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) مُعاملات التعداد
properties: {
sort_by: {
type: 'string',
enum: ['name', 'size', 'date'],
description: 'Sort criteria'
}
}
(5) مُعاملات المصفوفة
properties: {
extensions: {
type: 'array',
items: { type: 'string' },
description: 'File extensions to include (e.g. [".ts", ".js"])'
}
}
(6) الكائنات المتداخلة
properties: {
options: {
type: 'object',
properties: {
recursive: { type: 'boolean', default: false },
includeHidden: { type: 'boolean', default: false }
}
}
}
(7) حقل required
required: ['path'] // path مطلوب
// pattern له قيمة افتراضية، غير مطلوب
5. تنفيذ دالة execute
(1) توقيع الدالة
async execute(params: Params, ctx: Context): Promise<Result>
params: المُعاملات التي مرّرها المستخدم (الوكيل)، يُستنتج النوع من تعريف parametersctx: سياق Cordis، يمكنه الوصول للخدمات المُحقنة- القيمة المُعادة: أي بيانات قابلة للتسلسل بـ JSON
(2) ▶ مثال 2
async execute({ path }, ctx) {
const count = await countFiles(path)
return { count, path }
}
(3) الوصول للخدمات
داخل execute، يمكنك الوصول للخدمات المُسجّلة عبر ctx:
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) معالجة الأخطاء
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) صيغة القيمة المُعادة
يُوصى بإعادة كائنات مهيكلة:
// ✅ إعادة مهيكلة
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 عبر إضافة:
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) تسجيل أدوات متعددة
export function apply(ctx: Context) {
ctx.tools.register(fileCountTool)
ctx.tools.register(fileSizeTool)
ctx.tools.register(fileSearchTool)
}
(3) التسجيل الديناميكي
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 تظهر تلقائياً في قائمة الأدوات بواجهة الويب:
┌─────────────────────────────────────┐
│ 🔧 Tools │
├─────────────────────────────────────┤
│ file_count │ Count files in a dir │
│ file_size │ Get file size info │
│ file_search │ Search for files │
└─────────────────────────────────────┘
(2) عرض name و description
name: يُعرض كمُعرّف الأداةdescription: يُعرض كوصف الأداة
(3) عرض استدعاء الوكيل
عندما يستدعي الوكيل أداتك، واجهة الويب تعرض:
🤖 Agent:
🔧 Using tool: file_count
→ path: /home/alice/project
Result: { count: 42, path: "/home/alice/project" }
▶ مثال 8: أداة عدّ الملفات
اجمع كل المعرفة في إضافة أداة كاملة:
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')
}
ابدأ وتحقق:
# التسجيل في 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 هو DSL يُنشئ كائن تعريف أداة. ctx.tools.register هو طريقة التسجيل التي تُضيف التعريف لخدمة الأدوات. يعملان معاً: أولاً defineTool يُعرّف، ثم register يُسجّل.ctx.tools.execute('other_tool', params). لكن انتبه لتجنب الاستدعاءات الدائرية.typescript async execute(params, ctx) { ctx.logger.info('params:', JSON.stringify(params)) // ... } 📖 ملخص
- defineTool هو DSL تعريف الأدوات في DSH: name + description + parameters + execute
- name يستخدم snake_case؛ description يجب أن يشرح غرض الأداة وحالات استخدامها بوضوح
- parameters تتبع JSON Schema، تدعم string/number/boolean/object/array
- execute يستقبل params و ctx، يُعيد نتائج مهيكلة
- الأدوات تُسجّل عبر
ctx.tools.register()وتظهر تلقائياً في واجهة الويب - أعد كائنات مهيكلة بدلاً من نص عادي؛ عند الأخطاء، أعد كائنات خطأ بدلاً من رمي استثناءات
📝 تمارين
1. ⭐ أساسي: استخدم defineTool لإنشاء أداة current_time تقبل مُعامل منطقة زمنية اختياري (الافتراضي UTC) وتُعيد سلسلة الوقت الحالي. سجّلها في DSH واجعل الوكيل يستدعيها بنجاح.
2. ⭐⭐ متوسط: وسّع أداة file_count بإضافة مُعاملات min_size و max_size (بالبايت) للتصفية حسب نطاق حجم الملف. الاختبار: عدّ الملفات في /tmp الأكبر من 1KB والأصغر من 1MB.
3. ⭐⭐⭐ تحدٍ: أنشئ أداة code_stats تعدّ أسطر الكود في دليل محدد. المُعاملات: path (مسار الدليل)، languages (مصفوفة تصفية اللغات). أعد إجمالي الأسطر، أسطر كل لغة، الأسطر الفارغة، أسطر التعليقات. استخدم regex للتمييز بين أسطر الكود / الأسطر الفارغة / أسطر التعليقات.