Codex 教程
按顺序阅读,循序渐进掌握相关 AI 工具与实战方法。
想把 Harness 接到自己的模型端点,就要写一个 LLM 适配器:继承 LlmAdapter 并实现 stream() 方法的类。它把 Harness 的提供方无关请求转换成具体提供方的 API 调用,再把响应转换回 Harness 的分片 StreamChunk。整体三层:顶层 agent-loop 消费统一的流式生成服务;中间 ctx.llm 注册表维护抽象契约;底层各适配器对接不同 API 格式,用 ctx.llm.registerAdapter(['my-provider'], adapter) 绑定路由。
stream() 是核心,返回异步生成器,标准流程三步:转换消息格式、调用流式 API、把响应转成 StreamChunk:
import type { Context } from '@deepseek-ai/cordis'
import Schema from '@deepseek-ai/schemastery'
import { LlmAdapter, type GenerateOptions, type StreamChunk } from '@deepseek-ai/dsh-llm'
class MyAdapter extends LlmAdapter {
private apiKey: string
constructor(apiKey: string) {
super()
this.apiKey = apiKey
}
async *stream(options: GenerateOptions): AsyncIterable<StreamChunk> {
// 1. Convert options.messages to the provider format.
// 2. Call the streaming API.
// 3. Convert the response into StreamChunk values.
}
}
export interface Config {
apiKey: string
providers: string[]
}
export const Config: Schema<Config> = Schema.object({
apiKey: Schema.string().required(),
providers: Schema.array(Schema.string()).required(),
})
export const name = 'my-llm-adapter'
export const inject = ['llm'] // 保证 ctx.llm 已就绪
export function apply(ctx: Context, config: Config) {
const adapter = new MyAdapter(config.apiKey)
ctx.llm.registerAdapter(config.providers, adapter)
}
apiKey 来自插件配置而非硬编码;providers 数组声明适配器负责哪些提供方路由。
stream() 接收的 GenerateOptions 以 @deepseek-ai/dsh-llm 导出的类型为准,关键字段包括:provider(选择已注册的适配器)、model(适配器拥有的模型 id,无需启动时注册)、messages、系统提示词、tools、可选的 reasoning 推理强度 ID,以及 signal 中止信号。适配器必须把支持的字段映射到具体 API;无法支持的字段应抛出带稳定 code 的 LlmError,不得静默丢弃。
需要向选择器公布模型选项时可覆写 listModels();需要返回确切身份时覆写 resolveModel(provider, model, signal?),一次返回提供方、模型身份及可选的 context、reasoning 元数据。reasoning 元数据包含有序的不透明 ID、展示名称与可选默认值——保留适配器给出的权威可选列表(包括上游返回的 off),不要提升为核心枚举;异步查询必须响应 signal;显式指定但不支持的推理强度会在 stream() 调用前被拒绝;省略 reasoning 表示该模型没有可选推理能力。
# 文件:cordis.yml
- id: my-llm
name: './src/my-llm-adapter.ts'
config:
apiKey: !!js process.env.MY_API_KEY
providers:
- my-provider
- id: agent-loop
name: '@deepseek-ai/dsh-agent-loop'
config:
agents:
- id: main
provider: my-provider
model: my-model-v1
密钥经环境变量读取不落盘;agent-loop 的 agents.main 配置 provider 与 model,生成请求时即命中新适配器。仓库里的 llm-deepseek(OpenAI 兼容格式)与 llm-pi-ai(不同 API 格式)是两个现成实现,对照阅读最能体会「契约不变、实现各异」的 seam 思想。
LLM 适配器 = 继承 LlmAdapter + 实现 stream() + registerAdapter 路由注册 + cordis.yml 配置。自测:stream() 的返回类型是什么?registerAdapter 第一个参数代表什么?agent-loop 通过哪些配置项选择新适配器?