DeepSeek Harness: Desenvolvendo Sua Primeira Ferramenta

Última atualização: 2026-08-31

Ferramentas são as mãos do Agent — definir uma ferramenta dá ao Agent uma nova capacidade. defineTool é a DSL de definição de ferramentas do DSH, usando uma abordagem declarativa para descrever o nome, parâmetros e lógica de execução de uma ferramenta, para que o Agent entenda quando e como chamar sua ferramenta.

💡 Dica: O núcleo do defineTool é "fazer o LLM entender sua ferramenta" — nome e descrição são para o LLM, parâmetros descrevem o formato de entrada, e execute é a implementação real. Escreva boas descrições para que o Agent selecione sua ferramenta nos cenários corretos.

📋 Pré-requisitos: Ter completado 14-inject.md, entender injeção de dependência

1. O Que Você Vai Aprender

Pipeline de Ferramentas


2. Sintaxe da DSL defineTool

(1) Estrutura Básica

▶ Exemplo 1: Estrutura Básica do defineTool

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

export default defineTool({
  name: 'tool_name',
  description: 'O que esta ferramenta faz',
  parameters: {
    type: 'object',
    properties: { /* ... */ },
    required: [ /* ... */ ]
  },
  async execute(params, ctx) {
    // Lógica da ferramenta
    return result
  }
})

(2) Descrições de Campos

Campo Tipo Obrigatório Descrição
name string Identificador único da ferramenta, minúsculas + underscores
description string Descrição da funcionalidade da ferramenta, para o LLM ler
parameters JSON Schema Definição de parâmetros
execute function Lógica de execução

(3) Métodos de Exportação

defineTool retorna um objeto de definição de ferramenta que pode ser diretamente usado como export padrão:

TYPESCRIPT
// Método 1: Export padrão
export default defineTool({ ... })

// Método 2: Export nomeado
export const myTool = defineTool({ ... })

3. name e description

(1) Convenção de Nomenclatura

Nomes de ferramentas usam formato snake_case:

TYPESCRIPT
name: 'file_count'        // ✅
name: 'fileCount'         // ❌ Não recomendado
name: 'FileCount'         // ❌ Não recomendado
name: 'file-count'        // ❌ Não recomendado

(2) Escrita da Descrição

A descrição é a base para o LLM escolher uma ferramenta. Pontos-chave:

TYPESCRIPT
// ❌ Descrição ruim
description: 'A tool for testing'

// ✅ Boa descrição
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...

(3) Descrição Multi-idioma

Descrições atualmente suportam apenas inglês. O LLM entende o propósito da ferramenta a partir da descrição, mesmo que a entrada do usuário esteja em outro idioma.


4. Definição de Parâmetros JSON Schema

(1) Estrutura Básica

parameters seguem a especificação JSON Schema:

TYPESCRIPT
parameters: {
  type: 'object',
  properties: {
    param_name: {
      type: 'string',
      description: 'Descrição do parâmetro'
    }
  },
  required: ['param_name']
}

(2) Tipos Suportados

Tipo JSON Schema Tipo TypeScript Descrição
string string String
number number Número
integer number Inteiro
boolean boolean Booleano
object object Objeto aninhado
array array Array

(3) Parâmetros String

TYPESCRIPT
properties: {
  path: {
    type: 'string',
    description: 'Caminho do diretório para contar arquivos'
  },
  pattern: {
    type: 'string',
    description: 'Padrão Glob para filtrar arquivos (ex. *.ts)',
    default: '*'
  }
}

(4) Parâmetros Enum

TYPESCRIPT
properties: {
  sort_by: {
    type: 'string',
    enum: ['name', 'size', 'date'],
    description: 'Critério de ordenação'
  }
}

(5) Parâmetros Array

TYPESCRIPT
properties: {
  extensions: {
    type: 'array',
    items: { type: 'string' },
    description: 'Extensões de arquivo para incluir (ex. [".ts", ".js"])'
  }
}

(6) Objetos Aninhados

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

(7) Campo required

TYPESCRIPT
required: ['path']          // path é obrigatório
// pattern tem default, não obrigatório

5. Implementação da Função execute

(1) Assinatura da Função

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

(2) Implementação Simples

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

(3) Acessando Serviços

Dentro de execute, você pode acessar serviços registrados via 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) Tratamento de Erros

▶ Exemplo 2: Ferramenta com Tratamento de Erros

TYPESCRIPT
async execute({ path }, ctx) {
  try {
    const files = await ctx.fs.readdir(path)
    return { count: files.length, path }
  } catch (error) {
    return {
      error: true,
      message: `Falha ao ler diretório: ${error.message}`
    }
  }
}

Ferramentas não devem lançar exceções não capturadas — retornar um objeto de erro permite ao Agent entender o motivo da falha, o que é mais amigável do que um crash.

