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.
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
- Registro de adaptador ctx.llm
- Adaptador de endpoint compatível com OpenAI
- Roteamento de modelo customizado
- Protocolo StreamChunk
- Declarações de capacidade de modelo
- Balanceamento de carga multi-modelo
2. Registro de Adaptador ctx.llm
(1) Interface LLM Capability
O adaptador LLM do DSH implementa a interface LLMCapability:
interface LLMCapability {
complete(request: LLMRequest): Promise<LLMResponse>
stream(request: LLMRequest): AsyncIterable<StreamChunk>
getModels(): ModelInfo[]
getModelCapabilities(model: string): ModelCapabilities
}
(2) Registro de Serviço
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
# 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:
interface OpenAIRequest {
model: string
messages: { role: string; content: string }[]
temperature?: number
max_tokens?: number
stream?: boolean
tools?: ToolDefinition[]
}
(2) Configuração Básica
# 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:
# 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:
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:
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
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
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:
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
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
interface ModelCapabilities {
chat: boolean
toolUse: boolean
vision: boolean
maxTokens: number
supportedModes: string[]
}
(2) Declarando Capacidades do Modelo
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:
- Se permite uso de ferramentas para este modelo (toolUse)
- Se permite envio de imagens (vision)
- Limite máximo de tokens (maxTokens)
- Modos de execução suportados (supportedModes)
7. Balanceamento de Carga Multi-Modelo
(1) Configuração
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
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
complete, stream, getModels, getModelCapabilities. Ausência de qualquer método causa erro de compilação TypeScript.llm em testes unitários.error. Consumers devem tratar chunks de erro, registrá-los, e decidir se tentam novamente..env; não hardcode na configuração. Schemastery suporta .hidden() para marcar campos sensíveis.📖 Resumo
- Adaptadores LLM são Providers no sistema de capacidade Cordis, implementando a interface
LLMCapability - Adaptador built-in compatível com OpenAI suporta qualquer API compatível (DeepSeek, GPT-4o, Ollama, etc.)
- Adaptadores customizados implementam quatro métodos: complete/stream/getModels/getModelCapabilities
- Roteamento de modelo seleciona diferentes modelos com base em características da requisição; balanceamento de carga distribui entre múltiplos endpoints
- Protocolo StreamChunk unifica saída em streaming: text/tool_call/tool_result/error/done
- Declarações de capacidade de modelo dizem ao framework o que um modelo pode fazer; o framework restringe funcionalidades conforme
📝 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.