DeepSeek Harness servicios y dependencias: clase base Service y declaración de tipos
En los capítulos anteriores hemos estado usando los servicios integrados ctx.tools, ctx.llm y ctx.agents.
En este capítulo, desde la perspectiva del proveedor de servicios, veremos cómo usarla clase base Servicepara proporcionar un servicio con nombre y permitir que el consumidor obtenga sugerencias de tipo completas.
Qué es un servicio
En Harness, tools, llm y agents son servicios.
Un servicio es una capacidad con nombre montada en ctx; cualquier complemento puede proporcionar servicios para que otros complementos los utilicen.
Servicio: una capacidad que un complemento expone a otros complementos.
Ocupa un ctx.<key> estable (por ejemplo, ctx.tools, ctx.llm, ctx.sessions). Otros complementos buscan servicios por key, en lugar de importar una implementación concreta.
Por ejemplo, ctx.tools es el servicio de tiempo de ejecución de herramientas, ctx.llm es el servicio de modelos grandes y ctx.agents es el servicio de agentes.
Los nombres de los servicios integrados, los métodos públicos y la ubicación del código fuente se generan automáticamente desde el repositorio hacia las páginas de subsistemas de cada servicio. Al desarrollar complementos, debes basarte en estos bloques generados y en las interfaces TypeScript de los servicios, y no depender de ninguna lista estática escrita a mano.
Usar servicios: declaración inject
Para usar un servicio existente, declara inject en el complemento.
El framework garantiza que, cuando se ejecuta apply, todos los servicios declarados en inject ya están listos.
Ejemplo
// Declara dependencia del servicio tools
export const inject = ['tools']
export function apply(ctx: Context) {
// ctx.tools existe y está listo
ctx.tools.register(/* ... */)
}
Si el servicio aún no está listo, tu complemento esperará y no ejecutará apply.
Proveer servicios: clase base Service
Deriva una subclase de la clase base Service y llama en el constructor asuper(ctx, 'nombre del servicio')Registra el servicio con nombre.
Ejemplo
import { Service, type Context } from '@deepseek-ai/cordis'
// Los servicios también pueden depender de otros servicios; decláralo con inject estático
export default class MetricsService extends Service {
static inject = ['llm'] // Este servicio requiere que el servicio llm esté listo primero
constructor(ctx: Context) {
// El segundo parámetro 'metrics' es el nombre del servicio; se monta en ctx.metrics
super(ctx, 'metrics')
}
// Método de servicio público
record(event: string, value: number) {
// Aquí se registra una métrica, por ejemplo, el número de llamadas a herramientas
// La implementación real escribiría en el backend de métricas; se omite aquí
}
}
Después de cargar este complemento, el consumidor puede acceder a él a través de ctx.metrics.
Ejemplo
// El consumidor declara dependencia del servicio metrics
export const inject = ['metrics']
export function apply(ctx: Context) {
// Llama al método del servicio: registra una llamada a herramienta
ctx.metrics.record('ejemplo_tool_call', 1)
}
Declaración de tipos: fusión de declare module
Usa la fusión de declaraciones de TypeScript para que ctx.metrics tenga el tipo correcto.
De esta manera, al escribir código habrá autocompletado y, en tiempo de compilación, también se detectarán los errores por escribir mal el nombre del servicio.
Ejemplo
import { Service, type Context } from '@deepseek-ai/cordis'
// Fusión de declaraciones: le dice a TypeScript que Context tiene un campo metrics
declare module '@deepseek-ai/cordis' {
interface Context {
metrics: MetricsService
}
}
export default class MetricsService extends Service {
constructor(ctx: Context) {
super(ctx, 'metrics')
}
record(event: string, value: number) {
/* implementación omitida */
}
}
La fusión de declaraciones con declare module es la práctica estándar de TypeScript para agregar tipos a un módulo existente.
Aquí el tipo y la implementación de ctx.metrics se escriben en el mismo archivo para garantizar que no diverjan.
Dependencias obligatorias y opcionales
Las dependencias de servicios se dividen en obligatorias y opcionales.
Las dependencias obligatorias se declaran con inject; si el servicio está ausente, el complemento no se carga en absoluto.
En las dependencias opcionales se omite inject y en el punto de uso se usactx.get()para consultar, y es posible obtener undefined.
Ejemplo
// Dependencia obligatoria: si el servicio está ausente, el complemento no se carga
export const inject = ['tools']
// Dependencia opcional: se omite inject; en el punto de uso se consulta con ctx.get()
export function apply(ctx: Context) {
// ctx.get('metrics') puede devolver undefined; usa encadenamiento opcional para llamarlo de forma segura
const metrics = ctx.get('metrics')
metrics?.record('ejemplo_plugin_loaded', 1)
}
| Tipo de dependencia | Sintaxis | Comportamiento cuando el servicio está ausente | Caso de uso |
|---|---|---|---|
| Dependencia obligatoria | export const inject = ['tools'] | El complemento no se carga y permanece en PENDING | No puede funcionar sin ese servicio |
| Dependencia opcional | Se omite inject y se consulta con ctx.get('metrics') | El complemento se carga normalmente; ctx.get() devuelve undefined | El servicio es prescindible; debe funcionar normalmente también cuando está ausente |
¿Cuándo usar dependencias opcionales? Cuando el servicio es prescindible y tu complemento debe funcionar normalmente incluso en su ausencia.
Comportamiento cuando los servicios desaparecen
Si durante la ejecución de la aplicación un servicio obligatorio desaparece (por ejemplo, se desinstala su proveedor), ocurren dos cosas:
- Los complementos que dependen de él se eliminan (dispose) automáticamente (liberan recursos).
- Cuando el servicio reaparece, el complemento se recarga automáticamente.
Esto evita que los complementos llamen a servicios que ya no existen.
Resumen y autoevaluación
Las subclases de Service proporcionan servicios con nombre mediante super(ctx, 'nombre'); la fusión de declaraciones con declare module completa los tipos; inject declara dependencias obligatorias y ctx.get() consulta dependencias opcionales.
Ponte a prueba:
- ¿En qué forma existe un servicio en ctx? ¿Qué parámetro determina el nombre del servicio?
- ¿Cómo hacer que ctx.metrics tenga sugerencias de tipo?
- ¿En qué se diferencia la sintaxis de código entre dependencias obligatorias y opcionales?