(5) Formato do Valor de Retorno

Recomendado retornar objetos estruturados:

TYPESCRIPT
// ✅ Retorno estruturado
return {
  count: 42,
  path: '/home/alice/project',
  breakdown: {
    typescript: 28,
    javascript: 10,
    other: 4
  }
}

// ❌ Retorno em texto simples
return 'Encontrados 42 arquivos em /home/alice/project'

Retornos estruturados permitem ao Agent usar resultados programaticamente, em vez de apenas exibir texto.


6. Registro de Ferramenta em ctx.tools

(1) Registrando em um Plugin

Ferramentas definidas com defineTool precisam ser registradas em ctx.tools através de um plugin:

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

const fileCountTool = defineTool({
  name: 'file_count',
  description: 'Contar arquivos em um diretório',
  parameters: {
    type: 'object',
    properties: {
      path: { type: 'string', description: 'Caminho do diretório' }
    },
    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) Registrando Múltiplas Ferramentas

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

(3) Registro Dinâmico

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

7. Exibição de Ferramenta na Web UI

(1) Exibição Automática

Ferramentas registradas em ctx.tools aparecem automaticamente na lista de ferramentas da Web UI:

TEXT 📖 Somente leitura
┌─────────────────────────────────────┐
│ 🔧 Ferramentas                       │
├─────────────────────────────────────┤
│ file_count  │ Contar arquivos em dir │
│ file_size   │ Info de tamanho arquivo│
│ file_search │ Buscar por arquivos    │
└─────────────────────────────────────┘

(2) Exibição de name e description

(3) Exibição de Chamada do Agent

Quando o Agent chama sua ferramenta, a Web UI exibe:

TEXT 📖 Somente leitura
🤖 Agent:
🔧 Usando ferramenta: file_count
  → path: /home/alice/project
  
  Resultado: { count: 42, path: "/home/alice/project" }

▶ Exemplo 8:Ferramenta de Contagem de Arquivos

Combine todo o conhecimento em um plugin de ferramenta completo:

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')
}

Inicie e verifique:

BASH
# Registrar no cordis.yml
pnpm dsh web --patch

# Testar na Web UI
# 👤 Alice: Quantos arquivos há no diretório /home/alice/project?
# 🤖 Agent: 🔧 file_count → { count: 42, breakdown: { ".ts": 28, ".js": 10, ".json": 4 } }

❓ Perguntas Frequentes

P Qual a diferença entre defineTool e ctx.tools.register?
R defineTool é uma DSL que cria um objeto de definição de ferramenta. ctx.tools.register é o método de registro que adiciona a definição ao serviço de ferramentas. Eles trabalham juntos: primeiro defineTool define, depois register registra.
P Nomes de ferramentas podem ser duplicados?
R Não. Uma ferramenta registrada depois com o mesmo nome sobrescreve a anterior. Se precisar de ferramentas com mesmo nome coexistindo, use isolamento de escopo (veja 20-scope.md).
P Posso omitir description nos parâmetros?
R Tecnicamente sim, mas fortemente não recomendado. Descrições de parâmetros ajudam o LLM a entender o significado dos parâmetros; omiti-las leva a passagem incorreta de parâmetros pelo Agent.
P execute pode retornar dados em streaming?
R Atualmente o execute do defineTool suporta apenas retornar resultados completos. Saída em streaming é alcançada através do protocolo StreamChunk do adaptador LLM (veja 25-stream-error.md).
P Uma ferramenta pode chamar outras ferramentas?
R Sim, via ctx.tools.execute('other_tool', params). Mas cuidado para evitar chamadas circulares.
P Como depurar parsing de parâmetros da ferramenta?
R Log params no início do execute: typescript async execute(params, ctx) { ctx.logger.info('params:', JSON.stringify(params)) // ... }

📖 Resumo


📝 Exercícios

1. ⭐ Básico: Use defineTool para criar uma ferramenta current_time que aceita um parâmetro opcional de fuso horário (padrão UTC) e retorna a string de hora atual. Registre-a no DSH e faça o Agent chamá-la com sucesso.

2. ⭐⭐ Intermediário: Estenda a ferramenta file_count adicionando parâmetros min_size e max_size (em bytes) para filtrar por faixa de tamanho de arquivo. Teste: conte arquivos em /tmp maiores que 1KB e menores que 1MB.

3. ⭐⭐⭐ Desafio: Crie uma ferramenta code_stats que conte linhas de código em um diretório especificado. Parâmetros: path (caminho do diretório), languages (array de filtro de linguagem). Retorne total de linhas, linhas por linguagem, linhas em branco, linhas de comentário. Use regex para distinguir linhas de código / linhas em branco / linhas de comentário.

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%