DeepSeek Harness: Adaptador LLM

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

A abordagem "model-agnostic" do DSH não é apenas um slogan — é um design arquitetural. Adaptadores LLM, como Providers no sistema de capacidade Cordis, fazem com que trocar modelos seja tão fácil quanto trocar pilhas. Esta lição aprofunda em registro, implementação e gerenciamento multi-modelo de adaptadores LLM.

💡 Dica: Todos os adaptadores LLM implementam a mesma Definition — LLMCapability. Agents e ferramentas dependem apenas desta interface, não se o modelo subjacente é DeepSeek ou GPT-4o.

📋 Pré-requisitos: Ter completado 22-capability.md, entender capacidade três papéis

1. O Que Você Vai Aprender

Arquitetura do Adaptador LLM


2. Registro de Adaptador ctx.llm

(1) Interface LLM Capability

O adaptador LLM do DSH implementa a interface LLMCapability:

TYPESCRIPT
interface LLMCapability {
  complete(request: LLMRequest): Promise<LLMResponse>
  stream(request: LLMRequest): AsyncIterable<StreamChunk>
  getModels(): ModelInfo[]
  getModelCapabilities(model: string): ModelCapabilities
}

(2) Registro de Serviço

TYPESCRIPT
import { Service, Context } from '@deepseek-ai/cordis'

export default class MyLLMAdapter extends Service {
  constructor(ctx: Context) {
    super(ctx, 'llm')
  }
}

super(ctx, 'llm') registra o adaptador como o serviço llm.

(3) Adaptadores Built-in

O DSH vem com dois adaptadores LLM:

Adaptador Nome do Serviço Modelos Suportados
DeepSeek llm deepseek-chat, deepseek-reasoner
OpenAI Compatible llm Qualquer API compatível com OpenAI

(4) Substituindo o Adaptador Padrão

YAML
# cordis.yml
plugins:
  llm:
    $replace: ./adapters/my-custom-llm
    config:
      apiKey: sk-xxx
      endpoint: https://api.example.com/v1

3. Adaptador de Endpoint Compatível com OpenAI

(1) Visão Geral do Protocolo

A API OpenAI Chat Completions é o padrão de fato da indústria. O adaptador built-in do DSH é compatível com este protocolo:

TYPESCRIPT
interface OpenAIRequest {
  model: string
  messages: { role: string; content: string }[]
  temperature?: number
  max_tokens?: number
  stream?: boolean
  tools?: ToolDefinition[]
}

(2) Configuração Básica

YAML
# cordis.yml
plugins:
  llm:
    config:
      provider: openai-compatible
      apiKey: sk-xxx
      endpoint: https://api.openai.com/v1
      models:
        - id: gpt-4o
          capabilities: [chat, tool-use, vision]
        - id: gpt-4o-mini
          capabilities: [chat, tool-use]

(3) Endpoints Customizados

Qualquer API compatível com OpenAI pode ser conectada:

YAML
# Conectar ao Ollama local
plugins:
  llm:
    config:
      provider: openai-compatible
      endpoint: http://localhost:11434/v1
      apiKey: ollama
      models:
        - id: llama3
          capabilities: [chat]

# Conectar ao Azure OpenAI
plugins:
  llm:
    config:
      provider: openai-compatible
      endpoint: https://my-resource.openai.azure.com/openai/deployments/my-deployment
      apiKey: xxx
      headers:
        api-key: xxx

(4) Escrevendo um Adaptador Customizado

▶ Exemplo 1: Adaptador LLM Customizado

Quando o adaptador built-in não atende suas necessidades, escreva um customizado:

TYPESCRIPT
import { Service, Context } from '@deepseek-ai/cordis'
import { LLMCapability, LLMRequest, LLMResponse, StreamChunk } from '@deepseek-ai/dsh'

export const Config = Schema.object({
  apiKey: Schema.string().required().description('API key'),
  endpoint: Schema.string().required().description('API endpoint'),
  defaultModel: Schema.string().default('custom-model').description('Default model ID')
})

export default class CustomLLMAdapter extends Service implements LLMCapability {
  static inject = []

  private apiKey: string
  private endpoint: string
  private defaultModel: string

  constructor(ctx: Context) {
    super(ctx, 'llm')
    this.apiKey = ctx.config.apiKey
    this.endpoint = ctx.config.endpoint
    this.defaultModel = ctx.config.defaultModel
  }

  async complete(request: LLMRequest): Promise<LLMResponse> {
    const response = await fetch(`${this.endpoint}/chat/completions`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${this.apiKey}`
      },
      body: JSON.stringify({
        model: request.model || this.defaultModel,
        messages: request.messages,
        temperature: request.temperature,
        max_tokens: request.maxTokens,
        stream: false
      })
    })

    const data = await response.json()
    return {
      content: data.choices[0].message.content,
      model: data.model,
      usage: data.usage
    }
  }

  async *stream(request: LLMRequest): AsyncIterable<StreamChunk> {
    const response = await fetch(`${this.endpoint}/chat/completions`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${this.apiKey}`
      },
      body: JSON.stringify({
        model: request.model || this.defaultModel,
        messages: request.messages,
        temperature: request.temperature,
        max_tokens: request.maxTokens,
        stream: true
      })
    })

    const reader = response.body!.getReader()
    const decoder = new TextDecoder()
    let buffer = ''

    while (true) {
      const { done, value } = await reader.read()
      if (done) break

      buffer += decoder.decode(value, { stream: true })
      const lines = buffer.split('\n')
      buffer = lines.pop() || ''

      for (const line of lines) {
        if (line.startsWith('data: ')) {
          const data = line.slice(6)
          if (data === '[DONE]') return
          try {
            const parsed = JSON.parse(data)
            const delta = parsed.choices[0].delta
            if (delta.content) {
              yield { type: 'text', content: delta.content }
            }
          } catch {}
        }
      }
    }
  }

  getModels() {
    return [{ id: this.defaultModel, capabilities: ['chat', 'tool-use'] }]
  }

  getModelCapabilities(model: string) {
    return { chat: true, toolUse: true, vision: false }
  }
}

4. Roteamento de Modelo Customizado

(1) Conceito de Roteamento

Roteamento de modelo seleciona diferentes adaptadores LLM com base em características da requisição:

100%
graph TB
    REQ[Requisição LLM] --> ROUTER{Roteador de Modelo}
    ROUTER -->|tarefa código| CODE[DeepSeek Coder]
    ROUTER -->|raciocínio| REASON[DeepSeek Reasoner]
    ROUTER -->|chat simples| CHAT[GPT-4o-mini]

(2) Configurando Roteamento

YAML
plugins:
  llm:
    config:
      routing:
        default: deepseek-chat
        rules:
          - match:
              mode: ptc
            model: deepseek-reasoner
          - match:
              mode: creative
            model: deepseek-chat
          - match:
              tools: [shell]
            model: deepseek-coder

(3) Lógica de Roteamento Customizada

▶ Exemplo 2: Roteador de Modelo Customizado

TYPESCRIPT
export default class RoutingLLMAdapter extends Service {
  private adapters: Map<string, LLMCapability> = new Map()

  constructor(ctx: Context) {
    super(ctx, 'llm')
  }

  async complete(request: LLMRequest): Promise<LLMResponse> {
    const model = this._selectModel(request)
    const adapter = this._getAdapter(model)
    return adapter.complete({ ...request, model })
  }

  private _selectModel(request: LLMRequest): string {
    if (request.tools?.some(t => t.name === 'shell')) {
      return 'deepseek-coder'
    }
    if (request.metadata?.mode === 'ptc') {
      return 'deepseek-reasoner'
    }
    return 'deepseek-chat'
  }

  private _getAdapter(model: string): LLMCapability {
    const prefix = model.split('-')[0]
    return this.adapters.get(prefix) || this.adapters.get('default')!
  }
}

5. Protocolo StreamChunk

(1) Definição do Protocolo

StreamChunk é o protocolo unificado de saída em streaming do DSH:

TYPESCRIPT
type StreamChunk =
  | { type: 'text'; content: string }
  | { type: 'tool_call'; id: string; name: string; arguments: string }
  | { type: 'tool_result'; id: string; result: any }
  | { type: 'error'; error: Error }
  | { type: 'done'; reason: 'stop' | 'tool_use' | 'length' }

(2) Tipos de Chunk Explicados

Tipo Descrição Origem
text Fragmento de conteúdo de texto Saída em streaming do LLM
tool_call Requisição de chamada de ferramenta LLM decide chamar uma ferramenta
tool_result Resultado da execução da ferramenta Após execução da ferramenta
error Informação de erro Erros em qualquer estágio
done Fim do stream Saída do LLM finalizada

(3) Consumindo StreamChunks

▶ Exemplo 3: Consumindo StreamChunks em um Loop

