diff --git a/docs/static/llama-stack-spec.html b/docs/static/llama-stack-spec.html
index 457593729..9e28e0f42 100644
--- a/docs/static/llama-stack-spec.html
+++ b/docs/static/llama-stack-spec.html
@@ -6783,6 +6783,31 @@
"type": "boolean",
"default": true
},
+ "items": {
+ "oneOf": [
+ {
+ "type": "null"
+ },
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "number"
+ },
+ {
+ "type": "string"
+ },
+ {
+ "type": "array"
+ },
+ {
+ "type": "object"
+ }
+ ]
+ },
+ "title": {
+ "type": "string"
+ },
"default": {
"oneOf": [
{
@@ -7457,6 +7482,14 @@
"default": true,
"description": "Whether this parameter is required for tool invocation"
},
+ "items": {
+ "type": "object",
+ "description": "Type of the elements when parameter_type is array"
+ },
+ "title": {
+ "type": "string",
+ "description": "(Optional) Title of the parameter"
+ },
"default": {
"oneOf": [
{
diff --git a/docs/static/llama-stack-spec.yaml b/docs/static/llama-stack-spec.yaml
index ad1329c2e..1c06c74a5 100644
--- a/docs/static/llama-stack-spec.yaml
+++ b/docs/static/llama-stack-spec.yaml
@@ -4866,6 +4866,16 @@ components:
required:
type: boolean
default: true
+ items:
+ oneOf:
+ - type: 'null'
+ - type: boolean
+ - type: number
+ - type: string
+ - type: array
+ - type: object
+ title:
+ type: string
default:
oneOf:
- type: 'null'
@@ -5403,6 +5413,13 @@ components:
default: true
description: >-
Whether this parameter is required for tool invocation
+ items:
+ type: object
+ description: >-
+ Type of the elements when parameter_type is array
+ title:
+ type: string
+ description: (Optional) Title of the parameter
default:
oneOf:
- type: 'null'
diff --git a/llama_stack/apis/tools/tools.py b/llama_stack/apis/tools/tools.py
index 12bd9efa2..0ebbe8c50 100644
--- a/llama_stack/apis/tools/tools.py
+++ b/llama_stack/apis/tools/tools.py
@@ -27,6 +27,8 @@ class ToolParameter(BaseModel):
:param parameter_type: Type of the parameter (e.g., string, integer)
:param description: Human-readable description of what the parameter does
:param required: Whether this parameter is required for tool invocation
+ :param items: Type of the elements when parameter_type is array
+ :param title: (Optional) Title of the parameter
:param default: (Optional) Default value for the parameter if not provided
"""
@@ -34,6 +36,8 @@ class ToolParameter(BaseModel):
parameter_type: str
description: str
required: bool = Field(default=True)
+ items: dict | None = None
+ title: str | None = None
default: Any | None = None
diff --git a/llama_stack/models/llama/datatypes.py b/llama_stack/models/llama/datatypes.py
index 7f1ebed55..0baa6e55b 100644
--- a/llama_stack/models/llama/datatypes.py
+++ b/llama_stack/models/llama/datatypes.py
@@ -92,6 +92,8 @@ class ToolParamDefinition(BaseModel):
param_type: str
description: str | None = None
required: bool | None = True
+ items: Any | None = None
+ title: str | None = None
default: Any | None = None
diff --git a/llama_stack/providers/inline/agents/meta_reference/agent_instance.py b/llama_stack/providers/inline/agents/meta_reference/agent_instance.py
index fde38515b..dcc08a482 100644
--- a/llama_stack/providers/inline/agents/meta_reference/agent_instance.py
+++ b/llama_stack/providers/inline/agents/meta_reference/agent_instance.py
@@ -798,6 +798,8 @@ class ChatAgent(ShieldRunnerMixin):
param_type=param.parameter_type,
description=param.description,
required=param.required,
+ items=param.items,
+ title=param.title,
default=param.default,
)
for param in tool_def.parameters
@@ -841,6 +843,8 @@ class ChatAgent(ShieldRunnerMixin):
param_type=param.parameter_type,
description=param.description,
required=param.required,
+ items=param.items,
+ title=param.title,
default=param.default,
)
for param in tool_def.parameters
diff --git a/llama_stack/providers/utils/inference/openai_compat.py b/llama_stack/providers/utils/inference/openai_compat.py
index 55c2ac0ad..cdd471d5e 100644
--- a/llama_stack/providers/utils/inference/openai_compat.py
+++ b/llama_stack/providers/utils/inference/openai_compat.py
@@ -805,6 +805,10 @@ def convert_tooldef_to_openai_tool(tool: ToolDefinition) -> dict:
properties[param_name].update(description=param.description)
if param.default:
properties[param_name].update(default=param.default)
+ if param.items:
+ properties[param_name].update(items=param.items)
+ if param.title:
+ properties[param_name].update(title=param.title)
if param.required:
required.append(param_name)
diff --git a/llama_stack/providers/utils/tools/mcp.py b/llama_stack/providers/utils/tools/mcp.py
index fc8e2f377..155f7eff8 100644
--- a/llama_stack/providers/utils/tools/mcp.py
+++ b/llama_stack/providers/utils/tools/mcp.py
@@ -120,6 +120,10 @@ async def list_mcp_tools(endpoint: str, headers: dict[str, str]) -> ListToolDefs
name=param_name,
parameter_type=param_schema.get("type", "string"),
description=param_schema.get("description", ""),
+ required="default" not in param_schema,
+ items=param_schema.get("items", None),
+ title=param_schema.get("title", None),
+ default=param_schema.get("default", None),
)
)
tools.append(
diff --git a/tests/common/mcp.py b/tests/common/mcp.py
index d05ac39c6..f65f7c952 100644
--- a/tests/common/mcp.py
+++ b/tests/common/mcp.py
@@ -167,6 +167,8 @@ def make_mcp_server(required_auth_token: str | None = None, tools: dict[str, Cal
from starlette.responses import Response
from starlette.routing import Mount, Route
+ from llama_stack.log import get_logger
+
server = FastMCP("FastMCP Test Server", log_level="WARNING")
tools = tools or default_tools()
@@ -211,6 +213,7 @@ def make_mcp_server(required_auth_token: str | None = None, tools: dict[str, Cal
return sock.getsockname()[1]
port = get_open_port()
+ logger = get_logger(__name__, category="tests::mcp")
# make uvicorn logs be less verbose
config = uvicorn.Config(app, host="0.0.0.0", port=port, log_level="warning")
@@ -218,10 +221,17 @@ def make_mcp_server(required_auth_token: str | None = None, tools: dict[str, Cal
app.state.uvicorn_server = server_instance
def run_server():
- server_instance.run()
+ try:
+ logger.info(f"Starting MCP server on port {port}")
+ server_instance.run()
+ logger.info(f"MCP server on port {port} has stopped")
+ except Exception as e:
+ logger.error(f"MCP server failed to start on port {port}: {e}")
+ raise
# Start the server in a new thread
server_thread = threading.Thread(target=run_server, daemon=True)
+ logger.info(f"Starting MCP server thread on port {port}")
server_thread.start()
# Polling until the server is ready
@@ -229,24 +239,36 @@ def make_mcp_server(required_auth_token: str | None = None, tools: dict[str, Cal
start_time = time.time()
server_url = f"http://localhost:{port}/sse"
+ logger.info(f"Waiting for MCP server to be ready at {server_url}")
+
while time.time() - start_time < timeout:
try:
response = httpx.get(server_url)
if response.status_code in [200, 401]:
+ logger.info(f"MCP server is ready on port {port} (status: {response.status_code})")
break
- except httpx.RequestError:
+ except httpx.RequestError as e:
+ logger.debug(f"Server not ready yet, retrying... ({e})")
pass
time.sleep(0.1)
+ else:
+ # If we exit the loop due to timeout
+ logger.error(f"MCP server failed to start within {timeout} seconds on port {port}")
+ logger.error(f"Thread alive: {server_thread.is_alive()}")
+ if server_thread.is_alive():
+ logger.error("Server thread is still running but not responding to HTTP requests")
try:
yield {"server_url": server_url}
finally:
+ logger.info(f"Shutting down MCP server on port {port}")
server_instance.should_exit = True
time.sleep(0.5)
# Force shutdown if still running
if server_thread.is_alive():
try:
+ logger.info("Force shutting down server thread")
if hasattr(server_instance, "servers") and server_instance.servers:
for srv in server_instance.servers:
srv.close()
@@ -254,9 +276,9 @@ def make_mcp_server(required_auth_token: str | None = None, tools: dict[str, Cal
# Wait for graceful shutdown
server_thread.join(timeout=3)
if server_thread.is_alive():
- print("Warning: Server thread still alive after shutdown attempt")
+ logger.warning("Server thread still alive after shutdown attempt")
except Exception as e:
- print(f"Error during server shutdown: {e}")
+ logger.error(f"Error during server shutdown: {e}")
# CRITICAL: Reset SSE global state to prevent event loop contamination
# Reset the SSE AppStatus singleton that stores anyio.Event objects
diff --git a/tests/integration/recordings/responses/168daab89068.json b/tests/integration/recordings/responses/168daab89068.json
new file mode 100644
index 000000000..323b0dfd4
--- /dev/null
+++ b/tests/integration/recordings/responses/168daab89068.json
@@ -0,0 +1,167 @@
+{
+ "request": {
+ "method": "POST",
+ "url": "http://localhost:11434/api/generate",
+ "headers": {},
+ "body": {
+ "model": "llama3.2:3b-instruct-fp16",
+ "raw": true,
+ "prompt": "<|begin_of_text|><|start_header_id|>system<|end_header_id|>\n\nYou are a helpful assistant. You have access to functions, but you should only use them if they are required.\nYou are an expert in composing functions. You are given a question and a set of possible functions.\nBased on the question, you may or may not need to make one function/tool call to achieve the purpose.\n\nIf you decide to invoke any of the function(s), you MUST put it in the format of [func_name1(params_name1=params_value1, params_name2=params_value2...), func_name2(params)]\nIf you decide to invoke a function, you SHOULD NOT include any other text in the response. besides the function call in the above format.\nFor a boolean parameter, be sure to use `True` or `False` (capitalized) for the value.\n\n\nHere is a list of functions in JSON format that you can invoke.\n\n[\n {\n \"name\": \"greet_everyone\",\n \"description\": \"\",\n \"parameters\": {\n \"type\": \"dict\",\n \"required\": [\"url\"],\n \"properties\": {\n \"url\": {\n \"type\": \"string\",\n \"description\": \"\"\n }\n }\n }\n },\n {\n \"name\": \"get_boiling_point\",\n \"description\": \"\n Returns the boiling point of a liquid in Celsius or Fahrenheit.\n\n :param liquid_name: The name of the liquid\n :param celsius: Whether to return the boiling point in Celsius\n :return: The boiling point of the liquid in Celcius or Fahrenheit\n \",\n \"parameters\": {\n \"type\": \"dict\",\n \"required\": [\"liquid_name\"],\n \"properties\": {\n \"liquid_name\": {\n \"type\": \"string\",\n \"description\": \"\"\n },\n \"celsius\": {\n \"type\": \"boolean\",\n \"description\": \"\",\n \"default\": \"True\"\n }\n }\n }\n }\n]\n\nYou can answer general questions or invoke tools when necessary.\nIn addition to tool calls, you should also augment your responses by using the tool outputs.\nYou are a helpful assistant.<|eot_id|><|start_header_id|>user<|end_header_id|>\n\nSay hi to the world. Use tools to do so.<|eot_id|><|start_header_id|>assistant<|end_header_id|>\n\n[greet_everyone(url=\"world\")]<|eot_id|><|start_header_id|>ipython<|end_header_id|>\n\nHello, world!<|eot_id|><|start_header_id|>assistant<|end_header_id|>\n\n",
+ "options": {
+ "temperature": 0.0
+ },
+ "stream": true
+ },
+ "endpoint": "/api/generate",
+ "model": "llama3.2:3b-instruct-fp16"
+ },
+ "response": {
+ "body": [
+ {
+ "__type__": "ollama._types.GenerateResponse",
+ "__data__": {
+ "model": "llama3.2:3b-instruct-fp16",
+ "created_at": "2025-09-27T18:05:56.663224Z",
+ "done": false,
+ "done_reason": null,
+ "total_duration": null,
+ "load_duration": null,
+ "prompt_eval_count": null,
+ "prompt_eval_duration": null,
+ "eval_count": null,
+ "eval_duration": null,
+ "response": "How",
+ "thinking": null,
+ "context": null
+ }
+ },
+ {
+ "__type__": "ollama._types.GenerateResponse",
+ "__data__": {
+ "model": "llama3.2:3b-instruct-fp16",
+ "created_at": "2025-09-27T18:05:56.706706Z",
+ "done": false,
+ "done_reason": null,
+ "total_duration": null,
+ "load_duration": null,
+ "prompt_eval_count": null,
+ "prompt_eval_duration": null,
+ "eval_count": null,
+ "eval_duration": null,
+ "response": " can",
+ "thinking": null,
+ "context": null
+ }
+ },
+ {
+ "__type__": "ollama._types.GenerateResponse",
+ "__data__": {
+ "model": "llama3.2:3b-instruct-fp16",
+ "created_at": "2025-09-27T18:05:56.751075Z",
+ "done": false,
+ "done_reason": null,
+ "total_duration": null,
+ "load_duration": null,
+ "prompt_eval_count": null,
+ "prompt_eval_duration": null,
+ "eval_count": null,
+ "eval_duration": null,
+ "response": " I",
+ "thinking": null,
+ "context": null
+ }
+ },
+ {
+ "__type__": "ollama._types.GenerateResponse",
+ "__data__": {
+ "model": "llama3.2:3b-instruct-fp16",
+ "created_at": "2025-09-27T18:05:56.794187Z",
+ "done": false,
+ "done_reason": null,
+ "total_duration": null,
+ "load_duration": null,
+ "prompt_eval_count": null,
+ "prompt_eval_duration": null,
+ "eval_count": null,
+ "eval_duration": null,
+ "response": " assist",
+ "thinking": null,
+ "context": null
+ }
+ },
+ {
+ "__type__": "ollama._types.GenerateResponse",
+ "__data__": {
+ "model": "llama3.2:3b-instruct-fp16",
+ "created_at": "2025-09-27T18:05:56.837831Z",
+ "done": false,
+ "done_reason": null,
+ "total_duration": null,
+ "load_duration": null,
+ "prompt_eval_count": null,
+ "prompt_eval_duration": null,
+ "eval_count": null,
+ "eval_duration": null,
+ "response": " you",
+ "thinking": null,
+ "context": null
+ }
+ },
+ {
+ "__type__": "ollama._types.GenerateResponse",
+ "__data__": {
+ "model": "llama3.2:3b-instruct-fp16",
+ "created_at": "2025-09-27T18:05:56.879926Z",
+ "done": false,
+ "done_reason": null,
+ "total_duration": null,
+ "load_duration": null,
+ "prompt_eval_count": null,
+ "prompt_eval_duration": null,
+ "eval_count": null,
+ "eval_duration": null,
+ "response": " further",
+ "thinking": null,
+ "context": null
+ }
+ },
+ {
+ "__type__": "ollama._types.GenerateResponse",
+ "__data__": {
+ "model": "llama3.2:3b-instruct-fp16",
+ "created_at": "2025-09-27T18:05:56.92182Z",
+ "done": false,
+ "done_reason": null,
+ "total_duration": null,
+ "load_duration": null,
+ "prompt_eval_count": null,
+ "prompt_eval_duration": null,
+ "eval_count": null,
+ "eval_duration": null,
+ "response": "?",
+ "thinking": null,
+ "context": null
+ }
+ },
+ {
+ "__type__": "ollama._types.GenerateResponse",
+ "__data__": {
+ "model": "llama3.2:3b-instruct-fp16",
+ "created_at": "2025-09-27T18:05:56.963339Z",
+ "done": true,
+ "done_reason": "stop",
+ "total_duration": 492973041,
+ "load_duration": 103979375,
+ "prompt_eval_count": 482,
+ "prompt_eval_duration": 87032041,
+ "eval_count": 8,
+ "eval_duration": 300586375,
+ "response": "",
+ "thinking": null,
+ "context": null
+ }
+ }
+ ],
+ "is_streaming": true
+ }
+}
diff --git a/tests/integration/recordings/responses/949d3ad16367.json b/tests/integration/recordings/responses/949d3ad16367.json
new file mode 100644
index 000000000..21348ade2
--- /dev/null
+++ b/tests/integration/recordings/responses/949d3ad16367.json
@@ -0,0 +1,347 @@
+{
+ "request": {
+ "method": "POST",
+ "url": "http://localhost:11434/api/generate",
+ "headers": {},
+ "body": {
+ "model": "llama3.2:3b-instruct-fp16",
+ "raw": true,
+ "prompt": "<|begin_of_text|><|start_header_id|>system<|end_header_id|>\n\nYou are a helpful assistant. You have access to functions, but you should only use them if they are required.\nYou are an expert in composing functions. You are given a question and a set of possible functions.\nBased on the question, you may or may not need to make one function/tool call to achieve the purpose.\n\nIf you decide to invoke any of the function(s), you MUST put it in the format of [func_name1(params_name1=params_value1, params_name2=params_value2...), func_name2(params)]\nIf you decide to invoke a function, you SHOULD NOT include any other text in the response. besides the function call in the above format.\nFor a boolean parameter, be sure to use `True` or `False` (capitalized) for the value.\n\n\nHere is a list of functions in JSON format that you can invoke.\n\n[\n {\n \"name\": \"greet_everyone\",\n \"description\": \"\",\n \"parameters\": {\n \"type\": \"dict\",\n \"required\": [\"url\"],\n \"properties\": {\n \"url\": {\n \"type\": \"string\",\n \"description\": \"\"\n }\n }\n }\n },\n {\n \"name\": \"get_boiling_point\",\n \"description\": \"\n Returns the boiling point of a liquid in Celsius or Fahrenheit.\n\n :param liquid_name: The name of the liquid\n :param celsius: Whether to return the boiling point in Celsius\n :return: The boiling point of the liquid in Celcius or Fahrenheit\n \",\n \"parameters\": {\n \"type\": \"dict\",\n \"required\": [\"liquid_name\"],\n \"properties\": {\n \"liquid_name\": {\n \"type\": \"string\",\n \"description\": \"\"\n },\n \"celsius\": {\n \"type\": \"boolean\",\n \"description\": \"\",\n \"default\": \"True\"\n }\n }\n }\n }\n]\n\nYou can answer general questions or invoke tools when necessary.\nIn addition to tool calls, you should also augment your responses by using the tool outputs.\nYou are a helpful assistant.<|eot_id|><|start_header_id|>user<|end_header_id|>\n\nSay hi to the world. Use tools to do so.<|eot_id|><|start_header_id|>assistant<|end_header_id|>\n\n[greet_everyone(url=\"world\")]<|eot_id|><|start_header_id|>ipython<|end_header_id|>\n\nHello, world!<|eot_id|><|start_header_id|>assistant<|end_header_id|>\n\nHow can I assist you further?<|eot_id|><|start_header_id|>user<|end_header_id|>\n\nWhat is the boiling point of polyjuice? Use tools to answer.<|eot_id|><|start_header_id|>assistant<|end_header_id|>\n\n",
+ "options": {
+ "temperature": 0.0
+ },
+ "stream": true
+ },
+ "endpoint": "/api/generate",
+ "model": "llama3.2:3b-instruct-fp16"
+ },
+ "response": {
+ "body": [
+ {
+ "__type__": "ollama._types.GenerateResponse",
+ "__data__": {
+ "model": "llama3.2:3b-instruct-fp16",
+ "created_at": "2025-09-27T18:05:57.177453Z",
+ "done": false,
+ "done_reason": null,
+ "total_duration": null,
+ "load_duration": null,
+ "prompt_eval_count": null,
+ "prompt_eval_duration": null,
+ "eval_count": null,
+ "eval_duration": null,
+ "response": "[",
+ "thinking": null,
+ "context": null
+ }
+ },
+ {
+ "__type__": "ollama._types.GenerateResponse",
+ "__data__": {
+ "model": "llama3.2:3b-instruct-fp16",
+ "created_at": "2025-09-27T18:05:57.220271Z",
+ "done": false,
+ "done_reason": null,
+ "total_duration": null,
+ "load_duration": null,
+ "prompt_eval_count": null,
+ "prompt_eval_duration": null,
+ "eval_count": null,
+ "eval_duration": null,
+ "response": "get",
+ "thinking": null,
+ "context": null
+ }
+ },
+ {
+ "__type__": "ollama._types.GenerateResponse",
+ "__data__": {
+ "model": "llama3.2:3b-instruct-fp16",
+ "created_at": "2025-09-27T18:05:57.261232Z",
+ "done": false,
+ "done_reason": null,
+ "total_duration": null,
+ "load_duration": null,
+ "prompt_eval_count": null,
+ "prompt_eval_duration": null,
+ "eval_count": null,
+ "eval_duration": null,
+ "response": "_bo",
+ "thinking": null,
+ "context": null
+ }
+ },
+ {
+ "__type__": "ollama._types.GenerateResponse",
+ "__data__": {
+ "model": "llama3.2:3b-instruct-fp16",
+ "created_at": "2025-09-27T18:05:57.302818Z",
+ "done": false,
+ "done_reason": null,
+ "total_duration": null,
+ "load_duration": null,
+ "prompt_eval_count": null,
+ "prompt_eval_duration": null,
+ "eval_count": null,
+ "eval_duration": null,
+ "response": "iling",
+ "thinking": null,
+ "context": null
+ }
+ },
+ {
+ "__type__": "ollama._types.GenerateResponse",
+ "__data__": {
+ "model": "llama3.2:3b-instruct-fp16",
+ "created_at": "2025-09-27T18:05:57.344343Z",
+ "done": false,
+ "done_reason": null,
+ "total_duration": null,
+ "load_duration": null,
+ "prompt_eval_count": null,
+ "prompt_eval_duration": null,
+ "eval_count": null,
+ "eval_duration": null,
+ "response": "_point",
+ "thinking": null,
+ "context": null
+ }
+ },
+ {
+ "__type__": "ollama._types.GenerateResponse",
+ "__data__": {
+ "model": "llama3.2:3b-instruct-fp16",
+ "created_at": "2025-09-27T18:05:57.386025Z",
+ "done": false,
+ "done_reason": null,
+ "total_duration": null,
+ "load_duration": null,
+ "prompt_eval_count": null,
+ "prompt_eval_duration": null,
+ "eval_count": null,
+ "eval_duration": null,
+ "response": "(",
+ "thinking": null,
+ "context": null
+ }
+ },
+ {
+ "__type__": "ollama._types.GenerateResponse",
+ "__data__": {
+ "model": "llama3.2:3b-instruct-fp16",
+ "created_at": "2025-09-27T18:05:57.42778Z",
+ "done": false,
+ "done_reason": null,
+ "total_duration": null,
+ "load_duration": null,
+ "prompt_eval_count": null,
+ "prompt_eval_duration": null,
+ "eval_count": null,
+ "eval_duration": null,
+ "response": "liquid",
+ "thinking": null,
+ "context": null
+ }
+ },
+ {
+ "__type__": "ollama._types.GenerateResponse",
+ "__data__": {
+ "model": "llama3.2:3b-instruct-fp16",
+ "created_at": "2025-09-27T18:05:57.469673Z",
+ "done": false,
+ "done_reason": null,
+ "total_duration": null,
+ "load_duration": null,
+ "prompt_eval_count": null,
+ "prompt_eval_duration": null,
+ "eval_count": null,
+ "eval_duration": null,
+ "response": "_name",
+ "thinking": null,
+ "context": null
+ }
+ },
+ {
+ "__type__": "ollama._types.GenerateResponse",
+ "__data__": {
+ "model": "llama3.2:3b-instruct-fp16",
+ "created_at": "2025-09-27T18:05:57.512543Z",
+ "done": false,
+ "done_reason": null,
+ "total_duration": null,
+ "load_duration": null,
+ "prompt_eval_count": null,
+ "prompt_eval_duration": null,
+ "eval_count": null,
+ "eval_duration": null,
+ "response": "='",
+ "thinking": null,
+ "context": null
+ }
+ },
+ {
+ "__type__": "ollama._types.GenerateResponse",
+ "__data__": {
+ "model": "llama3.2:3b-instruct-fp16",
+ "created_at": "2025-09-27T18:05:57.554479Z",
+ "done": false,
+ "done_reason": null,
+ "total_duration": null,
+ "load_duration": null,
+ "prompt_eval_count": null,
+ "prompt_eval_duration": null,
+ "eval_count": null,
+ "eval_duration": null,
+ "response": "poly",
+ "thinking": null,
+ "context": null
+ }
+ },
+ {
+ "__type__": "ollama._types.GenerateResponse",
+ "__data__": {
+ "model": "llama3.2:3b-instruct-fp16",
+ "created_at": "2025-09-27T18:05:57.597092Z",
+ "done": false,
+ "done_reason": null,
+ "total_duration": null,
+ "load_duration": null,
+ "prompt_eval_count": null,
+ "prompt_eval_duration": null,
+ "eval_count": null,
+ "eval_duration": null,
+ "response": "ju",
+ "thinking": null,
+ "context": null
+ }
+ },
+ {
+ "__type__": "ollama._types.GenerateResponse",
+ "__data__": {
+ "model": "llama3.2:3b-instruct-fp16",
+ "created_at": "2025-09-27T18:05:57.639581Z",
+ "done": false,
+ "done_reason": null,
+ "total_duration": null,
+ "load_duration": null,
+ "prompt_eval_count": null,
+ "prompt_eval_duration": null,
+ "eval_count": null,
+ "eval_duration": null,
+ "response": "ice",
+ "thinking": null,
+ "context": null
+ }
+ },
+ {
+ "__type__": "ollama._types.GenerateResponse",
+ "__data__": {
+ "model": "llama3.2:3b-instruct-fp16",
+ "created_at": "2025-09-27T18:05:57.683223Z",
+ "done": false,
+ "done_reason": null,
+ "total_duration": null,
+ "load_duration": null,
+ "prompt_eval_count": null,
+ "prompt_eval_duration": null,
+ "eval_count": null,
+ "eval_duration": null,
+ "response": "',",
+ "thinking": null,
+ "context": null
+ }
+ },
+ {
+ "__type__": "ollama._types.GenerateResponse",
+ "__data__": {
+ "model": "llama3.2:3b-instruct-fp16",
+ "created_at": "2025-09-27T18:05:57.72556Z",
+ "done": false,
+ "done_reason": null,
+ "total_duration": null,
+ "load_duration": null,
+ "prompt_eval_count": null,
+ "prompt_eval_duration": null,
+ "eval_count": null,
+ "eval_duration": null,
+ "response": " c",
+ "thinking": null,
+ "context": null
+ }
+ },
+ {
+ "__type__": "ollama._types.GenerateResponse",
+ "__data__": {
+ "model": "llama3.2:3b-instruct-fp16",
+ "created_at": "2025-09-27T18:05:57.768012Z",
+ "done": false,
+ "done_reason": null,
+ "total_duration": null,
+ "load_duration": null,
+ "prompt_eval_count": null,
+ "prompt_eval_duration": null,
+ "eval_count": null,
+ "eval_duration": null,
+ "response": "elsius",
+ "thinking": null,
+ "context": null
+ }
+ },
+ {
+ "__type__": "ollama._types.GenerateResponse",
+ "__data__": {
+ "model": "llama3.2:3b-instruct-fp16",
+ "created_at": "2025-09-27T18:05:57.8098Z",
+ "done": false,
+ "done_reason": null,
+ "total_duration": null,
+ "load_duration": null,
+ "prompt_eval_count": null,
+ "prompt_eval_duration": null,
+ "eval_count": null,
+ "eval_duration": null,
+ "response": "=True",
+ "thinking": null,
+ "context": null
+ }
+ },
+ {
+ "__type__": "ollama._types.GenerateResponse",
+ "__data__": {
+ "model": "llama3.2:3b-instruct-fp16",
+ "created_at": "2025-09-27T18:05:57.851578Z",
+ "done": false,
+ "done_reason": null,
+ "total_duration": null,
+ "load_duration": null,
+ "prompt_eval_count": null,
+ "prompt_eval_duration": null,
+ "eval_count": null,
+ "eval_duration": null,
+ "response": ")]",
+ "thinking": null,
+ "context": null
+ }
+ },
+ {
+ "__type__": "ollama._types.GenerateResponse",
+ "__data__": {
+ "model": "llama3.2:3b-instruct-fp16",
+ "created_at": "2025-09-27T18:05:57.893693Z",
+ "done": true,
+ "done_reason": "stop",
+ "total_duration": 885274541,
+ "load_duration": 99578333,
+ "prompt_eval_count": 514,
+ "prompt_eval_duration": 67915875,
+ "eval_count": 18,
+ "eval_duration": 717086791,
+ "response": "",
+ "thinking": null,
+ "context": null
+ }
+ }
+ ],
+ "is_streaming": true
+ }
+}
diff --git a/tests/integration/recordings/responses/d0ac68cbde69.json b/tests/integration/recordings/responses/d0ac68cbde69.json
index b37962fb6..78784e0ca 100644
--- a/tests/integration/recordings/responses/d0ac68cbde69.json
+++ b/tests/integration/recordings/responses/d0ac68cbde69.json
@@ -11,7 +11,27 @@
"body": {
"__type__": "ollama._types.ProcessResponse",
"__data__": {
- "models": []
+ "models": [
+ {
+ "model": "llama3.2:3b",
+ "name": "llama3.2:3b",
+ "digest": "a80c4f17acd55265feec403c7aef86be0c25983ab279d83f3bcd3abbcb5b8b72",
+ "expires_at": "2025-09-27T11:54:56.718552-07:00",
+ "size": 3367856128,
+ "size_vram": 3367856128,
+ "details": {
+ "parent_model": "",
+ "format": "gguf",
+ "family": "llama",
+ "families": [
+ "llama"
+ ],
+ "parameter_size": "3.2B",
+ "quantization_level": "Q4_K_M"
+ },
+ "context_length": 4096
+ }
+ ]
}
},
"is_streaming": false
diff --git a/tests/integration/recordings/responses/e61266e87842.json b/tests/integration/recordings/responses/e61266e87842.json
new file mode 100644
index 000000000..572279188
--- /dev/null
+++ b/tests/integration/recordings/responses/e61266e87842.json
@@ -0,0 +1,185 @@
+{
+ "request": {
+ "method": "POST",
+ "url": "http://localhost:11434/api/generate",
+ "headers": {},
+ "body": {
+ "model": "llama3.2:3b-instruct-fp16",
+ "raw": true,
+ "prompt": "<|begin_of_text|><|start_header_id|>system<|end_header_id|>\n\nYou are a helpful assistant. You have access to functions, but you should only use them if they are required.\nYou are an expert in composing functions. You are given a question and a set of possible functions.\nBased on the question, you may or may not need to make one function/tool call to achieve the purpose.\n\nIf you decide to invoke any of the function(s), you MUST put it in the format of [func_name1(params_name1=params_value1, params_name2=params_value2...), func_name2(params)]\nIf you decide to invoke a function, you SHOULD NOT include any other text in the response. besides the function call in the above format.\nFor a boolean parameter, be sure to use `True` or `False` (capitalized) for the value.\n\n\nHere is a list of functions in JSON format that you can invoke.\n\n[\n {\n \"name\": \"greet_everyone\",\n \"description\": \"\",\n \"parameters\": {\n \"type\": \"dict\",\n \"required\": [\"url\"],\n \"properties\": {\n \"url\": {\n \"type\": \"string\",\n \"description\": \"\"\n }\n }\n }\n },\n {\n \"name\": \"get_boiling_point\",\n \"description\": \"\n Returns the boiling point of a liquid in Celsius or Fahrenheit.\n\n :param liquid_name: The name of the liquid\n :param celsius: Whether to return the boiling point in Celsius\n :return: The boiling point of the liquid in Celcius or Fahrenheit\n \",\n \"parameters\": {\n \"type\": \"dict\",\n \"required\": [\"liquid_name\"],\n \"properties\": {\n \"liquid_name\": {\n \"type\": \"string\",\n \"description\": \"\"\n },\n \"celsius\": {\n \"type\": \"boolean\",\n \"description\": \"\",\n \"default\": \"True\"\n }\n }\n }\n }\n]\n\nYou can answer general questions or invoke tools when necessary.\nIn addition to tool calls, you should also augment your responses by using the tool outputs.\nYou are a helpful assistant.<|eot_id|><|start_header_id|>user<|end_header_id|>\n\nSay hi to the world. Use tools to do so.<|eot_id|><|start_header_id|>assistant<|end_header_id|>\n\n",
+ "options": {
+ "temperature": 0.0
+ },
+ "stream": true
+ },
+ "endpoint": "/api/generate",
+ "model": "llama3.2:3b-instruct-fp16"
+ },
+ "response": {
+ "body": [
+ {
+ "__type__": "ollama._types.GenerateResponse",
+ "__data__": {
+ "model": "llama3.2:3b-instruct-fp16",
+ "created_at": "2025-09-27T18:05:56.034121Z",
+ "done": false,
+ "done_reason": null,
+ "total_duration": null,
+ "load_duration": null,
+ "prompt_eval_count": null,
+ "prompt_eval_duration": null,
+ "eval_count": null,
+ "eval_duration": null,
+ "response": "[g",
+ "thinking": null,
+ "context": null
+ }
+ },
+ {
+ "__type__": "ollama._types.GenerateResponse",
+ "__data__": {
+ "model": "llama3.2:3b-instruct-fp16",
+ "created_at": "2025-09-27T18:05:56.07569Z",
+ "done": false,
+ "done_reason": null,
+ "total_duration": null,
+ "load_duration": null,
+ "prompt_eval_count": null,
+ "prompt_eval_duration": null,
+ "eval_count": null,
+ "eval_duration": null,
+ "response": "reet",
+ "thinking": null,
+ "context": null
+ }
+ },
+ {
+ "__type__": "ollama._types.GenerateResponse",
+ "__data__": {
+ "model": "llama3.2:3b-instruct-fp16",
+ "created_at": "2025-09-27T18:05:56.116927Z",
+ "done": false,
+ "done_reason": null,
+ "total_duration": null,
+ "load_duration": null,
+ "prompt_eval_count": null,
+ "prompt_eval_duration": null,
+ "eval_count": null,
+ "eval_duration": null,
+ "response": "_every",
+ "thinking": null,
+ "context": null
+ }
+ },
+ {
+ "__type__": "ollama._types.GenerateResponse",
+ "__data__": {
+ "model": "llama3.2:3b-instruct-fp16",
+ "created_at": "2025-09-27T18:05:56.159755Z",
+ "done": false,
+ "done_reason": null,
+ "total_duration": null,
+ "load_duration": null,
+ "prompt_eval_count": null,
+ "prompt_eval_duration": null,
+ "eval_count": null,
+ "eval_duration": null,
+ "response": "one",
+ "thinking": null,
+ "context": null
+ }
+ },
+ {
+ "__type__": "ollama._types.GenerateResponse",
+ "__data__": {
+ "model": "llama3.2:3b-instruct-fp16",
+ "created_at": "2025-09-27T18:05:56.201675Z",
+ "done": false,
+ "done_reason": null,
+ "total_duration": null,
+ "load_duration": null,
+ "prompt_eval_count": null,
+ "prompt_eval_duration": null,
+ "eval_count": null,
+ "eval_duration": null,
+ "response": "(url",
+ "thinking": null,
+ "context": null
+ }
+ },
+ {
+ "__type__": "ollama._types.GenerateResponse",
+ "__data__": {
+ "model": "llama3.2:3b-instruct-fp16",
+ "created_at": "2025-09-27T18:05:56.243056Z",
+ "done": false,
+ "done_reason": null,
+ "total_duration": null,
+ "load_duration": null,
+ "prompt_eval_count": null,
+ "prompt_eval_duration": null,
+ "eval_count": null,
+ "eval_duration": null,
+ "response": "=\"",
+ "thinking": null,
+ "context": null
+ }
+ },
+ {
+ "__type__": "ollama._types.GenerateResponse",
+ "__data__": {
+ "model": "llama3.2:3b-instruct-fp16",
+ "created_at": "2025-09-27T18:05:56.284651Z",
+ "done": false,
+ "done_reason": null,
+ "total_duration": null,
+ "load_duration": null,
+ "prompt_eval_count": null,
+ "prompt_eval_duration": null,
+ "eval_count": null,
+ "eval_duration": null,
+ "response": "world",
+ "thinking": null,
+ "context": null
+ }
+ },
+ {
+ "__type__": "ollama._types.GenerateResponse",
+ "__data__": {
+ "model": "llama3.2:3b-instruct-fp16",
+ "created_at": "2025-09-27T18:05:56.326276Z",
+ "done": false,
+ "done_reason": null,
+ "total_duration": null,
+ "load_duration": null,
+ "prompt_eval_count": null,
+ "prompt_eval_duration": null,
+ "eval_count": null,
+ "eval_duration": null,
+ "response": "\")]",
+ "thinking": null,
+ "context": null
+ }
+ },
+ {
+ "__type__": "ollama._types.GenerateResponse",
+ "__data__": {
+ "model": "llama3.2:3b-instruct-fp16",
+ "created_at": "2025-09-27T18:05:56.367959Z",
+ "done": true,
+ "done_reason": "stop",
+ "total_duration": 5381441291,
+ "load_duration": 4112439791,
+ "prompt_eval_count": 459,
+ "prompt_eval_duration": 932587833,
+ "eval_count": 9,
+ "eval_duration": 334328250,
+ "response": "",
+ "thinking": null,
+ "context": null
+ }
+ }
+ ],
+ "is_streaming": true
+ }
+}
diff --git a/tests/integration/recordings/responses/models-bd032f995f2a-52e8575f.json b/tests/integration/recordings/responses/models-bd032f995f2a-52e8575f.json
new file mode 100644
index 000000000..161c12006
--- /dev/null
+++ b/tests/integration/recordings/responses/models-bd032f995f2a-52e8575f.json
@@ -0,0 +1,834 @@
+{
+ "request": {
+ "method": "POST",
+ "url": "https://api.openai.com/v1/v1/models",
+ "headers": {},
+ "body": {},
+ "endpoint": "/v1/models",
+ "model": ""
+ },
+ "response": {
+ "body": [
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "gpt-4-0613",
+ "created": 1686588896,
+ "object": "model",
+ "owned_by": "openai"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "gpt-4",
+ "created": 1687882411,
+ "object": "model",
+ "owned_by": "openai"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "gpt-3.5-turbo",
+ "created": 1677610602,
+ "object": "model",
+ "owned_by": "openai"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "gpt-5-codex",
+ "created": 1757527818,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "gpt-audio-2025-08-28",
+ "created": 1756256146,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "gpt-realtime",
+ "created": 1756271701,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "gpt-realtime-2025-08-28",
+ "created": 1756271773,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "gpt-audio",
+ "created": 1756339249,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "davinci-002",
+ "created": 1692634301,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "babbage-002",
+ "created": 1692634615,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "gpt-3.5-turbo-instruct",
+ "created": 1692901427,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "gpt-3.5-turbo-instruct-0914",
+ "created": 1694122472,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "dall-e-3",
+ "created": 1698785189,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "dall-e-2",
+ "created": 1698798177,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "gpt-4-1106-preview",
+ "created": 1698957206,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "gpt-3.5-turbo-1106",
+ "created": 1698959748,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "tts-1-hd",
+ "created": 1699046015,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "tts-1-1106",
+ "created": 1699053241,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "tts-1-hd-1106",
+ "created": 1699053533,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "text-embedding-3-small",
+ "created": 1705948997,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "text-embedding-3-large",
+ "created": 1705953180,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "gpt-4-0125-preview",
+ "created": 1706037612,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "gpt-4-turbo-preview",
+ "created": 1706037777,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "gpt-3.5-turbo-0125",
+ "created": 1706048358,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "gpt-4-turbo",
+ "created": 1712361441,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "gpt-4-turbo-2024-04-09",
+ "created": 1712601677,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "gpt-4o",
+ "created": 1715367049,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "gpt-4o-2024-05-13",
+ "created": 1715368132,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "gpt-4o-mini-2024-07-18",
+ "created": 1721172717,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "gpt-4o-mini",
+ "created": 1721172741,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "gpt-4o-2024-08-06",
+ "created": 1722814719,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "chatgpt-4o-latest",
+ "created": 1723515131,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "o1-mini-2024-09-12",
+ "created": 1725648979,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "o1-mini",
+ "created": 1725649008,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "gpt-4o-realtime-preview-2024-10-01",
+ "created": 1727131766,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "gpt-4o-audio-preview-2024-10-01",
+ "created": 1727389042,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "gpt-4o-audio-preview",
+ "created": 1727460443,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "gpt-4o-realtime-preview",
+ "created": 1727659998,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "omni-moderation-latest",
+ "created": 1731689265,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "omni-moderation-2024-09-26",
+ "created": 1732734466,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "gpt-4o-realtime-preview-2024-12-17",
+ "created": 1733945430,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "gpt-4o-audio-preview-2024-12-17",
+ "created": 1734034239,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "gpt-4o-mini-realtime-preview-2024-12-17",
+ "created": 1734112601,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "gpt-4o-mini-audio-preview-2024-12-17",
+ "created": 1734115920,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "o1-2024-12-17",
+ "created": 1734326976,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "o1",
+ "created": 1734375816,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "gpt-4o-mini-realtime-preview",
+ "created": 1734387380,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "gpt-4o-mini-audio-preview",
+ "created": 1734387424,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "o3-mini",
+ "created": 1737146383,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "o3-mini-2025-01-31",
+ "created": 1738010200,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "gpt-4o-2024-11-20",
+ "created": 1739331543,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "gpt-4o-search-preview-2025-03-11",
+ "created": 1741388170,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "gpt-4o-search-preview",
+ "created": 1741388720,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "gpt-4o-mini-search-preview-2025-03-11",
+ "created": 1741390858,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "gpt-4o-mini-search-preview",
+ "created": 1741391161,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "gpt-4o-transcribe",
+ "created": 1742068463,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "gpt-4o-mini-transcribe",
+ "created": 1742068596,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "o1-pro-2025-03-19",
+ "created": 1742251504,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "o1-pro",
+ "created": 1742251791,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "gpt-4o-mini-tts",
+ "created": 1742403959,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "o3-2025-04-16",
+ "created": 1744133301,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "o4-mini-2025-04-16",
+ "created": 1744133506,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "o3",
+ "created": 1744225308,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "o4-mini",
+ "created": 1744225351,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "gpt-4.1-2025-04-14",
+ "created": 1744315746,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "gpt-4.1",
+ "created": 1744316542,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "gpt-4.1-mini-2025-04-14",
+ "created": 1744317547,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "gpt-4.1-mini",
+ "created": 1744318173,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "gpt-4.1-nano-2025-04-14",
+ "created": 1744321025,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "gpt-4.1-nano",
+ "created": 1744321707,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "gpt-image-1",
+ "created": 1745517030,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "codex-mini-latest",
+ "created": 1746673257,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "o3-pro",
+ "created": 1748475349,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "gpt-4o-realtime-preview-2025-06-03",
+ "created": 1748907838,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "gpt-4o-audio-preview-2025-06-03",
+ "created": 1748908498,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "o3-pro-2025-06-10",
+ "created": 1749166761,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "o4-mini-deep-research",
+ "created": 1749685485,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "o3-deep-research",
+ "created": 1749840121,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "o3-deep-research-2025-06-26",
+ "created": 1750865219,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "o4-mini-deep-research-2025-06-26",
+ "created": 1750866121,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "gpt-5-chat-latest",
+ "created": 1754073306,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "gpt-5-2025-08-07",
+ "created": 1754075360,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "gpt-5",
+ "created": 1754425777,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "gpt-5-mini-2025-08-07",
+ "created": 1754425867,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "gpt-5-mini",
+ "created": 1754425928,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "gpt-5-nano-2025-08-07",
+ "created": 1754426303,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "gpt-5-nano",
+ "created": 1754426384,
+ "object": "model",
+ "owned_by": "system"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "gpt-3.5-turbo-16k",
+ "created": 1683758102,
+ "object": "model",
+ "owned_by": "openai-internal"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "tts-1",
+ "created": 1681940951,
+ "object": "model",
+ "owned_by": "openai-internal"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "whisper-1",
+ "created": 1677532384,
+ "object": "model",
+ "owned_by": "openai-internal"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "text-embedding-ada-002",
+ "created": 1671217299,
+ "object": "model",
+ "owned_by": "openai-internal"
+ }
+ }
+ ],
+ "is_streaming": false
+ }
+}
diff --git a/tests/integration/recordings/responses/models-bd032f995f2a-e660ee4a.json b/tests/integration/recordings/responses/models-bd032f995f2a-e660ee4a.json
new file mode 100644
index 000000000..8f1a5a012
--- /dev/null
+++ b/tests/integration/recordings/responses/models-bd032f995f2a-e660ee4a.json
@@ -0,0 +1,96 @@
+{
+ "request": {
+ "method": "POST",
+ "url": "http://0.0.0.0:11434/v1/v1/models",
+ "headers": {},
+ "body": {},
+ "endpoint": "/v1/models",
+ "model": ""
+ },
+ "response": {
+ "body": [
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "nomic-embed-text:latest",
+ "created": 1756922046,
+ "object": "model",
+ "owned_by": "library"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "all-minilm:l6-v2",
+ "created": 1756919946,
+ "object": "model",
+ "owned_by": "library"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "llama3.2-vision:11b",
+ "created": 1753926302,
+ "object": "model",
+ "owned_by": "library"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "llama3.2-vision:latest",
+ "created": 1753845527,
+ "object": "model",
+ "owned_by": "library"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "llama-guard3:1b",
+ "created": 1753479584,
+ "object": "model",
+ "owned_by": "library"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "llama3.2:1b",
+ "created": 1752814944,
+ "object": "model",
+ "owned_by": "library"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "all-minilm:latest",
+ "created": 1748994610,
+ "object": "model",
+ "owned_by": "library"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "llama3.2:3b",
+ "created": 1746123323,
+ "object": "model",
+ "owned_by": "library"
+ }
+ },
+ {
+ "__type__": "openai.types.model.Model",
+ "__data__": {
+ "id": "llama3.2:3b-instruct-fp16",
+ "created": 1746052428,
+ "object": "model",
+ "owned_by": "library"
+ }
+ }
+ ],
+ "is_streaming": false
+ }
+}
diff --git a/tests/integration/tool_runtime/test_mcp.py b/tests/integration/tool_runtime/test_mcp.py
index 91ed26684..831186b15 100644
--- a/tests/integration/tool_runtime/test_mcp.py
+++ b/tests/integration/tool_runtime/test_mcp.py
@@ -31,6 +31,11 @@ def test_mcp_invocation(llama_stack_client, text_model_id, mcp_server):
uri = mcp_server["server_url"]
# registering should not raise an error anymore even if you don't specify the auth token
+ try:
+ llama_stack_client.toolgroups.unregister(toolgroup_id=test_toolgroup_id)
+ except Exception:
+ pass
+
llama_stack_client.toolgroups.register(
toolgroup_id=test_toolgroup_id,
provider_id="model-context-protocol",
diff --git a/tests/unit/providers/agent/test_meta_reference_agent.py b/tests/unit/providers/agent/test_meta_reference_agent.py
index c06d9ab0e..07e5aa79d 100644
--- a/tests/unit/providers/agent/test_meta_reference_agent.py
+++ b/tests/unit/providers/agent/test_meta_reference_agent.py
@@ -16,9 +16,11 @@ from llama_stack.apis.agents import (
)
from llama_stack.apis.common.responses import PaginatedResponse
from llama_stack.apis.inference import Inference
+from llama_stack.apis.resource import ResourceType
from llama_stack.apis.safety import Safety
-from llama_stack.apis.tools import ToolGroups, ToolRuntime
+from llama_stack.apis.tools import ListToolsResponse, Tool, ToolGroups, ToolParameter, ToolRuntime
from llama_stack.apis.vector_io import VectorIO
+from llama_stack.providers.inline.agents.meta_reference.agent_instance import ChatAgent
from llama_stack.providers.inline.agents.meta_reference.agents import MetaReferenceAgentsImpl
from llama_stack.providers.inline.agents.meta_reference.config import MetaReferenceAgentsImplConfig
from llama_stack.providers.inline.agents.meta_reference.persistence import AgentInfo
@@ -75,11 +77,11 @@ def sample_agent_config():
},
input_shields=["string"],
output_shields=["string"],
- toolgroups=["string"],
+ toolgroups=["mcp::my_mcp_server"],
client_tools=[
{
- "name": "string",
- "description": "string",
+ "name": "client_tool",
+ "description": "Client Tool",
"parameters": [
{
"name": "string",
@@ -226,3 +228,83 @@ async def test_delete_agent(agents_impl, sample_agent_config):
# Verify the agent was deleted
with pytest.raises(ValueError):
await agents_impl.get_agent(agent_id)
+
+
+async def test__initialize_tools(agents_impl, sample_agent_config):
+ # Mock tool_groups_api.list_tools()
+ agents_impl.tool_groups_api.list_tools.return_value = ListToolsResponse(
+ data=[
+ Tool(
+ identifier="story_maker",
+ provider_id="model-context-protocol",
+ type=ResourceType.tool,
+ toolgroup_id="mcp::my_mcp_server",
+ description="Make a story",
+ parameters=[
+ ToolParameter(
+ name="story_title",
+ parameter_type="string",
+ description="Title of the story",
+ required=True,
+ title="Story Title",
+ ),
+ ToolParameter(
+ name="input_words",
+ parameter_type="array",
+ description="Input words",
+ required=False,
+ items={"type": "string"},
+ title="Input Words",
+ default=[],
+ ),
+ ],
+ )
+ ]
+ )
+
+ create_response = await agents_impl.create_agent(sample_agent_config)
+ agent_id = create_response.agent_id
+
+ # Get an instance of ChatAgent
+ chat_agent = await agents_impl._get_agent_impl(agent_id)
+ assert chat_agent is not None
+ assert isinstance(chat_agent, ChatAgent)
+
+ # Initialize tool definitions
+ await chat_agent._initialize_tools()
+ assert len(chat_agent.tool_defs) == 2
+
+ # Verify the first tool, which is a client tool
+ first_tool = chat_agent.tool_defs[0]
+ assert first_tool.tool_name == "client_tool"
+ assert first_tool.description == "Client Tool"
+
+ # Verify the second tool, which is an MCP tool that has an array-type property
+ second_tool = chat_agent.tool_defs[1]
+ assert second_tool.tool_name == "story_maker"
+ assert second_tool.description == "Make a story"
+
+ parameters = second_tool.parameters
+ assert len(parameters) == 2
+
+ # Verify a string property
+ story_title = parameters.get("story_title")
+ assert story_title is not None
+ assert story_title.param_type == "string"
+ assert story_title.description == "Title of the story"
+ assert story_title.required
+ assert story_title.items is None
+ assert story_title.title == "Story Title"
+ assert story_title.default is None
+
+ # Verify an array property
+ input_words = parameters.get("input_words")
+ assert input_words is not None
+ assert input_words.param_type == "array"
+ assert input_words.description == "Input words"
+ assert not input_words.required
+ assert input_words.items is not None
+ assert len(input_words.items) == 1
+ assert input_words.items.get("type") == "string"
+ assert input_words.title == "Input Words"
+ assert input_words.default == []