- AI 应用
- API网关
- LLM 网关
- 后端
- 前端
【免费下载链接】axonhub
⚡️ Open-source AI Gateway — Use any SDK to call 100+ LLMs. Built-in failover, load balancing, cost control & end-to-end tracing.
本文档围绕 AxonHub 仓库中 integration_test/openai/responses/conversation 目录下的对话类集成测试展开,系统讲解如何基于 OpenAI Responses API 编写多轮对话测试:既包括通过previous_response_id让 API 自动维护上下文的有状态对话,也包括把完整历史塞进input数组的无状态对话,并覆盖指令(instructions)、工具调用与上下文保持的验证手法。读完本文,你将掌握在 AxonHub 的 OpenAI 兼容网关(/v1/responses端点)上运行、迁移和扩展多轮对话集成测试的完整套路。
一、背景:为什么需要 Responses API 对话测试
AxonHub 是一个开源的 AI 网关,对外暴露 OpenAI 兼容的/v1/responses端点,因此仓库使用官方 Go SDK(github.com/openai/openai-go/v3,见 integration_test/openai/go.mod)直接对其进行集成测试。与传统的 Chat Completions 相比,Responses API 引入了一个关键变化:多轮对话的上下文不再依赖调用方手动维护messages数组,而是可以通过previous_response_id字段把上一轮返回的response.id链式传下去,由服务端自动拼接上下文。
conversation目录下的测试正是为了验证这一机制,同时验证在链式上下文之外,仍可显式通过input数组手动携带历史消息(无状态方式)也能获得等价效果。
二、测试文件与用例清单
该目录包含两个测试文件(conversation_test.go 与 conversation_stateless_test.go),外加一个 README.md。
conversation_test.go(有状态,基于 previous_response_id)
| 用例 | 验证目标 |
|---|---|
TestResponsesConversation | 基础多轮对话与上下文保持:第一轮告知“去日本旅行、喜欢蓝色”,第二轮问“我喜欢的颜色是什么”应回答 blue,第三轮问“我要去哪旅行”应回答 Japan |
TestResponsesConversationWithInstructions | 携带Instructions的多轮对话,验证助手始终按指令(太空科学导师风格)回答 |
TestResponsesConversationContextChain | 长对话链:依次引入姓名 Alice、城市 Paris,再回溯提问“我叫什么/我住哪”,验证链式上下文与信息召回 |
TestMultiTurnConversation | 从原 Chat Completions 对话测试迁移而来:日本旅行 + 东京天气 + 算术 365×24=8760 |
TestConversationWithTools | 对话中集成 calculate / get_current_weather 两个函数工具,处理函数调用并追问后续问题 |
TestConversationContextPreservation | 带系统提示词的三轮对话,统计空间/天文学主题关键词命中数,验证上下文跨轮保持 |
TestConversationSystemPrompt | 烹饪助手系统提示词:第一轮推荐意面,第二轮改问披萨仍保持烹饪语境 |
conversation_stateless_test.go(无状态,基于 input 数组)
| 用例 | 验证目标 |
|---|---|
TestResponsesConversationStateless | 无状态三连问:手动把“用户消息 + 助手回复”逐轮累加到input数组中 |
TestResponsesConversationStatelessWithInstructions | 无状态 + 指令参数的黑洞形成问题 |
TestResponsesConversationStatelessContextChain | 用增量历史数组构造的长链对话(Alice/Paris 召回) |
说明:从源码看,
conversation_stateless_test.go中实际还包含TestMultiTurnConversationStateless、TestConversationWithToolsStateless、TestConversationContextPreservationStateless、TestConversationSystemPromptStateless四个补充用例,与有状态版本一一对应;README 仅收录了前三个核心用例。以 conversation_stateless_test.go 源码为准。
三、两种对话模式的原理解读
3.1 有状态对话:previous_response_id 链
核心机制是每个 Response 都拥有唯一 ID,后续请求只要带上PreviousResponseID字段指向上一轮响应的 ID,服务端即可自动恢复该轮为止的完整上下文,调用方无需自行拼历史消息。源码中三连问的结构如下(节选自 conversation_test.go):
// 第一轮:只有输入 params1 := responses.ResponseNewParams{ Model: shared.ResponsesModel(helper.GetModel()), Input: responses.ResponseNewParamsInputUnion{ OfString: openai.String(firstInput), }, } resp1, err := helper.CreateResponseWithHeaders(ctx, params1) // 第二轮:带上上一轮 response 的 ID params2 := responses.ResponseNewParams{ Model: shared.ResponsesModel(helper.GetModel()), PreviousResponseID: openai.String(resp1.ID), Input: responses.ResponseNewParamsInputUnion{ OfString: openai.String(secondInput), }, }TestResponsesConversationContextChain则展示了通用循环写法:维护一个previousID变量,每轮请求后更新为resp.ID(见 conversation_test.go#L171-L243),4 轮内同时完成“引入事实”和“召回事实”两种验证:
var previousID string for i, fact := range facts { params := responses.ResponseNewParams{ Model: shared.ResponsesModel(helper.GetModel()), Input: responses.ResponseNewParamsInputUnion{ OfString: openai.String(fact.input) }, } if previousID != "" { params.PreviousResponseID = openai.String(previousID) } resp, err := helper.CreateResponseWithHeaders(ctx, params) // ... 校验 fact.validate(output) ... previousID = resp.ID // 链式更新 }3.2 无状态对话:手动维护 input 历史数组
无状态模式不依赖服务端记忆,而是由客户端把“用户消息 + 助手消息”交替追加进input数组,每次请求都携带完整历史。conversation_stateless_test.go提供了两个关键构造器:
createUserMessage(text):通过responses.ResponseInputItemParamOfInputMessage(...)构造user角色输入项;createAssistantMessage(text):通过responses.ResponseInputItemParamOfOutputMessage(...)把上一轮助手输出转成历史输入项。
第三轮请求的input数组形如(conversation_stateless_test.go#L106-L117):
params3 := responses.ResponseNewParams{ Model: shared.ResponsesModel(helper.GetModel()), Input: responses.ResponseNewParamsInputUnion{ OfInputItemList: responses.ResponseInputParam{ createUserMessage(firstInput), // 第一轮用户消息 createAssistantMessage(output1), // 第一轮助手回复 createUserMessage(secondInput), // 第二轮用户消息 createAssistantMessage(output2), // 第二轮助手回复 createUserMessage(thirdInput), // 第三轮用户消息 }, }, }TestResponsesConversationStatelessContextChain把这一过程循环化:每轮先用“当前历史 + 当前用户消息”发起请求,拿到输出后再把用户消息与助手回复同时追加进history切片(conversation_stateless_test.go#L236-L273)。
3.3 两种模式对比
| 维度 | 有状态(PreviousResponseID) | 无状态(input 数组) |
|---|---|---|
| 上下文维护方 | 服务端自动管理 | 客户端手动维护历史数组 |
| 请求体大小 | 恒定(仅本轮输入 + 一个 ID) | 随轮数线性增长 |
| 控制力度 | 简化流程、省 token | 完全掌控、可截断/改写历史 |
| 实现复杂度 | 低 | 中(需自行构造消息项) |
四、关键参数详解(结合源码)
所有用例统一使用responses.ResponseNewParams构造请求,常用字段如下:
- Model:
shared.ResponsesModel(helper.GetModel()),模型来自TEST_MODEL环境变量,缺省deepseek-chat(见 internal/testutil/config.go)。 - Input:联合类型
responses.ResponseNewParamsInputUnion,字符串简写走OfString(如openai.String("Hello!")),结构化历史走OfInputItemList(responses.ResponseInputParam(...))。 - Instructions:相当于系统提示词,
openai.String(instructions),有状态与无状态用例中每一轮都会重复携带,验证指令的持续引导作用(见TestResponsesConversationWithInstructions)。 - PreviousResponseID:有状态模式的核心字段,
openai.String(resp.ID)链式传参。 - Tools:
[]responses.ToolUnionParam,每个工具用responses.FunctionToolParam定义Name、Description、Strict(严格模式openai.Bool(true))与 JSON SchemaParameters(含required与additionalProperties: false),详见TestConversationWithTools中 calculate / get_current_weather 两个工具的构造(conversation_test.go#L345-L381)。
五、对话中的工具调用流程
TestConversationWithTools(conversation_test.go#L333-L485)演示了 Responses API 下“对话 + 工具”的完整闭环:
- 第一轮:输入“东京天气 + 计算 365×24”,带上两个工具。遍历
resp1.Output,筛出item.Type == "function_call"的条目,得到[]responses.ResponseFunctionToolCall。 - 执行工具:解析
fc.Arguments(JSON),按fc.Name分发到本地模拟函数simulateCalculatorFunction/simulateWeatherFunction(仓库用硬编码数据模拟真实工具,避免测试依赖外部服务)。 - 回填结果:用
responses.ResponseInputItemParamOfFunctionCallOutput(fc.CallID, result)构造 function_call_output 输入项。 - 第二轮:
PreviousResponseID: resp1.ID+OfInputItemList携带工具结果,让模型整合出最终回答。 - 第三轮:继续链式追问“该不该带伞”,验证工具结果对后续对话的影响。
无状态版本(TestConversationWithToolsStateless)的差异在于:把 function_call 与 function_call_output 也按顺序追加进历史数组,再整体作为input提交(conversation_stateless_test.go#L455-L531)。
// 有状态版本:工具结果回填 toolResults = append(toolResults, responses.ResponseInputItemParamOfFunctionCallOutput(fc.CallID, result)) params2 := responses.ResponseNewParams{ Model: shared.ResponsesModel(helper.GetModel()), PreviousResponseID: openai.String(resp1.ID), Input: responses.ResponseNewParamsInputUnion{ OfInputItemList: responses.ResponseInputParam(toolResults), }, Tools: []responses.ToolUnionParam{calculatorTool, weatherTool}, }六、环境配置与运行方式
测试通过 internal/testutil 的Config读取环境变量,NewTestHelper在配置缺失(如未设 API Key)时会直接t.Skipf跳过而非失败(internal/testutil/helper.go#L21-L33)。
必需/常用变量:
| 环境变量 | 作用 | 默认值 |
|---|---|---|
TEST_AXONHUB_API_KEY | AxonHub API Key,必需 | 无(缺省跳过测试) |
TEST_OPENAI_BASE_URL | OpenAI 兼容端点地址 | http://localhost:8090/v1 |
TEST_MODEL | 测试模型 | deepseek-chat |
TEST_TRACE_ID | 追踪 ID(写入AH-Trace-Id头) | 随机生成 |
TEST_THREAD_ID | 会话线程 ID(写入AH-Thread-Id头) | 随机生成 |
TEST_DISABLE_TRACE/TEST_DISABLE_THREAD | 置为true关闭对应头 | false |
所有请求通过helper.CreateResponseWithHeaders(ctx, params)发送,底层是Client.Responses.New(ctx, params, headerOpts...),会把AH-Trace-Id、AH-Thread-Id两个 AxonHub 专有请求头注入调用(internal/testutil/helper.go#L83-L86),这正是网关侧做全链路追踪与会话归组的关键。
运行命令
# 在目录内运行全部用例 go test -v # 分别运行单个测试文件 go test -v conversation_test.go go test -v conversation_stateless_test.go # 运行指定用例(有状态) go test -v -run TestResponsesConversation go test -v -run TestResponsesConversationWithInstructions go test -v -run TestMultiTurnConversation go test -v -run TestConversationWithTools go test -v -run TestConversationContextPreservation go test -v -run TestConversationSystemPrompt # 运行无状态用例 go test -v -run TestResponsesConversationStateless也可在仓库根目录直接执行go test -v ./integration_test/openai/responses/conversation。需要 Go 1.25+(见 integration_test/openai/go.mod),依赖安装执行go mod tidy。
七、从 Chat Completions 迁移到 Responses API
原对话测试位于 integration_test/openai/chat/conversation/conversation_test.go,使用的是消息数组 +openai.ChatCompletionNewParams。迁移到 Responses API 后主要变化(README 的 Migration Notes 与两份源码对比可相互印证):
- API 调用入口:
Chat.Completions.New→Responses.New(测试中统一经 helper 封装)。 - 参数结构:
openai.ChatCompletionNewParams{Messages, Model}→responses.ResponseNewParams{Model, Input, Instructions, PreviousResponseID, Tools}。 - 上下文管理:手动维护
messages数组(每轮append用户消息与completion.Choices[0].Message.ToParam())→ 有状态时改用PreviousResponseID链。 - 输入格式:字符串(
OfString)与结构化输入数组(OfInputItemList)双支持,而 Chat Completions 只有消息数组一种形态。 - 工具支持:函数定义从
shared.FunctionDefinitionParam演进为responses.FunctionToolParam,多出Strict严格模式开关与additionalProperties校验;README 同时提示“Responses API 的工具支持级别可能与 Chat Completions 存在差异,个别用例可能被临时跳过”。
八、预期行为与断言清单
仓库对“什么算通过”给出了明确的断言标准,可复用到你自己的测试中:
- 上下文保持:后轮必须能引用前轮引入的信息——如第二轮必须出现
blue、第三轮必须出现japan,断言用testutil.ContainsCaseInsensitive(output, "blue")做大小写不敏感匹配(internal/testutil/helper.go#L168-L178)。 - 响应链:
previous_response_id必须维持上下文(resp.ID不能为空,否则t.Fatal)。 - 指令持续生效:带
Instructions的每轮回答都应符合指令风格,如黑洞话题命中black hole / star / gravity / collapse任一关键词即可通过。 - 信息召回:长链测试用“先给事实、后提问”的方式验证
alice、paris的召回。 - 工具可用性:对话过程中工具可被持续调用,工具结果能进入后续对话(如“带伞”追问)。
- 系统提示词影响:烹饪助手场景要求响应命中至少 2 个烹饪词(
pasta / recipe / cook / ingredient / boil);转向披萨后仍须出现pizza或dough。
九、注意事项与扩展方向
- 源码中
TestResponsesConversation、TestResponsesConversationWithInstructions、TestResponsesConversationContextChain三个用例在文件头部带有t.Skip("Test uses PreviousResponseID - temporarily skipped"),说明当前实现对该特性的支持仍在完善中,运行时会跳过;其余有状态用例未跳过,可正常执行。 - 若需扩展新的对话场景,可参照现有模式:新增用例 → 用
testutil.NewTestHelper建 helper →CreateTestContext注入头信息 → 构造ResponseNewParams→ 用AssertNoError与ContainsCaseInsensitive断言 → 最后补充 README 中的用例清单与运行命令。 - 对话测试的上下文长度受模型上下文窗口限制,超长对话建议采用无状态模式并自行控制历史截断;相关完整用例与参数说明还可见 integration_test/openai/responses/README.md 与 integration_test/openai/README.md。
- AI 应用
- API网关
- LLM 网关
- 后端
- 前端
【免费下载链接】axonhub
⚡️ Open-source AI Gateway — Use any SDK to call 100+ LLMs. Built-in failover, load balancing, cost control & end-to-end tracing.
相关推荐
AxonHub 会话集成测试实战:基于 Anthropic Go SDK 的多轮对话、工具调用与上下文保持
AxonHub 会话集成测试实战:基于 Anthropic Go SDK 的多轮对话、工具调用与上下文保持 本文围绕 AxonHub 仓库中 integrati
AI 应用API网关LLM 网关后端前端富文本搭建与可视化搭建的结合:从光标交互到统一 DSL 协议
富文本搭建与可视化搭建的结合:从光标交互到统一 DSL 协议 富文本不只是"能写字的组件",它可能是可视化搭建中承上启下的关键交互形态:既能作为搭建画布中的一个
后端API网关模型推理服务AI AgentAxonHub OpenAI Responses API 内置 `web_search` 工具集成测试实战:从工具调用到引文校验
AxonHub OpenAI Responses API 内置 web_search 工具集成测试实战:从工具调用到引文校验 导读 本文以 AxonHub 仓库
AI 应用API网关LLM 网关后端前端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考