mirror of
https://github.com/meta-llama/llama-stack.git
synced 2025-12-03 09:53:45 +00:00
fix!: Enhance response API support to not fail with tool calling (#3385)
Some checks failed
Python Package Build Test / build (3.12) (push) Failing after 8s
Test External Providers Installed via Module / test-external-providers-from-module (venv) (push) Has been skipped
SqlStore Integration Tests / test-postgres (3.13) (push) Failing after 3s
SqlStore Integration Tests / test-postgres (3.12) (push) Failing after 5s
Integration Auth Tests / test-matrix (oauth2_token) (push) Failing after 6s
Python Package Build Test / build (3.13) (push) Failing after 6s
Integration Tests (Replay) / Integration Tests (, , , client=, ) (push) Failing after 10s
Unit Tests / unit-tests (3.13) (push) Failing after 14s
Unit Tests / unit-tests (3.12) (push) Failing after 19s
Test External API and Providers / test-external (venv) (push) Failing after 1m3s
Vector IO Integration Tests / test-matrix (push) Failing after 1m6s
API Conformance Tests / check-schema-compatibility (push) Successful in 1m17s
UI Tests / ui-tests (22) (push) Successful in 1m18s
Pre-commit / pre-commit (push) Successful in 3m5s
Some checks failed
Python Package Build Test / build (3.12) (push) Failing after 8s
Test External Providers Installed via Module / test-external-providers-from-module (venv) (push) Has been skipped
SqlStore Integration Tests / test-postgres (3.13) (push) Failing after 3s
SqlStore Integration Tests / test-postgres (3.12) (push) Failing after 5s
Integration Auth Tests / test-matrix (oauth2_token) (push) Failing after 6s
Python Package Build Test / build (3.13) (push) Failing after 6s
Integration Tests (Replay) / Integration Tests (, , , client=, ) (push) Failing after 10s
Unit Tests / unit-tests (3.13) (push) Failing after 14s
Unit Tests / unit-tests (3.12) (push) Failing after 19s
Test External API and Providers / test-external (venv) (push) Failing after 1m3s
Vector IO Integration Tests / test-matrix (push) Failing after 1m6s
API Conformance Tests / check-schema-compatibility (push) Successful in 1m17s
UI Tests / ui-tests (22) (push) Successful in 1m18s
Pre-commit / pre-commit (push) Successful in 3m5s
# What does this PR do? Introduces two main fixes to enhance the stability of Responses API when dealing with tool calling responses and structured outputs. ### Changes Made 1. It added OpenAIResponseOutputMessageMCPCall and ListTools to OpenAIResponseInput but https://github.com/llamastack/llama-stack/pull/3810 got merge that did the same in a different way. Still this PR does it in a way that keep the sync between OpenAIResponsesOutput and the allowed objects in OpenAIResponseInput. 2. Add protection in case self.ctx.response_format does not have type attribute BREAKING CHANGE: OpenAIResponseInput now uses OpenAIResponseOutput union type. This is semantically equivalent - all previously accepted types are still supported via the OpenAIResponseOutput union. This improves type consistency and maintainability.
This commit is contained in:
parent
f18b5eb537
commit
63422e5b36
10 changed files with 84 additions and 79 deletions
|
|
@ -6735,14 +6735,9 @@ components:
|
||||||
Error details for failed OpenAI response requests.
|
Error details for failed OpenAI response requests.
|
||||||
OpenAIResponseInput:
|
OpenAIResponseInput:
|
||||||
oneOf:
|
oneOf:
|
||||||
- $ref: '#/components/schemas/OpenAIResponseOutputMessageWebSearchToolCall'
|
- $ref: '#/components/schemas/OpenAIResponseOutput'
|
||||||
- $ref: '#/components/schemas/OpenAIResponseOutputMessageFileSearchToolCall'
|
|
||||||
- $ref: '#/components/schemas/OpenAIResponseOutputMessageFunctionToolCall'
|
|
||||||
- $ref: '#/components/schemas/OpenAIResponseInputFunctionToolCallOutput'
|
- $ref: '#/components/schemas/OpenAIResponseInputFunctionToolCallOutput'
|
||||||
- $ref: '#/components/schemas/OpenAIResponseMCPApprovalRequest'
|
|
||||||
- $ref: '#/components/schemas/OpenAIResponseMCPApprovalResponse'
|
- $ref: '#/components/schemas/OpenAIResponseMCPApprovalResponse'
|
||||||
- $ref: '#/components/schemas/OpenAIResponseOutputMessageMCPCall'
|
|
||||||
- $ref: '#/components/schemas/OpenAIResponseOutputMessageMCPListTools'
|
|
||||||
- $ref: '#/components/schemas/OpenAIResponseMessage'
|
- $ref: '#/components/schemas/OpenAIResponseMessage'
|
||||||
OpenAIResponseInputToolFileSearch:
|
OpenAIResponseInputToolFileSearch:
|
||||||
type: object
|
type: object
|
||||||
|
|
|
||||||
17
docs/static/deprecated-llama-stack-spec.html
vendored
17
docs/static/deprecated-llama-stack-spec.html
vendored
|
|
@ -8526,29 +8526,14 @@
|
||||||
"OpenAIResponseInput": {
|
"OpenAIResponseInput": {
|
||||||
"oneOf": [
|
"oneOf": [
|
||||||
{
|
{
|
||||||
"$ref": "#/components/schemas/OpenAIResponseOutputMessageWebSearchToolCall"
|
"$ref": "#/components/schemas/OpenAIResponseOutput"
|
||||||
},
|
|
||||||
{
|
|
||||||
"$ref": "#/components/schemas/OpenAIResponseOutputMessageFileSearchToolCall"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"$ref": "#/components/schemas/OpenAIResponseOutputMessageFunctionToolCall"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"$ref": "#/components/schemas/OpenAIResponseInputFunctionToolCallOutput"
|
"$ref": "#/components/schemas/OpenAIResponseInputFunctionToolCallOutput"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"$ref": "#/components/schemas/OpenAIResponseMCPApprovalRequest"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"$ref": "#/components/schemas/OpenAIResponseMCPApprovalResponse"
|
"$ref": "#/components/schemas/OpenAIResponseMCPApprovalResponse"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"$ref": "#/components/schemas/OpenAIResponseOutputMessageMCPCall"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"$ref": "#/components/schemas/OpenAIResponseOutputMessageMCPListTools"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"$ref": "#/components/schemas/OpenAIResponseMessage"
|
"$ref": "#/components/schemas/OpenAIResponseMessage"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
7
docs/static/deprecated-llama-stack-spec.yaml
vendored
7
docs/static/deprecated-llama-stack-spec.yaml
vendored
|
|
@ -6369,14 +6369,9 @@ components:
|
||||||
Error details for failed OpenAI response requests.
|
Error details for failed OpenAI response requests.
|
||||||
OpenAIResponseInput:
|
OpenAIResponseInput:
|
||||||
oneOf:
|
oneOf:
|
||||||
- $ref: '#/components/schemas/OpenAIResponseOutputMessageWebSearchToolCall'
|
- $ref: '#/components/schemas/OpenAIResponseOutput'
|
||||||
- $ref: '#/components/schemas/OpenAIResponseOutputMessageFileSearchToolCall'
|
|
||||||
- $ref: '#/components/schemas/OpenAIResponseOutputMessageFunctionToolCall'
|
|
||||||
- $ref: '#/components/schemas/OpenAIResponseInputFunctionToolCallOutput'
|
- $ref: '#/components/schemas/OpenAIResponseInputFunctionToolCallOutput'
|
||||||
- $ref: '#/components/schemas/OpenAIResponseMCPApprovalRequest'
|
|
||||||
- $ref: '#/components/schemas/OpenAIResponseMCPApprovalResponse'
|
- $ref: '#/components/schemas/OpenAIResponseMCPApprovalResponse'
|
||||||
- $ref: '#/components/schemas/OpenAIResponseOutputMessageMCPCall'
|
|
||||||
- $ref: '#/components/schemas/OpenAIResponseOutputMessageMCPListTools'
|
|
||||||
- $ref: '#/components/schemas/OpenAIResponseMessage'
|
- $ref: '#/components/schemas/OpenAIResponseMessage'
|
||||||
"OpenAIResponseInputFunctionToolCallOutput":
|
"OpenAIResponseInputFunctionToolCallOutput":
|
||||||
type: object
|
type: object
|
||||||
|
|
|
||||||
17
docs/static/llama-stack-spec.html
vendored
17
docs/static/llama-stack-spec.html
vendored
|
|
@ -7305,29 +7305,14 @@
|
||||||
"OpenAIResponseInput": {
|
"OpenAIResponseInput": {
|
||||||
"oneOf": [
|
"oneOf": [
|
||||||
{
|
{
|
||||||
"$ref": "#/components/schemas/OpenAIResponseOutputMessageWebSearchToolCall"
|
"$ref": "#/components/schemas/OpenAIResponseOutput"
|
||||||
},
|
|
||||||
{
|
|
||||||
"$ref": "#/components/schemas/OpenAIResponseOutputMessageFileSearchToolCall"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"$ref": "#/components/schemas/OpenAIResponseOutputMessageFunctionToolCall"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"$ref": "#/components/schemas/OpenAIResponseInputFunctionToolCallOutput"
|
"$ref": "#/components/schemas/OpenAIResponseInputFunctionToolCallOutput"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"$ref": "#/components/schemas/OpenAIResponseMCPApprovalRequest"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"$ref": "#/components/schemas/OpenAIResponseMCPApprovalResponse"
|
"$ref": "#/components/schemas/OpenAIResponseMCPApprovalResponse"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"$ref": "#/components/schemas/OpenAIResponseOutputMessageMCPCall"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"$ref": "#/components/schemas/OpenAIResponseOutputMessageMCPListTools"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"$ref": "#/components/schemas/OpenAIResponseMessage"
|
"$ref": "#/components/schemas/OpenAIResponseMessage"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
7
docs/static/llama-stack-spec.yaml
vendored
7
docs/static/llama-stack-spec.yaml
vendored
|
|
@ -5522,14 +5522,9 @@ components:
|
||||||
Error details for failed OpenAI response requests.
|
Error details for failed OpenAI response requests.
|
||||||
OpenAIResponseInput:
|
OpenAIResponseInput:
|
||||||
oneOf:
|
oneOf:
|
||||||
- $ref: '#/components/schemas/OpenAIResponseOutputMessageWebSearchToolCall'
|
- $ref: '#/components/schemas/OpenAIResponseOutput'
|
||||||
- $ref: '#/components/schemas/OpenAIResponseOutputMessageFileSearchToolCall'
|
|
||||||
- $ref: '#/components/schemas/OpenAIResponseOutputMessageFunctionToolCall'
|
|
||||||
- $ref: '#/components/schemas/OpenAIResponseInputFunctionToolCallOutput'
|
- $ref: '#/components/schemas/OpenAIResponseInputFunctionToolCallOutput'
|
||||||
- $ref: '#/components/schemas/OpenAIResponseMCPApprovalRequest'
|
|
||||||
- $ref: '#/components/schemas/OpenAIResponseMCPApprovalResponse'
|
- $ref: '#/components/schemas/OpenAIResponseMCPApprovalResponse'
|
||||||
- $ref: '#/components/schemas/OpenAIResponseOutputMessageMCPCall'
|
|
||||||
- $ref: '#/components/schemas/OpenAIResponseOutputMessageMCPListTools'
|
|
||||||
- $ref: '#/components/schemas/OpenAIResponseMessage'
|
- $ref: '#/components/schemas/OpenAIResponseMessage'
|
||||||
OpenAIResponseInputToolFileSearch:
|
OpenAIResponseInputToolFileSearch:
|
||||||
type: object
|
type: object
|
||||||
|
|
|
||||||
17
docs/static/stainless-llama-stack-spec.html
vendored
17
docs/static/stainless-llama-stack-spec.html
vendored
|
|
@ -8977,29 +8977,14 @@
|
||||||
"OpenAIResponseInput": {
|
"OpenAIResponseInput": {
|
||||||
"oneOf": [
|
"oneOf": [
|
||||||
{
|
{
|
||||||
"$ref": "#/components/schemas/OpenAIResponseOutputMessageWebSearchToolCall"
|
"$ref": "#/components/schemas/OpenAIResponseOutput"
|
||||||
},
|
|
||||||
{
|
|
||||||
"$ref": "#/components/schemas/OpenAIResponseOutputMessageFileSearchToolCall"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"$ref": "#/components/schemas/OpenAIResponseOutputMessageFunctionToolCall"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"$ref": "#/components/schemas/OpenAIResponseInputFunctionToolCallOutput"
|
"$ref": "#/components/schemas/OpenAIResponseInputFunctionToolCallOutput"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"$ref": "#/components/schemas/OpenAIResponseMCPApprovalRequest"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"$ref": "#/components/schemas/OpenAIResponseMCPApprovalResponse"
|
"$ref": "#/components/schemas/OpenAIResponseMCPApprovalResponse"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"$ref": "#/components/schemas/OpenAIResponseOutputMessageMCPCall"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"$ref": "#/components/schemas/OpenAIResponseOutputMessageMCPListTools"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"$ref": "#/components/schemas/OpenAIResponseMessage"
|
"$ref": "#/components/schemas/OpenAIResponseMessage"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
7
docs/static/stainless-llama-stack-spec.yaml
vendored
7
docs/static/stainless-llama-stack-spec.yaml
vendored
|
|
@ -6735,14 +6735,9 @@ components:
|
||||||
Error details for failed OpenAI response requests.
|
Error details for failed OpenAI response requests.
|
||||||
OpenAIResponseInput:
|
OpenAIResponseInput:
|
||||||
oneOf:
|
oneOf:
|
||||||
- $ref: '#/components/schemas/OpenAIResponseOutputMessageWebSearchToolCall'
|
- $ref: '#/components/schemas/OpenAIResponseOutput'
|
||||||
- $ref: '#/components/schemas/OpenAIResponseOutputMessageFileSearchToolCall'
|
|
||||||
- $ref: '#/components/schemas/OpenAIResponseOutputMessageFunctionToolCall'
|
|
||||||
- $ref: '#/components/schemas/OpenAIResponseInputFunctionToolCallOutput'
|
- $ref: '#/components/schemas/OpenAIResponseInputFunctionToolCallOutput'
|
||||||
- $ref: '#/components/schemas/OpenAIResponseMCPApprovalRequest'
|
|
||||||
- $ref: '#/components/schemas/OpenAIResponseMCPApprovalResponse'
|
- $ref: '#/components/schemas/OpenAIResponseMCPApprovalResponse'
|
||||||
- $ref: '#/components/schemas/OpenAIResponseOutputMessageMCPCall'
|
|
||||||
- $ref: '#/components/schemas/OpenAIResponseOutputMessageMCPListTools'
|
|
||||||
- $ref: '#/components/schemas/OpenAIResponseMessage'
|
- $ref: '#/components/schemas/OpenAIResponseMessage'
|
||||||
OpenAIResponseInputToolFileSearch:
|
OpenAIResponseInputToolFileSearch:
|
||||||
type: object
|
type: object
|
||||||
|
|
|
||||||
|
|
@ -1254,14 +1254,9 @@ class OpenAIResponseInputFunctionToolCallOutput(BaseModel):
|
||||||
|
|
||||||
OpenAIResponseInput = Annotated[
|
OpenAIResponseInput = Annotated[
|
||||||
# Responses API allows output messages to be passed in as input
|
# Responses API allows output messages to be passed in as input
|
||||||
OpenAIResponseOutputMessageWebSearchToolCall
|
OpenAIResponseOutput
|
||||||
| OpenAIResponseOutputMessageFileSearchToolCall
|
|
||||||
| OpenAIResponseOutputMessageFunctionToolCall
|
|
||||||
| OpenAIResponseInputFunctionToolCallOutput
|
| OpenAIResponseInputFunctionToolCallOutput
|
||||||
| OpenAIResponseMCPApprovalRequest
|
|
||||||
| OpenAIResponseMCPApprovalResponse
|
| OpenAIResponseMCPApprovalResponse
|
||||||
| OpenAIResponseOutputMessageMCPCall
|
|
||||||
| OpenAIResponseOutputMessageMCPListTools
|
|
||||||
| OpenAIResponseMessage,
|
| OpenAIResponseMessage,
|
||||||
Field(union_mode="left_to_right"),
|
Field(union_mode="left_to_right"),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -217,7 +217,9 @@ class StreamingResponseOrchestrator:
|
||||||
while True:
|
while True:
|
||||||
# Text is the default response format for chat completion so don't need to pass it
|
# Text is the default response format for chat completion so don't need to pass it
|
||||||
# (some providers don't support non-empty response_format when tools are present)
|
# (some providers don't support non-empty response_format when tools are present)
|
||||||
response_format = None if self.ctx.response_format.type == "text" else self.ctx.response_format
|
response_format = (
|
||||||
|
None if getattr(self.ctx.response_format, "type", None) == "text" else self.ctx.response_format
|
||||||
|
)
|
||||||
logger.debug(f"calling openai_chat_completion with tools: {self.ctx.chat_tools}")
|
logger.debug(f"calling openai_chat_completion with tools: {self.ctx.chat_tools}")
|
||||||
|
|
||||||
params = OpenAIChatCompletionRequestWithExtraBody(
|
params = OpenAIChatCompletionRequestWithExtraBody(
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ from llama_stack.apis.agents.openai_responses import (
|
||||||
OpenAIResponseInputToolWebSearch,
|
OpenAIResponseInputToolWebSearch,
|
||||||
OpenAIResponseMessage,
|
OpenAIResponseMessage,
|
||||||
OpenAIResponseOutputMessageContentOutputText,
|
OpenAIResponseOutputMessageContentOutputText,
|
||||||
|
OpenAIResponseOutputMessageFunctionToolCall,
|
||||||
OpenAIResponseOutputMessageMCPCall,
|
OpenAIResponseOutputMessageMCPCall,
|
||||||
OpenAIResponseOutputMessageWebSearchToolCall,
|
OpenAIResponseOutputMessageWebSearchToolCall,
|
||||||
OpenAIResponseText,
|
OpenAIResponseText,
|
||||||
|
|
@ -1169,3 +1170,75 @@ async def test_create_openai_response_with_invalid_text_format(openai_responses_
|
||||||
model=model,
|
model=model,
|
||||||
text=OpenAIResponseText(format={"type": "invalid"}),
|
text=OpenAIResponseText(format={"type": "invalid"}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_create_openai_response_with_output_types_as_input(
|
||||||
|
openai_responses_impl, mock_inference_api, mock_responses_store
|
||||||
|
):
|
||||||
|
"""Test that response outputs can be used as inputs in multi-turn conversations.
|
||||||
|
|
||||||
|
Before adding OpenAIResponseOutput types to OpenAIResponseInput,
|
||||||
|
creating a _OpenAIResponseObjectWithInputAndMessages with some output types
|
||||||
|
in the input field would fail with a Pydantic ValidationError.
|
||||||
|
|
||||||
|
This test simulates storing a response where the input contains output message
|
||||||
|
types (MCP calls, function calls), which happens in multi-turn conversations.
|
||||||
|
"""
|
||||||
|
model = "meta-llama/Llama-3.1-8B-Instruct"
|
||||||
|
|
||||||
|
# Mock the inference response
|
||||||
|
mock_inference_api.openai_chat_completion.return_value = fake_stream()
|
||||||
|
|
||||||
|
# Create a response with store=True to trigger the storage path
|
||||||
|
result = await openai_responses_impl.create_openai_response(
|
||||||
|
input="What's the weather?",
|
||||||
|
model=model,
|
||||||
|
stream=True,
|
||||||
|
temperature=0.1,
|
||||||
|
store=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Consume the stream
|
||||||
|
_ = [chunk async for chunk in result]
|
||||||
|
|
||||||
|
# Verify store was called
|
||||||
|
assert mock_responses_store.store_response_object.called
|
||||||
|
|
||||||
|
# Get the stored data
|
||||||
|
store_call_args = mock_responses_store.store_response_object.call_args
|
||||||
|
stored_response = store_call_args.kwargs["response_object"]
|
||||||
|
|
||||||
|
# Now simulate a multi-turn conversation where outputs become inputs
|
||||||
|
input_with_output_types = [
|
||||||
|
OpenAIResponseMessage(role="user", content="What's the weather?", name=None),
|
||||||
|
# These output types need to be valid OpenAIResponseInput
|
||||||
|
OpenAIResponseOutputMessageFunctionToolCall(
|
||||||
|
call_id="call_123",
|
||||||
|
name="get_weather",
|
||||||
|
arguments='{"city": "Tokyo"}',
|
||||||
|
type="function_call",
|
||||||
|
),
|
||||||
|
OpenAIResponseOutputMessageMCPCall(
|
||||||
|
id="mcp_456",
|
||||||
|
type="mcp_call",
|
||||||
|
server_label="weather_server",
|
||||||
|
name="get_temperature",
|
||||||
|
arguments='{"location": "Tokyo"}',
|
||||||
|
output="25°C",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
# This simulates storing a response in a multi-turn conversation
|
||||||
|
# where previous outputs are included in the input.
|
||||||
|
stored_with_outputs = _OpenAIResponseObjectWithInputAndMessages(
|
||||||
|
id=stored_response.id,
|
||||||
|
created_at=stored_response.created_at,
|
||||||
|
model=stored_response.model,
|
||||||
|
status=stored_response.status,
|
||||||
|
output=stored_response.output,
|
||||||
|
input=input_with_output_types, # This will trigger Pydantic validation
|
||||||
|
messages=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert stored_with_outputs.input == input_with_output_types
|
||||||
|
assert len(stored_with_outputs.input) == 3
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue