Skip to content

fix: preserve Responses function call namespace#1797

Merged
looplj merged 2 commits into
looplj:unstablefrom
DevilSi0722:fix/responses-function-call-namespace
Jun 8, 2026
Merged

fix: preserve Responses function call namespace#1797
looplj merged 2 commits into
looplj:unstablefrom
DevilSi0722:fix/responses-function-call-namespace

Conversation

@DevilSi0722

Copy link
Copy Markdown
Contributor

修复 OpenAI Responses 流式响应中 function_call.namespace 丢失问题

概述

这个 PR 修复 AxonHub 在 OpenAI Responses API 转换链路中丢弃 function_call.namespace 字段的问题。

此前,上游 provider 返回的 Responses function_call item 中可以正常包含 namespace,但经过 AxonHub 的解析、内部转换、流式转发或最终聚合后,该字段会被丢弃。对于依赖 namespace tools 的客户端来说,这会导致工具调用无法正确路由。

例如模型实际调用的是 mcp__gitnexus.query,但客户端最终只收到裸函数名 query,无法判断它属于哪个 namespace。

问题表现

请求体中的 namespace tool 是正确的:

{
  "type": "namespace",
  "name": "mcp__gitnexus",
  "tools": [
    {
      "type": "function",
      "name": "query"
    }
  ]
}

上游 provider 返回的 function_call 也包含 namespace:

{
  "type": "function_call",
  "name": "query",
  "namespace": "mcp__gitnexus",
  "call_id": "call_xxx"
}

但修复前 AxonHub 返回或保存的结果会变成:

{
  "type": "function_call",
  "name": "query",
  "call_id": "call_xxx"
}

缺少 namespace 后,客户端无法把该调用分发到 mcp__gitnexus.query

修改内容

本 PR 做了以下调整:

  • llm.FunctionCall 添加 Namespace 字段;
  • 给 OpenAI Responses 的 Item 添加 Namespace 字段;
  • 给 OpenAI Responses 的 StreamEvent 添加 Namespace 字段;
  • 在 Responses inbound / outbound conversion 中保留 namespace;
  • 在 Responses stream inbound / outbound conversion 中保留 namespace;
  • 在 stream aggregator 中保存 namespace,并在最终 response.output[] 中输出。

验证

我使用 streaming /v1/responses 请求和 namespace tool 复现了该问题。

修复前,数据库中保存的 function call 类似:

function_call | query | namespace=NULL

使用本补丁重新构建 AxonHub Docker 镜像后,数据库记录变为:

function_call | query   | namespace=mcp__gitnexus
function_call | context | namespace=mcp__gitnexus

随后客户端可以正确路由 GitNexus MCP 工具调用。

兼容性

这个修改不应该影响普通非命名空间 function call。

namespace 是可选字段,空值时会继续通过 omitempty 省略;普通 function call 的返回结构保持不变。

@greptile-apps

greptile-apps Bot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds Namespace field propagation across the full OpenAI Responses API conversion pipeline so that namespace-qualified function calls (e.g., mcp__gitnexus.query) are no longer stripped to bare function names.

  • Adds Namespace string to llm.FunctionCall, responses.Item, and responses.StreamEvent, then threads it through every inbound/outbound conversion, stream handler, and the stream aggregator.
  • Fixes a subtle bug in outbound_stream.go where the FunctionCallArgumentsDone handler was unconditionally overwriting tc.Function.Name with a potentially empty string from the done event; it is now guarded with a non-empty check, consistent with how the aggregator path already handled the same field.

Confidence Score: 5/5

Safe to merge — the change is additive field propagation with omitempty on all new fields, leaving non-namespace function calls entirely unaffected.

Every conversion site (streaming and non-streaming, inbound and outbound, aggregator and response builder) now consistently threads the new Namespace field. The previously noted gap in the aggregator's FunctionCallArgumentsDone handler is addressed in this revision. The Name guard added to outbound_stream.go is a correct defensive fix that prevents an empty done-event name from overwriting a previously captured name.

No files require special attention.

Important Files Changed

Filename Overview
llm/tools.go Adds optional Namespace field to FunctionCall struct with omitempty; no logic changes.
llm/transformer/openai/responses/model.go Adds Namespace field to Item struct with omitempty; straightforward model extension.
llm/transformer/openai/responses/stream_event.go Adds Namespace to StreamEvent for function_call_arguments.done events; no logic changes.
llm/transformer/openai/responses/aggregator.go Adds Namespace to aggregatedItem, propagates it from OutputItemAdded and from FunctionCallArgumentsDone (guarded non-empty check), and includes it in buildResponse().
llm/transformer/openai/responses/outbound_stream.go Propagates namespace in OutputItemAdded handler; adds non-empty guard on Name update in FunctionCallArgumentsDone handler to prevent clearing a previously set name.
llm/transformer/openai/responses/inbound_stream.go Threads namespace through initToolCall and closeCurrentOutputItem when emitting stream events.
llm/transformer/openai/responses/inbound.go Propagates namespace in both convertReasoningWithFollowing and convertItemToMessage paths as well as the response conversion helper.
llm/transformer/openai/responses/outbound_convert.go Propagates namespace in convertAssistantMessage and convertOutputToMessage; changes are symmetric with inbound.go.

Sequence Diagram

sequenceDiagram
    participant UP as Upstream Provider
    participant OS as outbound_stream.go
    participant IS as inbound_stream.go
    participant AGG as aggregator.go
    participant Client

    UP->>OS: "output_item.added {type:function_call, name, namespace}"
    OS->>OS: "store toolCall{Name, Namespace} in state"
    OS->>Client: "stream delta ToolCall{Name, Namespace}"

    UP->>OS: "function_call_arguments.delta {delta}"
    OS->>Client: "stream delta ToolCall{Arguments delta}"

    UP->>OS: "function_call_arguments.done {name?, namespace?, arguments}"
    OS->>OS: guard update Name (if non-empty), Namespace (if non-empty)
    Note over OS: Returns nil — no event emitted

    UP->>OS: response.completed
    OS->>Client: "finish_reason=tool_calls"

    Note over IS,AGG: Aggregator path (non-streaming client)
    IS->>AGG: "OutputItemAdded {Item{Name, Namespace}}"
    AGG->>AGG: "aggregatedItem.Namespace = item.Namespace"
    IS->>AGG: FunctionCallArgumentsDelta
    IS->>AGG: "FunctionCallArgumentsDone {namespace? (guarded)}"
    AGG->>AGG: "if ev.Namespace != empty then item.Namespace = ev.Namespace"
    IS->>AGG: OutputItemDone
    AGG->>AGG: "buildResponse() with Item{Name, Namespace, Arguments}"
Loading

Reviews (2): Last reviewed commit: "fix: preserve namespace in function call..." | Re-trigger Greptile

@DevilSi0722

Copy link
Copy Markdown
Contributor Author

已补充 aggregator 在 StreamEventTypeFunctionCallArgumentsDone 中对 ev.Namespace 的处理,与 outbound_stream.go 的逻辑保持一致。

@looplj looplj merged commit c3bb25b into looplj:unstable Jun 8, 2026
4 checks passed
junjiangao pushed a commit to junjiangao/axonhub that referenced this pull request Jun 9, 2026
* fix: preserve Responses function call namespace

* fix: preserve namespace in function call done aggregation
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants