返回知识库
0

第五章:LLM 能力

本章目标:帮助你理解 LLM 服务定义与消费模型、DeepSeek Provider 的实现细节、Adapter 机制、Router、重试策略和上下文构建。阅读本章后,你应该能回答"模型请求是如何从 Agent Loop 到达 DeepSeek API 的"以及"如何添加一个新的 LLM Provider"。


5.1 LLM 服务架构

dsh 的 LLM 能力遵循能力接缝(Capability Seam)模式:Service Definition Service Provider Consumer 三角色分离。

预览
源码

Consumer

Service Providers

Service Definition

llm/llm
ctx.llm: LlmRuntime

llm/llm-deepseek
DeepSeekAdapter

llm/llm-openai
(未来)

llm/llm-anthropic
(未来)

core/agent-loop
ReactLoopAgent

graph TB
    subgraph "Service Definition"
        A["llm/llm<br/>ctx.llm: LlmRuntime"]
    end

    subgraph "Service Providers"
        B["llm/llm-deepseek<br/>DeepSeekAdapter"]
        C["llm/llm-openai<br/>(未来)"]
        D["llm/llm-anthropic<br/>(未来)"]
    end

    subgraph "Consumer"
        E["core/agent-loop<br/>ReactLoopAgent"]
    end

    A --> B
    A --> C
    A --> D
    B --> E
    C --> E
    D --> E

5.1.1 ctx.llm 的角色

ctx.llm 是 LLM 服务的入口点,提供:

  • 流式调用 APIllm/stream waterfall 事件

  • 适配器注册:注册和发现模型适配器

  • 模型解析:将模型名称解析为具体的适配器和配置

// 来自 packages/llm/llm/src/index.ts
declare module '@deepseek-ai/cordis' {
  interface Context {
    llm: LlmRuntime
  }
}

5.2 LLM 流式调用

5.2.1 llm/stream 事件

所有模型调用都通过 llm/stream waterfall 事件:

// 来自 packages/llm/llm/src/index.ts
'llm/stream'(
  this: LlmRuntime,
  options: GenerateOptions,
  next: () => AsyncIterable<StreamChunk>
): AsyncIterable<StreamChunk>

关键特性

  • Waterfall 语义:监听者必须调用 next() 来委托

  • 重试、回放、路由:可以在 next() 前后添加逻辑

  • 短路:可以 yield 自己的 chunks 来绕过实际调用

5.2.2 请求流程

预览
源码
sequenceDiagram
    participant Loop as Agent Loop
    participant LLM as LlmRuntime
    participant Stream as llm/stream 事件
    participant Adapter as DeepSeek Adapter
    participant API as DeepSeek API

    Loop->>LLM: generate(options)
    LLM->>Stream: waterfall('llm/stream', options, next)
    Note over Stream: 重试策略、路由决策
    Stream->>Adapter: next()
    Adapter->>API: HTTP POST /chat/completions
    API-->>Adapter: SSE stream
    Adapter-->>Stream: StreamChunk*
    Stream-->>LLM: StreamChunk*
    LLM-->>Loop: StreamChunk*
sequenceDiagram
    participant Loop as Agent Loop
    participant LLM as LlmRuntime
    participant Stream as llm/stream 事件
    participant Adapter as DeepSeek Adapter
    participant API as DeepSeek API

    Loop->>LLM: generate(options)
    LLM->>Stream: waterfall('llm/stream', options, next)
    Note over Stream: 重试策略、路由决策
    Stream->>Adapter: next()
    Adapter->>API: HTTP POST /chat/completions
    API-->>Adapter: SSE stream
    Adapter-->>Stream: StreamChunk*
    Stream-->>LLM: StreamChunk*
    LLM-->>Loop: StreamChunk*

5.2.3 GenerateOptions

interface GenerateOptions {
  /** 模型名称 */
  model: string
  /** 消息列表 */
  messages: Message[]
  /** 工具 schema */
  tools?: ToolSchema[]
  /** 调用配置 */
  config?: LlmCallConfig
  // ... 其他选项
}

5.2.4 LlmCallConfig

interface LlmCallConfig {
  /** 温度 */
  temperature?: number
  /** 最大输出 token */
  maxTokens?: number
  /** 推理努力程度 */
  reasoningEffort?: ReasoningEffortId
  /** 流超时 */
  streamIdleTimeoutMs?: number
  // ... 其他配置
}

5.3 DeepSeek Provider

5.3.1 DeepSeekAdapter

DeepSeekAdapter 是 DeepSeek API 的具体实现:

// 来自 packages/llm/llm-deepseek/src/adapter.ts(概念性)
class DeepSeekAdapter implements LlmAdapter {
  /** 每次请求解析连接事实 */
  async *stream(options: GenerateOptions): AsyncIterable<StreamChunk> {
    // 1. 解析 API key
    const apiKey = await this.resolveApiKey()
    
    // 2. 构建请求
    const request = this.buildRequest(options)
    
    // 3. 发起流式请求
    const response = await fetch(this.baseUrl + '/chat/completions', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${apiKey}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(request)
    })
    
    // 4. 解析 SSE 流
    yield* this.parseSSEStream(response)
  }
}

5.3.2 连接事实解析

DeepSeek Adapter 的关键设计:连接事实按请求解析,而不是在加载时冻结

// 概念性描述
interface DeepSeekConnectionOptions {
  baseUrl: string
  apiKey: string
  catalog: ModelCatalog
}

这意味着:

  • 修改 base URL、catalog 或 key 后,下一个请求就会使用新值

  • 不需要重启任何服务

  • 进行中的流保持其启动时的事实

5.3.3 重试策略

// 来自 packages/llm/llm-deepseek/src/index.ts
interface RetryPolicyConfig {
  /** 最大重试次数 */
  maxRetries: number
  /** 基础延迟(毫秒) */
  baseDelayMs: number
  /** 最大延迟(毫秒) */
  maxDelayMs: number
  /** 退避倍数 */
  backoffMultiplier: number
}

重试策略通过 resolveRetryPolicy() 解析:

const retryPolicy = resolveRetryPolicy(config)
// retryPolicy 包含:
// - 最大重试次数
// - 延迟计算函数
// - 退避策略

5.3.4 API Key 管理

// 来自 packages/llm/llm-deepseek/src/index.ts
async function resolveApiKey(ctx: Context): Promise<string> {
  // 1. 尝试从凭证接缝获取
  const credentials = ctx.credentials?.get('deepseek-api-key')
  if (credentials) return credentials
  
  // 2. 尝试从环境变量获取
  const envKey = process.env.DEEPSEEK_API_KEY
  if (envKey) return envKey
  
  // 3. 尝试从 settings 获取
  const settings = ctx.settings?.get('llm-deepseek')
  if (settings?.apiKey) return settings.apiKey
  
  throw new LlmError('No API key available', 'AUTH')
}

5.4 Adapter 机制

5.4.1 LlmAdapter 接口

// 概念性描述
interface LlmAdapter {
  /** 提供者信息 */
  providerInfo: LlmProviderInfo
  
  /** 发现模型 */
  discoverModels(): Promise<LlmDiscoveredModel[]>
  
  /** 流式生成 */
  stream(options: GenerateOptions): AsyncIterable<StreamChunk>
  
  /** 解析模型信息 */
  resolveModel(modelName: string): LlmResolvedModelInfo | undefined
}

5.4.2 注册适配器

// 在 llm-deepseek 的 apply 函数中
function apply(ctx: Context): void {
  // 注册 DeepSeek 适配器
  ctx.llm.registerAdapter('deepseek-official', new DeepSeekAdapter(ctx))
}

5.4.3 模型解析

// 概念性描述
function resolveModel(llm: LlmRuntime, modelName: string): {
  adapter: LlmAdapter
  resolvedName: string
  config: LlmCallConfig
} {
  // 1. 按 provider 前缀匹配
  // 2. 回退到默认适配器
  // 3. 返回解析后的适配器和配置
}

5.5 BlockAssembler(块组装器)

5.5.1 概念

BlockAssembler 负责将流式 chunks 组装成完整的响应块:

// 来自 packages/llm/llm/src/assembler.ts(概念性)
class BlockAssembler {
  /** 接收 chunks */
  feed(chunk: StreamChunk): void
  
  /** 获取当前块 */
  currentBlock(): AssistantBlock
  
  /** 完成当前块 */
  finalize(): AssistantBlock
}

5.5.2 流式组装流程

预览
源码

StreamChunk*

BlockAssembler

ContentBlock

ToolCallBlock

ReasoningBlock

完整 AssistantMessage

graph LR
    A[StreamChunk*] --> B[BlockAssembler]
    B --> C[ContentBlock]
    B --> D[ToolCallBlock]
    B --> E[ReasoningBlock]
    C --> F[完整 AssistantMessage]
    D --> F
    E --> F

5.6 错误处理

5.6.1 LlmError

class LlmError extends HarnessError {
  readonly failure: LlmFailure
  
  constructor(message: string, code: string, options?: LlmErrorOptions)
}

interface LlmFailure {
  /** HTTP 状态码 */
  status?: number
  /** 提供者请求的重试延迟 */
  providerRetryAfterMs?: number
  /** 提供者请求 ID */
  requestId?: ProviderRequestId
}

5.6.2 错误码

错误码说明
AUTH认证失败
RATE_LIMIT速率限制
NO_ADAPTER没有匹配的适配器
STREAM_TIMEOUT流超时
NETWORK网络错误

5.6.3 重试与错误恢复

预览
源码

AUTH

RATE_LIMIT

