[PR #10091] [MERGED] types: include the 'items' and '$defs' fields to properly handle "array" types #39017

Closed
opened 2026-04-22 23:39:39 -05:00 by GiteaMirror · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/ollama/ollama/pull/10091
Author: @sheffler
Created: 4/2/2025
Status: Merged
Merged: 4/10/2025
Merged by: @ParthSareen

Base: mainHead: itemsFieldForJsonSchemaTypes


📝 Commits (6)

  • da30598 support rich types: include the items field to properly handle "array" types
  • 73f760c include the $defs field as well
  • 9479f0a Merge branch 'main' into itemsFieldForJsonSchemaTypes
  • 56e7739 Update api/types.go
  • d8526d6 update tests
  • e5335ef merge and fix tests

📊 Changes

3 files changed (+16 additions, -0 deletions)

View changed files

📝 api/types.go (+4 -0)
📝 openai/openai_test.go (+4 -0)
📝 server/routes_generate_test.go (+8 -0)

📄 Description

This PR adds the capability for Ollama to handle tool argument types that are homegeneous arrays more correctly.

BACKGROUND

JSON-Schema allows the description of object with named properties. In addition, the type of each property can be indicated with a "type" annotation with value "string", "integer", "number", "array", etc.

{
  "field1": {
    "type": "string"
   },
   "field2": {
     "type": "number"
   }
}

An "array" can hold objects of arbitrary type. Thus it is acceptable to have an array
like

[1, 2.0, "tom", false, 5]

If an array should be constrained to hold values that all obey the same schema, then
an additional keyword, "items", can be included to indicate that.

{
  "field3": {
    "type": "array",
    "items": "number"
   }
}

The example above shows how a field would be constrained to only hold lists of numbers.

Ollama does not recognize the "items" keyword in a JSON-Schema.

EXPLANATION

Ollama does not demarshall or store the contents of an "items" field, even when it is presented in the /api/chat request given by a client. Without including it, an LLM cannot know the structure of an array when it is trying to fullfill a request.

(In my tests with Llama3.2 and an MCP tool that accepted an array of objects with fields, Llama3.2 would consistently provide an array of strings since it could not know any better. The tool call would simply fail.)

With the modification to include the "items" field, Llama3.2 can fullfill a request that satisfies the type signature of a function that takes an array of objects.

Interestingly, Ollama already includes all of the fields present in a Tool definition when it serializes it to a string, so by simply including the missing field it is passed to the LLM.

EXAMPLE and SUCCESS

The following is an example request generated by OTERM (https://github.com/ggozad/oterm) with an attached MCP Tool (Model Context Protocol Tool) called "preparation_tool". The preparation_tool operates on an array of structures, all of the same type. With the code modifications shown here, the "items" field is included with the text presented to Llama3.2 which can form a tool call that matches the type signature.

The prompt loads a list of (sample, mass) pairs as a markdown table and asks the LLM to call the MCP Tool with the data.

{'format': '',
 'keep_alive': '5m',
 'messages': [{'content': 'Process the following table of data with the '
                          'preparation_tool.\n'
                          '<table>\n'
                          '<tr>\n'
                          '  <th>Sample Name</th> <th>Input Mass</th>\n'
                          '<tr>\n'
                          '<tr>\n'
                          '  <td> NA12878 </td> <td> 45 </td>\n'
                          '</tr\n'
                          '<tr>\n'
                          '  <td> NCC1701 </td> <td> 77 </td>\n'
                          '</tr\n'
                          '<tr>\n'
                          '  <td> NACL </td> <td> 12 </td>\n'
                          '</tr\n'
                          '<tr>\n'
                          '  <td> NA12879 </td> <td> 32 </td>\n'
                          '</tr\n'
                          '</table>\n'
                          '\n',
               'role': 'user'}],
 'model': 'llama3.2:latest',
 'options': {'stop': ['<|start_header_id|>',
                      '<|end_header_id|>',
                      '<|eot_id|>']},
 'stream': False,
 'tools': [{'function': {'description': 'Prepare the samples for analyze.  '
                                        "Mark samples as 'passed' or "
                                        "'failed'. ",
                         'name': 'preparation_tool',
                         'parameters': {
                             'properties': {
                                 'sample_list': {
                                     'items': {
                                         'properties': {
                                             'mass': {
                                                 'description': 'sample mass in ng',
                                                 'title': 'Mass',
                                                 'type': 'integer'
                                             },
                                             'sample_name': {
                                                 'description': 'sample name identifier',
                                                 'title': 'Sample Name',
                                                 'type': 'string'
                                             }
                                         },
                                         'required': ['sample_name', 'mass'],
                                         'title': 'InputSample', 'type': 'object'
                                     },
                                     'type': 'array'
                                 }
                             },
                             'required': ['sample_list'],
                             'type': 'object'}
                        },
            'type': 'function'}]}

FUTURE WORK

This PR simply annotates the type of the "items" field as "any" which allows the receipt of correctly-formated JSON, that is flattened and passed to the Model. A better implementation might include a more complete JSON Schema type structure.

It would be nice to be able to add a test that exercises this part of the code.

REFERENCES

https://modelcontextprotocol.io/
https://github.com/modelcontextprotocol/python-sdk/

OLLAMA DEBUG OUTPUT WITH THE FIX PRESENT

time=2025-04-02T06:48:09.741-07:00 level=DEBUG source=routes.go:1521 msg="chat request" images=0 prompt="<|start_header_id|>system<|end_header_id|>\n\nCutting Knowledge Date: December 2023\n\nWhen you receive a tool call response, use the output to format an answer to the orginal user question.\n\nYou are a helpful assistant with tool calling capabilities.<|eot_id|><|start_header_id|>user<|end_header_id|>\n\nGiven the following functions, please respond with a JSON for a function call with its proper arguments that best answers the given prompt.\n\nRespond in the format {\"name\": function name, \"parameters\": dictionary of argument name and its value}. Do not use variables.\n\n{\"type\":\"function\",\"function\":{\"name\":\"create_table\",\"description\":\"Create a new table in the SQLite database\",\"parameters\":{\"type\":\"object\",\"required\":[\"query\"],\"properties\":{\"query\":{\"type\":\"string\",\"description\":\"CREATE TABLE SQL statement\"}}}}}\n{\"type\":\"function\",\"function\":{\"name\":\"list_tables\",\"description\":\"List all tables in the SQLite database\",\"parameters\":{\"type\":\"object\",\"required\":null,\"properties\":{}}}}\n{\"type\":\"function\",\"function\":{\"name\":\"preparation_tool\",\"description\":\"Prepare the samples for analyze.  Mark samples as 'passed' or 'failed'. \",\"parameters\":{\"type\":\"object\",\"required\":[\"sample_list\"],\"properties\":{\"sample_list\":{\"type\":\"array\",\"items\":{\"properties\":{\"mass\":{\"description\":\"sample mass in ng\",\"title\":\"Mass\",\"type\":\"integer\"},\"sample_name\":{\"description\":\"sample name identifier\",\"title\":\"Sample Name\",\"type\":\"string\"}},\"required\":[\"sample_name\",\"mass\"],\"title\":\"InputSample\",\"type\":\"object\"},\"description\":\"\"}}}}}\n\nProcess the following table of data with the preparation_tool.\n<table>\n<tr>\n  <th>Sample Name</th> <th>Input Mass</th>\n<tr>\n<tr>\n  <td> NA12878 </td> <td> 45 </td>\n</tr\n<tr>\n  <td> NCC1701 </td> <td> 77 </td>\n</tr\n<tr>\n  <td> NACL </td> <td> 12 </td>\n</tr\n<tr>\n  <td> NA12879 </td> <td> 32 </td>\n</tr\n</table>\n<|eot_id|><|start_header_id|>assistant<|end_header_id|>\n\n"

OLLAMA DEBUG OUTPUT WITHOUT THE FIX

time=2025-04-02T06:51:06.962-07:00 level=DEBUG source=routes.go:1521 msg="chat request" images=0 prompt="<|start_header_id|>system<|end_header_id|>\n\nCutting Knowledge Date: December 2023\n\nWhen you receive a tool call response, use the output to format an answer to the orginal user question.\n\nYou are a helpful assistant with tool calling capabilities.<|eot_id|><|start_header_id|>user<|end_header_id|>\n\nGiven the following functions, please respond with a JSON for a function call with its proper arguments that best answers the given prompt.\n\nRespond in the format {\"name\": function name, \"parameters\": dictionary of argument name and its value}. Do not use variables.\n\n{\"type\":\"function\",\"function\":{\"name\":\"create_table\",\"description\":\"Create a new table in the SQLite database\",\"parameters\":{\"type\":\"object\",\"required\":[\"query\"],\"properties\":{\"query\":{\"type\":\"string\",\"description\":\"CREATE TABLE SQL statement\"}}}}}\n{\"type\":\"function\",\"function\":{\"name\":\"list_tables\",\"description\":\"List all tables in the SQLite database\",\"parameters\":{\"type\":\"object\",\"required\":null,\"properties\":{}}}}\n{\"type\":\"function\",\"function\":{\"name\":\"preparation_tool\",\"description\":\"Prepare the samples for analyze.  Mark samples as 'passed' or 'failed'. \",\"parameters\":{\"type\":\"object\",\"required\":[\"sample_list\"],\"properties\":{\"sample_list\":{\"type\":\"array\",\"description\":\"\"}}}}}\n\nProcess the following table of data with the preparation_tool.\n<table>\n<tr>\n  <th>Sample Name</th> <th>Input Mass</th>\n<tr>\n<tr>\n  <td> NA12878 </td> <td> 45 </td>\n</tr\n<tr>\n  <td> NCC1701 </td> <td> 77 </td>\n</tr\n<tr>\n  <td> NACL </td> <td> 12 </td>\n</tr\n<tr>\n  <td> NA12879 </td> <td> 32 </td>\n</tr\n</table>\n<|eot_id|><|start_header_id|>assistant<|end_header_id|>\n\n"

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/ollama/ollama/pull/10091 **Author:** [@sheffler](https://github.com/sheffler) **Created:** 4/2/2025 **Status:** ✅ Merged **Merged:** 4/10/2025 **Merged by:** [@ParthSareen](https://github.com/ParthSareen) **Base:** `main` ← **Head:** `itemsFieldForJsonSchemaTypes` --- ### 📝 Commits (6) - [`da30598`](https://github.com/ollama/ollama/commit/da305983b001dd08fa8df63a214058b7414b2076) support rich types: include the items field to properly handle "array" types - [`73f760c`](https://github.com/ollama/ollama/commit/73f760cf5f54a98fe1391bc54e97237e9ba80c23) include the $defs field as well - [`9479f0a`](https://github.com/ollama/ollama/commit/9479f0ac6f364a7b4c95dae74432c32b53a2e7c1) Merge branch 'main' into itemsFieldForJsonSchemaTypes - [`56e7739`](https://github.com/ollama/ollama/commit/56e7739bb9d443864d12ed66b6fad831084d1969) Update api/types.go - [`d8526d6`](https://github.com/ollama/ollama/commit/d8526d684747d5494a80d42b035f4921255ea0dc) update tests - [`e5335ef`](https://github.com/ollama/ollama/commit/e5335ef93606dd4d681cc9b1233ba87a9bfdd8d9) merge and fix tests ### 📊 Changes **3 files changed** (+16 additions, -0 deletions) <details> <summary>View changed files</summary> 📝 `api/types.go` (+4 -0) 📝 `openai/openai_test.go` (+4 -0) 📝 `server/routes_generate_test.go` (+8 -0) </details> ### 📄 Description This PR adds the capability for Ollama to handle tool argument types that are homegeneous arrays more correctly. ## BACKGROUND JSON-Schema allows the description of object with named properties. In addition, the type of each property can be indicated with a "type" annotation with value "string", "integer", "number", "array", etc. ``` json { "field1": { "type": "string" }, "field2": { "type": "number" } } ``` An "array" can hold objects of arbitrary type. Thus it is acceptable to have an array like ``` json [1, 2.0, "tom", false, 5] ``` If an array should be constrained to hold values that all obey the same schema, then an additional keyword, "items", can be included to indicate that. ``` json { "field3": { "type": "array", "items": "number" } } ``` The example above shows how a field would be constrained to only hold lists of numbers. Ollama does not recognize the "items" keyword in a JSON-Schema. ## EXPLANATION Ollama does not demarshall or store the contents of an "items" field, even when it is presented in the /api/chat request given by a client. Without including it, an LLM cannot know the structure of an array when it is trying to fullfill a request. (In my tests with Llama3.2 and an MCP tool that accepted an array of objects with fields, Llama3.2 would consistently provide an array of strings since it could not know any better. The tool call would simply fail.) With the modification to include the "items" field, Llama3.2 can fullfill a request that satisfies the type signature of a function that takes an array of objects. Interestingly, Ollama already includes all of the fields present in a Tool definition when it serializes it to a string, so by simply including the missing field it is passed to the LLM. EXAMPLE and SUCCESS The following is an example request generated by OTERM (https://github.com/ggozad/oterm) with an attached MCP Tool (Model Context Protocol Tool) called "preparation_tool". The preparation_tool operates on an array of structures, all of the same type. With the code modifications shown here, the "items" field is included with the text presented to Llama3.2 which can form a tool call that matches the type signature. The prompt loads a list of (sample, mass) pairs as a markdown table and asks the LLM to call the MCP Tool with the data. ``` {'format': '', 'keep_alive': '5m', 'messages': [{'content': 'Process the following table of data with the ' 'preparation_tool.\n' '<table>\n' '<tr>\n' ' <th>Sample Name</th> <th>Input Mass</th>\n' '<tr>\n' '<tr>\n' ' <td> NA12878 </td> <td> 45 </td>\n' '</tr\n' '<tr>\n' ' <td> NCC1701 </td> <td> 77 </td>\n' '</tr\n' '<tr>\n' ' <td> NACL </td> <td> 12 </td>\n' '</tr\n' '<tr>\n' ' <td> NA12879 </td> <td> 32 </td>\n' '</tr\n' '</table>\n' '\n', 'role': 'user'}], 'model': 'llama3.2:latest', 'options': {'stop': ['<|start_header_id|>', '<|end_header_id|>', '<|eot_id|>']}, 'stream': False, 'tools': [{'function': {'description': 'Prepare the samples for analyze. ' "Mark samples as 'passed' or " "'failed'. ", 'name': 'preparation_tool', 'parameters': { 'properties': { 'sample_list': { 'items': { 'properties': { 'mass': { 'description': 'sample mass in ng', 'title': 'Mass', 'type': 'integer' }, 'sample_name': { 'description': 'sample name identifier', 'title': 'Sample Name', 'type': 'string' } }, 'required': ['sample_name', 'mass'], 'title': 'InputSample', 'type': 'object' }, 'type': 'array' } }, 'required': ['sample_list'], 'type': 'object'} }, 'type': 'function'}]} ``` ## FUTURE WORK This PR simply annotates the type of the "items" field as "any" which allows the receipt of correctly-formated JSON, that is flattened and passed to the Model. A better implementation might include a more complete JSON Schema type structure. It would be nice to be able to add a test that exercises this part of the code. ## REFERENCES https://modelcontextprotocol.io/ https://github.com/modelcontextprotocol/python-sdk/ ## OLLAMA DEBUG OUTPUT WITH THE FIX PRESENT ``` time=2025-04-02T06:48:09.741-07:00 level=DEBUG source=routes.go:1521 msg="chat request" images=0 prompt="<|start_header_id|>system<|end_header_id|>\n\nCutting Knowledge Date: December 2023\n\nWhen you receive a tool call response, use the output to format an answer to the orginal user question.\n\nYou are a helpful assistant with tool calling capabilities.<|eot_id|><|start_header_id|>user<|end_header_id|>\n\nGiven the following functions, please respond with a JSON for a function call with its proper arguments that best answers the given prompt.\n\nRespond in the format {\"name\": function name, \"parameters\": dictionary of argument name and its value}. Do not use variables.\n\n{\"type\":\"function\",\"function\":{\"name\":\"create_table\",\"description\":\"Create a new table in the SQLite database\",\"parameters\":{\"type\":\"object\",\"required\":[\"query\"],\"properties\":{\"query\":{\"type\":\"string\",\"description\":\"CREATE TABLE SQL statement\"}}}}}\n{\"type\":\"function\",\"function\":{\"name\":\"list_tables\",\"description\":\"List all tables in the SQLite database\",\"parameters\":{\"type\":\"object\",\"required\":null,\"properties\":{}}}}\n{\"type\":\"function\",\"function\":{\"name\":\"preparation_tool\",\"description\":\"Prepare the samples for analyze. Mark samples as 'passed' or 'failed'. \",\"parameters\":{\"type\":\"object\",\"required\":[\"sample_list\"],\"properties\":{\"sample_list\":{\"type\":\"array\",\"items\":{\"properties\":{\"mass\":{\"description\":\"sample mass in ng\",\"title\":\"Mass\",\"type\":\"integer\"},\"sample_name\":{\"description\":\"sample name identifier\",\"title\":\"Sample Name\",\"type\":\"string\"}},\"required\":[\"sample_name\",\"mass\"],\"title\":\"InputSample\",\"type\":\"object\"},\"description\":\"\"}}}}}\n\nProcess the following table of data with the preparation_tool.\n<table>\n<tr>\n <th>Sample Name</th> <th>Input Mass</th>\n<tr>\n<tr>\n <td> NA12878 </td> <td> 45 </td>\n</tr\n<tr>\n <td> NCC1701 </td> <td> 77 </td>\n</tr\n<tr>\n <td> NACL </td> <td> 12 </td>\n</tr\n<tr>\n <td> NA12879 </td> <td> 32 </td>\n</tr\n</table>\n<|eot_id|><|start_header_id|>assistant<|end_header_id|>\n\n" ``` ## OLLAMA DEBUG OUTPUT WITHOUT THE FIX ``` time=2025-04-02T06:51:06.962-07:00 level=DEBUG source=routes.go:1521 msg="chat request" images=0 prompt="<|start_header_id|>system<|end_header_id|>\n\nCutting Knowledge Date: December 2023\n\nWhen you receive a tool call response, use the output to format an answer to the orginal user question.\n\nYou are a helpful assistant with tool calling capabilities.<|eot_id|><|start_header_id|>user<|end_header_id|>\n\nGiven the following functions, please respond with a JSON for a function call with its proper arguments that best answers the given prompt.\n\nRespond in the format {\"name\": function name, \"parameters\": dictionary of argument name and its value}. Do not use variables.\n\n{\"type\":\"function\",\"function\":{\"name\":\"create_table\",\"description\":\"Create a new table in the SQLite database\",\"parameters\":{\"type\":\"object\",\"required\":[\"query\"],\"properties\":{\"query\":{\"type\":\"string\",\"description\":\"CREATE TABLE SQL statement\"}}}}}\n{\"type\":\"function\",\"function\":{\"name\":\"list_tables\",\"description\":\"List all tables in the SQLite database\",\"parameters\":{\"type\":\"object\",\"required\":null,\"properties\":{}}}}\n{\"type\":\"function\",\"function\":{\"name\":\"preparation_tool\",\"description\":\"Prepare the samples for analyze. Mark samples as 'passed' or 'failed'. \",\"parameters\":{\"type\":\"object\",\"required\":[\"sample_list\"],\"properties\":{\"sample_list\":{\"type\":\"array\",\"description\":\"\"}}}}}\n\nProcess the following table of data with the preparation_tool.\n<table>\n<tr>\n <th>Sample Name</th> <th>Input Mass</th>\n<tr>\n<tr>\n <td> NA12878 </td> <td> 45 </td>\n</tr\n<tr>\n <td> NCC1701 </td> <td> 77 </td>\n</tr\n<tr>\n <td> NACL </td> <td> 12 </td>\n</tr\n<tr>\n <td> NA12879 </td> <td> 32 </td>\n</tr\n</table>\n<|eot_id|><|start_header_id|>assistant<|end_header_id|>\n\n" ``` --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
GiteaMirror added the pull-request label 2026-04-22 23:39:39 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/ollama#39017