☰
AxonHub 集成测试指南:基于 OpenAI Responses API 的多轮对话测试(previous_response_id 与无状态 input 数组)
2026/10/13 2:04:44 网站建设 项目流程
  • 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.

项目地址:https://gitcode.com/gh_mirrors/ax/axonhub
点击查看免费下载

本文档围绕 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 下“对话 + 工具”的完整闭环:

  1. 第一轮:输入“东京天气 + 计算 365×24”,带上两个工具。遍历resp1.Output,筛出item.Type == "function_call"的条目,得到[]responses.ResponseFunctionToolCall。
  2. 执行工具:解析fc.Arguments(JSON),按fc.Name分发到本地模拟函数simulateCalculatorFunction/simulateWeatherFunction(仓库用硬编码数据模拟真实工具,避免测试依赖外部服务)。
  3. 回填结果:用responses.ResponseInputItemParamOfFunctionCallOutput(fc.CallID, result)构造 function_call_output 输入项。
  4. 第二轮:PreviousResponseID: resp1.ID+OfInputItemList携带工具结果,让模型整合出最终回答。
  5. 第三轮:继续链式追问“该不该带伞”,验证工具结果对后续对话的影响。

无状态版本(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_KEYAxonHub API Key,必需无(缺省跳过测试)
TEST_OPENAI_BASE_URLOpenAI 兼容端点地址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 与两份源码对比可相互印证):

  1. API 调用入口:Chat.Completions.New→Responses.New(测试中统一经 helper 封装)。
  2. 参数结构:openai.ChatCompletionNewParams{Messages, Model}→responses.ResponseNewParams{Model, Input, Instructions, PreviousResponseID, Tools}。
  3. 上下文管理:手动维护messages数组(每轮append用户消息与completion.Choices[0].Message.ToParam())→ 有状态时改用PreviousResponseID链。
  4. 输入格式:字符串(OfString)与结构化输入数组(OfInputItemList)双支持,而 Chat Completions 只有消息数组一种形态。
  5. 工具支持:函数定义从shared.FunctionDefinitionParam演进为responses.FunctionToolParam,多出Strict严格模式开关与additionalProperties校验;README 同时提示“Responses API 的工具支持级别可能与 Chat Completions 存在差异,个别用例可能被临时跳过”。

八、预期行为与断言清单

仓库对“什么算通过”给出了明确的断言标准,可复用到你自己的测试中:

  1. 上下文保持:后轮必须能引用前轮引入的信息——如第二轮必须出现blue、第三轮必须出现japan,断言用testutil.ContainsCaseInsensitive(output, "blue")做大小写不敏感匹配(internal/testutil/helper.go#L168-L178)。
  2. 响应链:previous_response_id必须维持上下文(resp.ID不能为空,否则t.Fatal)。
  3. 指令持续生效:带Instructions的每轮回答都应符合指令风格,如黑洞话题命中black hole / star / gravity / collapse任一关键词即可通过。
  4. 信息召回:长链测试用“先给事实、后提问”的方式验证alice、paris的召回。
  5. 工具可用性:对话过程中工具可被持续调用,工具结果能进入后续对话(如“带伞”追问)。
  6. 系统提示词影响:烹饪助手场景要求响应命中至少 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.

项目地址:https://gitcode.com/gh_mirrors/ax/axonhub
点击查看免费下载

相关推荐

上一篇:Instatic与AI助手集成:聊天机器人内容管理的终极指南
下一篇:PiKVM容器健康检查:自定义脚本

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询