feat: add System One API support (#749)

This commit is contained in:
Parth Sareen
2026-09-28 17:57:35 -07:00
committed by GitHub
parent b28b1e8a8c
commit ed18cea18d
7 changed files with 291 additions and 0 deletions
+41
View File
@@ -255,6 +255,47 @@ ollama.embed(model='gemma4', input=['The sky is blue because of rayleigh scatter
ollama.ps()
```
### System One
```python
import ollama
response = ollama.systemone(
model='nimble',
state='Our checkout has returned 500 errors since 9am.',
questions={
'team': {
'type': 'choice',
'instructions': 'Which team should handle this ticket?',
'criteria': {'billing': 'Payments and refunds', 'technical': 'Software errors'},
},
},
)
print(response.answers['team'])
```
System One uses `POST /v1/systemone` and requires Ollama v0.35.0 or later with a
compatible local model such as `nimble`. It returns one JSON response; streaming
and cloud models are not supported.
`state` and question `instructions` accept text, JSON objects, or arrays. Questions
are evaluated in their supplied order:
- `choice`: 2–26 option keys mapped to descriptions; `None` uses the key as its description.
- `noul`: probability of true, with optional `{"false": "No", "true": "Yes"}` descriptions.
- `score`: 2–26 descriptions ordered lowest to highest; returns a potentially fractional, zero-based score.
Responses contain `model`, `answers`, and `usage.input_tokens` / `usage.output_tokens`.
Confidence measures probability concentration, not calibrated correctness.
Token usage comes from the server; output tokens are not necessarily zero.
Optional `keep_alive` accepts seconds or a duration string. Requests use the client's
existing host, headers, and HTTP error handling. The server validates its body and
model context limits without truncating input.
Available as `ollama.systemone`, `Client.systemone`, and `await AsyncClient.systemone`.
Question and answer types, including `SystemOneResponse`, are exported from `ollama`.
See [the combined question example](examples/systemone.py).
## Errors
Errors are raised if requests return an error status or if an error is detected while streaming.
+4
View File
@@ -127,3 +127,7 @@ Requirement: `pip install tqdm`
### Thinking (levels) - Choose the thinking level
- [thinking-levels.py](thinking-levels.py)
### System One - Score typed questions
- [systemone.py](systemone.py) - Choice, boolean probability, and rubric score questions
+13
View File
@@ -0,0 +1,13 @@
import ollama
# Requires Ollama v0.35.0 or later and the local nimble model.
response = ollama.systemone(
model='nimble',
state={'ticket': 'I was charged twice. Please refund the extra payment.'},
questions={
'team': {'type': 'choice', 'instructions': 'Which team should handle this ticket?', 'criteria': {'billing': 'Payments and refunds', 'technical': 'Bugs and integrations', 'other': 'None of the above'}},
'refund': {'type': 'noul', 'instructions': 'Does the customer explicitly ask for a refund?'},
'urgency': {'type': 'score', 'instructions': 'How urgent is this ticket?', 'criteria': ['Routine', 'Soon', 'Urgent']},
},
)
print(response.model_dump())
+25
View File
@@ -14,6 +14,18 @@ from ollama._types import (
ResponseError,
ShowResponse,
StatusResponse,
SystemOneAnswer,
SystemOneChoiceAnswer,
SystemOneChoiceQuestion,
SystemOneContent,
SystemOneNoulAnswer,
SystemOneNoulQuestion,
SystemOneQuestion,
SystemOneRequest,
SystemOneResponse,
SystemOneScoreAnswer,
SystemOneScoreQuestion,
SystemOneUsage,
Tool,
WebFetchResponse,
WebSearchResponse,
@@ -36,6 +48,18 @@ __all__ = [
'ResponseError',
'ShowResponse',
'StatusResponse',
'SystemOneAnswer',
'SystemOneChoiceAnswer',
'SystemOneChoiceQuestion',
'SystemOneContent',
'SystemOneNoulAnswer',
'SystemOneNoulQuestion',
'SystemOneQuestion',
'SystemOneRequest',
'SystemOneResponse',
'SystemOneScoreAnswer',
'SystemOneScoreQuestion',
'SystemOneUsage',
'Tool',
'WebFetchResponse',
'WebSearchResponse',
@@ -57,3 +81,4 @@ show = _client.show
ps = _client.ps
web_search = _client.web_search
web_fetch = _client.web_fetch
systemone = _client.systemone
+50
View File
@@ -66,6 +66,10 @@ from ollama._types import (
ShowRequest,
ShowResponse,
StatusResponse,
SystemOneContent,
SystemOneQuestion,
SystemOneRequest,
SystemOneResponse,
Tool,
WebFetchRequest,
WebFetchResponse,
@@ -403,6 +407,29 @@ class Client(BaseClient):
stream=stream,
)
def systemone(
self,
model: str,
state: SystemOneContent,
questions: Mapping[str, Union[SystemOneQuestion, Mapping[str, Any]]],
keep_alive: Optional[Union[float, str]] = None,
) -> SystemOneResponse:
"""Score choice, noul, and score questions with a local System One model.
Returns one JSON response. Streaming is not supported.
"""
return self._request(
SystemOneResponse,
'POST',
'/v1/systemone',
json=SystemOneRequest(
model=model,
state=state,
questions=questions,
keep_alive=keep_alive,
).model_dump(exclude_none=True),
)
def embed(
self,
model: str = '',
@@ -1036,6 +1063,29 @@ class AsyncClient(BaseClient):
stream=stream,
)
async def systemone(
self,
model: str,
state: SystemOneContent,
questions: Mapping[str, Union[SystemOneQuestion, Mapping[str, Any]]],
keep_alive: Optional[Union[float, str]] = None,
) -> SystemOneResponse:
"""Score choice, noul, and score questions with a local System One model.
Returns one JSON response. Streaming is not supported.
"""
return await self._request(
SystemOneResponse,
'POST',
'/v1/systemone',
json=SystemOneRequest(
model=model,
state=state,
questions=questions,
keep_alive=keep_alive,
).model_dump(exclude_none=True),
)
async def embed(
self,
model: str = '',
+70
View File
@@ -618,6 +618,76 @@ class WebFetchResponse(SubscriptableBaseModel):
links: Optional[Sequence[str]] = None
SystemOneContent = Union[str, Dict[str, Any], List[Any]]
"""Text or JSON object/array rendered as text by System One."""
class SystemOneChoiceQuestion(SubscriptableBaseModel):
type: Literal['choice'] = 'choice'
instructions: SystemOneContent
criteria: Dict[str, Optional[str]]
'Ordered choices; a null description uses the choice key.'
class SystemOneNoulQuestion(SubscriptableBaseModel):
type: Literal['noul'] = 'noul'
instructions: SystemOneContent
criteria: Optional[Dict[Literal['false', 'true'], str]] = None
class SystemOneScoreQuestion(SubscriptableBaseModel):
type: Literal['score'] = 'score'
instructions: SystemOneContent
criteria: List[str]
'Descriptions ordered from lowest to highest score.'
SystemOneQuestion = Annotated[Union[SystemOneChoiceQuestion, SystemOneNoulQuestion, SystemOneScoreQuestion], Field(discriminator='type')]
class SystemOneRequest(SubscriptableBaseModel):
model: str
state: SystemOneContent
questions: Dict[str, SystemOneQuestion]
keep_alive: Optional[Union[float, str]] = None
class SystemOneNoulAnswer(SubscriptableBaseModel):
type: Literal['noul']
noul: float
'Probability of true.'
class SystemOneChoiceAnswer(SubscriptableBaseModel):
type: Literal['choice']
choice: str
probabilities: Dict[str, float]
confidence: float
class SystemOneScoreAnswer(SubscriptableBaseModel):
type: Literal['score']
score: float
'Expected zero-based score, which may be fractional.'
legend: Dict[str, str]
probabilities: Dict[str, float]
confidence: float
SystemOneAnswer = Annotated[Union[SystemOneNoulAnswer, SystemOneChoiceAnswer, SystemOneScoreAnswer], Field(discriminator='type')]
class SystemOneUsage(SubscriptableBaseModel):
input_tokens: int
output_tokens: int
class SystemOneResponse(SubscriptableBaseModel):
model: str
answers: Dict[str, SystemOneAnswer]
usage: SystemOneUsage
class RequestError(Exception):
"""
Common class for request errors.
+88
View File
@@ -0,0 +1,88 @@
import inspect
import json
import httpx
import pytest
import ollama
@pytest.fixture
def anyio_backend():
return 'asyncio'
@pytest.mark.anyio
@pytest.mark.parametrize('variant', ['sync', 'async', 'module'])
async def test_systemone_wire_and_answers(variant, monkeypatch):
questions = {
'team': {'type': 'choice', 'instructions': {'task': 'Route ticket'}, 'criteria': {'technical': None, 'billing': 'Payments'}},
'refund': ollama.SystemOneNoulQuestion(instructions=['Refund requested?']),
'urgency': {'type': 'score', 'instructions': 'Urgency?', 'criteria': ['Routine', 'Urgent']},
}
expected = {
'model': 'nimble',
'state': {'ticket': ['Duplicate charge', None], 'paid': True},
'questions': {
'team': {'type': 'choice', 'instructions': {'task': 'Route ticket'}, 'criteria': {'technical': None, 'billing': 'Payments'}},
'refund': {'type': 'noul', 'instructions': ['Refund requested?']},
'urgency': {'type': 'score', 'instructions': 'Urgency?', 'criteria': ['Routine', 'Urgent']},
},
}
# Exercise both omission and numeric zero without duplicating the client matrix.
if variant == 'sync':
expected['keep_alive'] = 0
elif variant == 'async':
expected['keep_alive'] = '5m'
result = {
'model': 'nimble',
'answers': {
'team': {'type': 'choice', 'choice': 'billing', 'probabilities': {'technical': 0.2, 'billing': 0.8}, 'confidence': 0.3},
'refund': {'type': 'noul', 'noul': 0.9},
'urgency': {'type': 'score', 'score': 0.25, 'legend': {'0': 'Routine', '1': 'Urgent'}, 'probabilities': {'0': 0.75, '1': 0.25}, 'confidence': 0.2},
},
'usage': {'input_tokens': 123, 'output_tokens': 4},
}
def handle(request):
assert request.method == 'POST'
assert str(request.url) == 'http://sdk.test:11474/v1/systemone'
assert request.headers['authorization'] == 'Bearer synthetic'
body = json.loads(request.content)
assert body == expected
assert list(body['questions']) == ['team', 'refund', 'urgency']
assert list(body['questions']['team']['criteria']) == ['technical', 'billing']
return httpx.Response(200, json=result)
transport = httpx.MockTransport(handle)
if variant == 'module':
monkeypatch.setattr(ollama._client, '_client', httpx.Client(base_url='http://sdk.test:11474', headers={'authorization': 'Bearer synthetic'}, transport=transport))
method = ollama.systemone
else:
client = (ollama.AsyncClient if variant == 'async' else ollama.Client)(host='http://sdk.test:11474', headers={'authorization': 'Bearer synthetic'}, transport=transport)
method = client.systemone
kwargs = {key: expected[key] for key in ('model', 'state', 'keep_alive') if key in expected}
response = method(**kwargs, questions=questions)
if inspect.isawaitable(response):
response = await response
assert response.model_dump() == result
assert isinstance(response.answers['team'], ollama.SystemOneChoiceAnswer)
assert isinstance(response.answers['refund'], ollama.SystemOneNoulAnswer)
assert isinstance(response.answers['urgency'], ollama.SystemOneScoreAnswer)
assert response['answers']['urgency']['score'] == 0.25
@pytest.mark.anyio
@pytest.mark.parametrize('client_type', [ollama.Client, ollama.AsyncClient])
@pytest.mark.parametrize('status,message', [(400, 'state must not be empty'), (404, "model 'missing' not found"), (413, 'request body must not exceed 64 KiB'), (503, 'server busy')])
async def test_systemone_http_errors(client_type, status, message):
def handle(request):
return httpx.Response(status, json={'error': message})
client = client_type(transport=httpx.MockTransport(handle))
with pytest.raises(ollama.ResponseError) as error:
response = client.systemone('nimble', 'ticket', {'refund': {'type': 'noul', 'instructions': 'Refund?'}})
if inspect.isawaitable(response):
await response
assert error.value.status_code == status
assert error.value.error == message