CMDCTest.MockProvider (cmdc_test v0.1.0)

Copy Markdown View Source

Mock LLM provider — Builder API 构造响应队列,配合 CMDC.Config.provider_fn 注入。

替代集成方各自实现的 inline mock,统一所有第三方测试的 mock LLM 行为。

设计

内部用 :persistent_term 存储响应队列 + 索引(避免持有 GenServer 进程生命周期), to_provider_fn/1 返回一个 Config.provider_fn 接口的闭包,每次调用从队头取响应:

  • 文本响应 → spawn 一个 stream bridge 进程,分块 emit 给 Agent
  • 工具调用响应 → spawn 一个 bridge,emit 单个 tool_call chunk
  • 错误响应 → 直接返回 {:error, reason}

Quick Start

provider =
  CMDCTest.MockProvider.new()
  |> CMDCTest.MockProvider.respond("Hello, world!")
  |> CMDCTest.MockProvider.respond_tool_call("shell", %{"cmd" => "ls"})
  |> CMDCTest.MockProvider.respond("done")

{:ok, session} =
  CMDC.create_agent(
    model: "mock:test",
    config: %{provider_fn: CMDCTest.MockProvider.to_provider_fn(provider)}
  )

CMDC.prompt(session, "go")   # 第 1 个 prompt 用第 1 个响应
CMDC.prompt(session, "tool") # 第 2 个 prompt 用 tool_call 响应
CMDC.prompt(session, "end")  # 第 3 个 prompt 用 "done"

清理

:persistent_term 在测试结束后会随 BEAM 节点持续存在;可在 on_exit 调用 cleanup/1 主动释放。短测试通常无需清理(数据量很小)。

setup do
  provider = MockProvider.new() |> MockProvider.respond("ok")
  on_exit(fn -> MockProvider.cleanup(provider) end)
  %{provider: provider}
end

Summary

Functions

释放该 MockProvider 在 :persistent_term 中的存储。

构造新的 MockProvider,初始响应队列为空。

追加一条文本响应。

追加一条错误响应。Agent 收到后走错误恢复路径(可能 retry 或 abort)。

追加一条工具调用响应。

导出 Config.provider_fn 兼容的闭包。

Types

response()

@type response() ::
  {:text, String.t()} | {:tool_call, String.t(), map()} | {:error, term()}

t()

@type t() :: %CMDCTest.MockProvider{ref: reference(), responses: [response()]}

Functions

cleanup(mock_provider)

@spec cleanup(t()) :: :ok

释放该 MockProvider 在 :persistent_term 中的存储。

new()

@spec new() :: t()

构造新的 MockProvider,初始响应队列为空。

示例

iex> provider = CMDCTest.MockProvider.new()
iex> provider.responses
[]

respond(mp, text)

@spec respond(t(), String.t()) :: t()

追加一条文本响应。

respond_error(mp, reason)

@spec respond_error(t(), term()) :: t()

追加一条错误响应。Agent 收到后走错误恢复路径(可能 retry 或 abort)。

respond_tool_call(mp, name, args)

@spec respond_tool_call(t(), String.t(), map()) :: t()

追加一条工具调用响应。

参数

  • name — 工具名(必须与 Agent.tools 列表中某个 tool.name() 匹配,否则 Agent 会发 :tool_call_unknown 事件并注入 error tool_result)
  • args — 工具参数 map(按工具自身的 parameters schema 序列化)

Agent 收到此响应后会:

  1. 在当前 turn 调用对应工具
  2. 收集结果作为 ToolMessage
  3. 进入下一 turn 继续找 mock 队列中的下一个响应

to_provider_fn(mock_provider)

@spec to_provider_fn(t()) :: (String.t(), list(), list(), keyword() ->
                          {:ok, map()} | {:error, term()})

导出 Config.provider_fn 兼容的闭包。

接口签名:fn model, messages, tools, opts -> {:ok, %{bridge_pid: pid}} | {:error, term} 与 cmdc 主库 Agent.Integration 测试中的 mock 完全一致。