NETWORK

STREAM_TIMEOUT

请求失败

错误类型

不重试,报告错误

按 providerRetryAfterMs 延迟重试

指数退避重试

重试或放弃

重试次数 < maxRetries?

重试请求

放弃,报告错误

graph TB
    A[请求失败] --> B{错误类型}
    B -->|"AUTH"| C[不重试,报告错误]
    B -->|"RATE_LIMIT"| D[按 providerRetryAfterMs 延迟重试]
    B -->|"NETWORK"| E[指数退避重试]
    B -->|"STREAM_TIMEOUT"| F[重试或放弃]
    D --> G{重试次数 < maxRetries?}
    E --> G
    F --> G
    G -->|"是"| H[重试请求]
    G -->|"否"| I[放弃,报告错误]

5.7 上下文构建

5.7.1 System Prompt 组装

预览
源码

core/system-prompt
ctx.systemPrompt

PromptSection*

joinContextSections()

renderPrompt()

完整 System Prompt

core/tools
ctx.tools

ToolSchema*

graph TB
    A["core/system-prompt<br/>ctx.systemPrompt"] --> B[PromptSection*]
    B --> C["joinContextSections()"]
    C --> D["renderPrompt()"]
    D --> E[完整 System Prompt]
    
    F["core/tools<br/>ctx.tools"] --> G[ToolSchema*]
    G --> D

5.7.2 工具 Schema 组装

// 概念性描述
function assembleToolSchemas(tools: ToolRuntime): ToolSchema[] {
  return tools.schemas().map(schema => ({
    type: 'function',
    function: {
      name: schema.name,
      description: schema.description,
      parameters: schema.parameters
    }
  }))
}

5.7.3 消息历史派生

// 概念性描述
function deriveMessages(session: Session): Message[] {
  // 从会话日志派生消息历史
  // 关键原则:Model-visible ⟺ logged
  return session.log
    .filter(event => 
      event.type === 'user/message' ||
      event.type === 'assistant/message' ||
      event.type === 'tool/result'
    )
    .sort((a, b) => a.seq - b.seq)
    .map(event => eventToMessage(event))
}

5.8 模型发现

5.8.1 模型发现流程

预览
源码
DeepSeek APIDeepSeek AdapterLlmRuntimeAgent LoopDeepSeek APIDeepSeek AdapterLlmRuntimeAgent LoopdiscoverModels()discoverModels()GET /modelsModelListLlmDiscoveredModel*ModelInfo*
sequenceDiagram
    participant Agent as Agent Loop
    participant LLM as LlmRuntime
    participant Adapter as DeepSeek Adapter
    participant API as DeepSeek API

    Agent->>LLM: discoverModels()
    LLM->>Adapter: discoverModels()
    Adapter->>API: GET /models
    API-->>Adapter: ModelList
    Adapter-->>LLM: LlmDiscoveredModel*
    LLM-->>Agent: ModelInfo*

5.8.2 LlmDiscoveredModel

interface LlmDiscoveredModel {
  /** 模型 ID */
  id: string
  /** 显示名称 */
  name: string
  /** 模态能力 */
  modalities: ModelModality[]
  /** 上下文窗口大小 */
  contextWindow: number
  /** 最大输出 token */
  maxOutputTokens: number
}

5.9 配置

5.9.1 DeepSeek 配置

# cordis.patch.yml 中的 DeepSeek 配置
- id: llm-deepseek
  config:
    # API 密钥(通过凭证接缝或环境变量提供)
    # apiKey: "sk-..."
    
    # 自定义端点
    # baseUrl: "https://api.deepseek.com"
    
    # 重试策略
    retryPolicy:
      maxRetries: 3
      baseDelayMs: 1000
      maxDelayMs: 30000
      backoffMultiplier: 2
    
    # 模型目录覆盖
    # catalog:
    #   deepseek-chat: { contextWindow: 64000 }

5.9.2 运行时配置

配置变更在下一个请求生效,无需重启:

// 概念性描述
// 用户在 Settings 中修改了 baseUrl
// → 下一个 LLM 请求使用新 baseUrl
// → 进行中的流保持不变

5.10 小结

概念一句话解释
ctx.llmLLM 服务入口,流式调用和适配器注册
llm/streamWaterfall 事件,所有模型调用经过此
DeepSeekAdapterDeepSeek API 的具体实现
连接事实按请求解析,不是加载时冻结
BlockAssembler将流式 chunks 组装成完整响应
LlmError结构化的 LLM 错误类型
模型发现运行时查询可用模型

下一步第六章:工具与数据类能力——深入理解工具注册机制和 Shell Subprocess FS LSP Web / Skill 等能力插件。

DeepSeek-Harness / 05-LLM 能力 0 0 iliuqi
2026-09-04T07:48:53.173918961Z 2026-09-04T07:57:07.653751373Z