TYPESCRIPT
for await (const chunk of ctx.llm.stream(request)) {
  switch (chunk.type) {
    case 'text':
      process.stdout.write(chunk.content)
      break
    case 'tool_call':
      console.log(`\nChamada de ferramenta: ${chunk.name}`)
      break
    case 'tool_result':
      console.log(`Resultado da ferramenta:`, chunk.result)
      break
    case 'error':
      console.error(`Erro:`, chunk.error)
      break
    case 'done':
      console.log(`\nConcluído (${chunk.reason})`)
      break
  }
}

6. Declarações de Capacidade de Modelo

(1) Interface ModelCapabilities

TYPESCRIPT
interface ModelCapabilities {
  chat: boolean
  toolUse: boolean
  vision: boolean
  maxTokens: number
  supportedModes: string[]
}

(2) Declarando Capacidades do Modelo

TYPESCRIPT
getModels(): ModelInfo[] {
  return [
    {
      id: 'deepseek-chat',
      capabilities: {
        chat: true,
        toolUse: true,
        vision: false,
        maxTokens: 65536,
        supportedModes: ['standard', 'ptc', 'minimal', 'creative']
      }
    },
    {
      id: 'deepseek-reasoner',
      capabilities: {
        chat: true,
        toolUse: true,
        vision: false,
        maxTokens: 65536,
        supportedModes: ['ptc']
      }
    }
  ]
}

(3) Propósito das Declarações de Capacidade

O framework usa declarações de capacidade para decidir:


7. Balanceamento de Carga Multi-Modelo

(1) Configuração

YAML
plugins:
  llm:
    config:
      loadBalancing:
        strategy: round-robin
        endpoints:
          - model: deepseek-chat
            endpoint: https://api.deepseek.com/v1
            apiKey: sk-key1
            weight: 3
          - model: gpt-4o
            endpoint: https://api.openai.com/v1
            apiKey: sk-key2
            weight: 1

(2) Estratégias de Balanceamento de Carga

Estratégia Descrição
round-robin Round-robin
random Seleção aleatória
weighted Distribuição ponderada
least-latency Selecionar menor latência

(3) Balanceamento de Carga Customizado

TYPESCRIPT
export default class LoadBalancedLLM extends Service {
  private endpoints: Endpoint[]
  private currentIndex = 0

  async complete(request: LLMRequest): Promise<LLMResponse> {
    const endpoint = this._nextEndpoint()
    return endpoint.complete(request)
  }

  private _nextEndpoint(): Endpoint {
    const endpoint = this.endpoints[this.currentIndex]
    this.currentIndex = (this.currentIndex + 1) % this.endpoints.length
    return endpoint
  }
}

❓ Perguntas Frequentes

P Múltiplos adaptadores LLM podem ser registrados simultaneamente?
R Não por padrão — registros posteriores sobrescrevem anteriores. Use isolamento de realm ou um adaptador de roteamento para gerenciamento multi-modelo.
P Quais métodos um adaptador customizado deve implementar?
R Deve implementar complete, stream, getModels, getModelCapabilities. Ausência de qualquer método causa erro de compilação TypeScript.
P Como testar um adaptador customizado?
R Use um padrão InMemoryProvider — não envie requisições HTTP reais, retorne respostas predefinidas. Substitua o serviço llm em testes unitários.
P E se a saída em streaming é interrompida?
R O protocolo StreamChunk inclui um tipo error. Consumers devem tratar chunks de erro, registrá-los, e decidir se tentam novamente.
P Qual a diferença entre roteamento de modelo e balanceamento de carga?
R Roteamento seleciona um modelo com base em características da requisição (código→coder), enquanto balanceamento de carga distribui requisições entre múltiplos endpoints do mesmo modelo.
P Como armazenar chaves API com segurança em adaptadores?
R Use variáveis de ambiente ou arquivos .env; não hardcode na configuração. Schemastery suporta .hidden() para marcar campos sensíveis.

📖 Resumo


📝 Exercícios

1. ⭐ Básico: Configure o DSH para usar um endpoint compatível com OpenAI para GPT-4o, defina API Key e ID do modelo, e faça o Agent completar uma conversa com sucesso.

2. ⭐⭐ Intermediário: Escreva um adaptador LLM customizado que encaminha requisições para um serviço Ollama local (http://localhost:11434/v1). Implemente métodos complete e stream, teste saída em streaming.

3. ⭐⭐⭐ Desafio: Implemente um adaptador de roteamento que seleciona modelos com base no modo de execução do Agent — modo PTC usa reasoner, outros modos usam chat. Crie duas sessões usando modos diferentes e verifique que usam modelos diferentes.

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%