Skip to content

Python: unknown-call termination leaves service-managed conversations unresolved #8093

Description

@CorgiBoyG

Description

With terminate_on_unknown_calls=True, an unknown local function call raises KeyError after a service-managed continuation has already been persisted, but the function-calling loop does not settle the unresolved server-side call before propagating the exception.

This leaves the session pointing at a hosted response whose function call has no terminal output. Response-ID and similar service-managed continuations can reject the next request over that session because the prior call remains unresolved.

The behavior is present on current main at 2c49f50cf08ebb6c1687146336f039051f159333.

Root cause

Both non-streaming and streaming loops update continuation state immediately after the model response:

self._update_function_invocation_continuation_state(...)

They settle dangling calls only when _process_model_function_calls(...) raises MiddlewareFailure:

try:
    function_processing = await _process_model_function_calls(...)
except MiddlewareFailure:
    await self._settle_dangling_service_function_calls(...)
    raise

Unknown-call termination is raised separately as a plain KeyError inside _try_execute_function_call_groups(...), so it bypasses that settlement path.

Reproduction

client = MockBaseChatClient()

@tool(name="known_func")
def known_func(value: str) -> str:
    return value

client.function_invocation_configuration["terminate_on_unknown_calls"] = True
client.run_responses = [
    ChatResponse(
        messages=Message(
            role="assistant",
            contents=[
                Content.from_function_call(
                    call_id="u1",
                    name="unknown_func",
                    arguments={},
                ),
                Content.from_function_call(
                    call_id="k1",
                    name="known_func",
                    arguments={"value": "x"},
                ),
            ],
        ),
        conversation_id="conv_123",
    )
]

session = AgentSession()
agent = Agent(client=client, tools=[known_func])

with pytest.raises(KeyError, match="unknown_func"):
    await agent.run("run", session=session)

assert session.service_session_id == "conv_123"
assert client.call_count == 2  # fails: actual value is 1

Observed:

exception=KeyError:'Error: Requested function "unknown_func" not found.'
conversation_id=conv_123
call_count=1

The same missing settlement boundary exists in the streaming loop.

Expected behavior

The configured fail-closed KeyError should still reach the caller, but a service-managed conversation must first receive one terminal error result per unresolved local function call, with tool calling disabled, just as it does for MiddlewareFailure.

Sessions without a service-managed continuation should not incur an extra model request.

Implementation considerations

A focused fix should:

  • distinguish unknown-call termination from unrelated KeyError exceptions;
  • settle the original unresolved batch in both streaming and non-streaming paths;
  • preserve the public KeyError behavior;
  • advance response-ID continuation state to the settlement response;
  • keep settlement best-effort so settlement failure never masks the original abort;
  • cover both service-managed and ordinary sessions.

This should remain separate from mixed-batch precedence work: the defect already reproduces with an ordinary unknown-call batch on main.

I can prepare a focused fix and regression coverage after the core team confirms the intended exception/settlement boundary.

AI assistance was used for call-path analysis and drafting. The reproduction above was executed against an isolated checkout of the cited main commit.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

agentsUsage: [Issues, PRs], Target: Single agentpythonUsage: [Issues, PRs], Target: PythonreproducedUsage: [Issues], Target: all issues that can be reproduced by the triage workflow

Type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions