mirror of
https://github.com/ollama/ollama-python.git
synced 2026-06-12 03:04:44 +00:00
Compare commits
245 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| db7e2a980e | |||
| a1d04f04f2 | |||
| 8af6cac86b | |||
| 9f41447f20 | |||
| da79e987f0 | |||
| c8392d6524 | |||
| 07ab287cdf | |||
| b0f6b99ca6 | |||
| c87604c66f | |||
| 53ff3cd025 | |||
| aa4b476f26 | |||
| 34e98bd237 | |||
| dad9e1ca3a | |||
| fe91357d4b | |||
| d7978cb234 | |||
| b23d79d8b5 | |||
| 33488eee06 | |||
| 63ca747622 | |||
| 4c11d507b0 | |||
| ce6846e4fc | |||
| e0253ab627 | |||
| 756bd8f31a | |||
| f8c6cd5131 | |||
| eaad6df5ef | |||
| 5d7c63fae1 | |||
| 5ae5f816bb | |||
| 65f94b4fba | |||
| e33ceb7188 | |||
| 8ac9f4da76 | |||
| 1b3406887d | |||
| 3eaa83781d | |||
| 07eec6d517 | |||
| 6b235b2d60 | |||
| aec125c773 | |||
| 8d0d0e483d | |||
| 0561f42701 | |||
| 7e64093df0 | |||
| f7e23ddb93 | |||
| 967fd657f1 | |||
| 12d6842f32 | |||
| 7a6ab044c6 | |||
| eefe5c9666 | |||
| 89e719ab92 | |||
| 02495ffd77 | |||
| 2cad1f5428 | |||
| d9746ead51 | |||
| c7d4f1674a | |||
| ba27e71a20 | |||
| fbf94d8660 | |||
| 2434443b88 | |||
| 2be0bcf92a | |||
| fc8585eabd | |||
| f5c8ee0a3e | |||
| a0388b2e32 | |||
| f718dab45d | |||
| 4dec73e8be | |||
| 4f9fb88137 | |||
| bee11029f7 | |||
| 427b0c6291 | |||
| ee349ecc6d | |||
| 7d1e002be9 | |||
| 4daf4afdb6 | |||
| 9057705bc0 | |||
| 70dd0b7e63 | |||
| 1066246ab5 | |||
| 4b10dee2b2 | |||
| e956a331e8 | |||
| 12f7302d5f | |||
| 366180aa8f | |||
| d6528cf731 | |||
| b50a65b27d | |||
| 758a1d2933 | |||
| d4c38978d1 | |||
| d8d98e17b2 | |||
| ec2c8fdd8d | |||
| ea0e0dc692 | |||
| 6c44bb2729 | |||
| 2095fc9107 | |||
| 64e3723e6b | |||
| 1e22f2e118 | |||
| 00c64332cc | |||
| 986fb4c7b3 | |||
| c6ade633b8 | |||
| 2e05cde1a5 | |||
| bfea240300 | |||
| 64c1eb78ff | |||
| 139c89e833 | |||
| da2893b099 | |||
| da5582ff14 | |||
| 5ec4d23893 | |||
| d0d7894f88 | |||
| 1fea8a8867 | |||
| 52510cca53 | |||
| 425bce2329 | |||
| dc38fe4675 | |||
| a92f111fc8 | |||
| 72052188c3 | |||
| f25834217b | |||
| b0da4ff2d8 | |||
| 0bbc246007 | |||
| a7571423d3 | |||
| ebe332b29d | |||
| 9bcd0d6b33 | |||
| f3e72b6c4f | |||
| 61c8d0d440 | |||
| 7fda5c9dae | |||
| 1ec88ed994 | |||
| a26537c188 | |||
| eff2afd827 | |||
| ccf8af3aaa | |||
| 89e8b74f1e | |||
| 81edab1b64 | |||
| 5f51129d25 | |||
| d98f646929 | |||
| 981015cfb8 | |||
| 9c34d810ab | |||
| 9f2832d2ed | |||
| e220e46320 | |||
| dfdeb7cef3 | |||
| 9e6726e28a | |||
| 10d0ff2b3a | |||
| 8b694bb0f4 | |||
| cbf0887573 | |||
| cd4fbfc2bc | |||
| d2da64f253 | |||
| ae2bdbf3cd | |||
| 60d292a98d | |||
| ab7e600b95 | |||
| 8e3a8febba | |||
| 6025c360da | |||
| f62eb97a4a | |||
| b498ab3451 | |||
| a49c9860b1 | |||
| 33c4b61ff9 | |||
| b0ea6d9e44 | |||
| 2cb796cbf5 | |||
| 359c63daa7 | |||
| 222c2079c2 | |||
| 49568d5e85 | |||
| 1a15742705 | |||
| ce56f279e8 | |||
| 982d65fea0 | |||
| d25c4aa1cf | |||
| fa7bf7cbd1 | |||
| 04d102b406 | |||
| 57c597b60e | |||
| 74db547ca4 | |||
| d5316d023a | |||
| 14d7f8c1b7 | |||
| b45848a9cf | |||
| 4fb909db7b | |||
| 0824044330 | |||
| 0f8c20a596 | |||
| cb81f522b0 | |||
| 5dc857ea66 | |||
| 309007da80 | |||
| 3b69774f22 | |||
| eaab4778c7 | |||
| 96214c0ffe | |||
| 00eafed0fa | |||
| a8cb34e0ec | |||
| 2b66adebef | |||
| cc1fca067b | |||
| 8b91188f66 | |||
| 85f526353a | |||
| 5cf83dcda8 | |||
| 2ac751fb79 | |||
| c4931b202d | |||
| 30f762ae77 | |||
| e5c4799650 | |||
| fed2b2ee02 | |||
| e403d748a3 | |||
| 30090c5508 | |||
| e1b0600088 | |||
| 029b080c4c | |||
| 5b94a8d872 | |||
| d38fe129c3 | |||
| 426b30e9ed | |||
| dc9543ba5f | |||
| 42f9f70bae | |||
| 2fde317490 | |||
| 7201dcbbd3 | |||
| dfa5b12061 | |||
| f5a8ec3272 | |||
| 3c9ba8cf2e | |||
| 768c559094 | |||
| 44126e5a37 | |||
| 841595aed1 | |||
| 281e149001 | |||
| 9c4a87c90b | |||
| adcae4ce25 | |||
| 6b351e54cf | |||
| 4896b53755 | |||
| e28f43f892 | |||
| fcdf5771f5 | |||
| ec8bf88c2b | |||
| 8b929ab496 | |||
| eee32dda37 | |||
| c74dd5835d | |||
| cdec2ad99e | |||
| 70e376368f | |||
| 4a81fa43ee | |||
| 98ad0d884e | |||
| 09ae36a0f9 | |||
| c27eebc158 | |||
| 46291d49a7 | |||
| cf3ab807c8 | |||
| 8e5d431d0d | |||
| e201181d4c | |||
| c077b5d685 | |||
| fbb6553e03 | |||
| f618a2f448 | |||
| 354f012168 | |||
| 19e7a3d9ce | |||
| 9bfa1d1ecf | |||
| 2e0b2e15f0 | |||
| 5c1df78b7d | |||
| c8f7d9b7d2 | |||
| 21aad8447c | |||
| 38f68e251d | |||
| fa6d8d5a9f | |||
| 373a2e3759 | |||
| 27f0dfe0ca | |||
| 467fd833f3 | |||
| 9c60d1569c | |||
| 43d36489b2 | |||
| 3119736eda | |||
| bae245ea63 | |||
| bf46f31936 | |||
| 81e253997d | |||
| 89c4b54316 | |||
| 8497ffa3d4 | |||
| af02cb3fba | |||
| a01b48814a | |||
| f00c93eab3 | |||
| 516fb19076 | |||
| 1a60f07e28 | |||
| 27e2ef1161 | |||
| 53b10f7c00 | |||
| 582839f24f | |||
| fc60bd74e9 | |||
| e3733a235d | |||
| b232a6c04d | |||
| e0ee198195 | |||
| 99df3c3a07 |
@@ -0,0 +1,10 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: github-actions
|
||||
directory: /
|
||||
schedule:
|
||||
interval: daily
|
||||
- package-ecosystem: pip
|
||||
directory: /
|
||||
schedule:
|
||||
interval: daily
|
||||
@@ -11,17 +11,15 @@ jobs:
|
||||
environment: release
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- run: pipx install poetry
|
||||
- uses: actions/setup-python@v5
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-python@v6
|
||||
- uses: astral-sh/setup-uv@v5
|
||||
with:
|
||||
python-version: '3.x'
|
||||
cache: poetry
|
||||
- run: |
|
||||
poetry version ${GITHUB_REF_NAME#v}
|
||||
poetry build
|
||||
enable-cache: true
|
||||
- run: uv build
|
||||
- uses: pypa/gh-action-pypi-publish@release/v1
|
||||
- run: gh release upload $GIT_REF_NAME dist/*
|
||||
- run: gh release upload $GITHUB_REF_NAME dist/*
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
|
||||
+23
-19
@@ -1,32 +1,36 @@
|
||||
name: test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- run: pipx install poetry
|
||||
- uses: actions/setup-python@v5
|
||||
- uses: actions/checkout@v5
|
||||
- uses: astral-sh/setup-uv@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
cache: poetry
|
||||
- run: poetry install --with=dev
|
||||
- run: poetry run ruff --output-format=github .
|
||||
- run: poetry run pytest . --junitxml=junit/test-results-${{ matrix.python-version }}.xml --cov=ollama --cov-report=xml --cov-report=html
|
||||
- name: check poetry.lock is up-to-date
|
||||
run: poetry check --lock
|
||||
enable-cache: true
|
||||
- run: uvx hatch test -acp
|
||||
if: ${{ always() }}
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-python@v6
|
||||
- uses: astral-sh/setup-uv@v5
|
||||
with:
|
||||
enable-cache: true
|
||||
- name: check formatting
|
||||
run: uvx hatch fmt --check -f
|
||||
- name: check linting
|
||||
run: uvx hatch fmt --check -l --output-format=github
|
||||
- name: check uv.lock is up-to-date
|
||||
run: uv lock --check
|
||||
- name: check requirements.txt is up-to-date
|
||||
run: |
|
||||
poetry export >requirements.txt
|
||||
uv export >requirements.txt
|
||||
git diff --exit-code requirements.txt
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: pytest-results-${{ matrix.python-version }}
|
||||
path: junit/test-results-${{ matrix.python-version }}.xml
|
||||
if: ${{ always() }}
|
||||
|
||||
@@ -1,48 +1,77 @@
|
||||
# Ollama Python Library
|
||||
|
||||
The Ollama Python library provides the easiest way to integrate your Python 3 project with [Ollama](https://github.com/jmorganca/ollama).
|
||||
The Ollama Python library provides the easiest way to integrate Python 3.8+ projects with [Ollama](https://github.com/ollama/ollama).
|
||||
|
||||
## Getting Started
|
||||
## Prerequisites
|
||||
|
||||
Requires Python 3.8 or higher.
|
||||
- [Ollama](https://ollama.com/download) should be installed and running
|
||||
- Pull a model to use with the library: `ollama pull <model>` e.g. `ollama pull gemma3`
|
||||
- See [Ollama.com](https://ollama.com/search) for more information on the models available.
|
||||
|
||||
## Install
|
||||
|
||||
```sh
|
||||
pip install ollama
|
||||
```
|
||||
|
||||
A global default client is provided for convenience and can be used in the same way as the synchronous client.
|
||||
## Usage
|
||||
|
||||
```python
|
||||
import ollama
|
||||
response = ollama.chat(model='llama2', messages=[{'role': 'user', 'content': 'Why is the sky blue?'}])
|
||||
from ollama import chat
|
||||
from ollama import ChatResponse
|
||||
|
||||
response: ChatResponse = chat(model='gemma3', messages=[
|
||||
{
|
||||
'role': 'user',
|
||||
'content': 'Why is the sky blue?',
|
||||
},
|
||||
])
|
||||
print(response['message']['content'])
|
||||
# or access fields directly from the response object
|
||||
print(response.message.content)
|
||||
```
|
||||
|
||||
See [_types.py](ollama/_types.py) for more information on the response types.
|
||||
|
||||
## Streaming responses
|
||||
|
||||
Response streaming can be enabled by setting `stream=True`.
|
||||
|
||||
```python
|
||||
import ollama
|
||||
message = {'role': 'user', 'content': 'Why is the sky blue?'}
|
||||
for part in ollama.chat(model='llama2', messages=[message], stream=True):
|
||||
print(part['message']['content'], end='', flush=True)
|
||||
from ollama import chat
|
||||
|
||||
stream = chat(
|
||||
model='gemma3',
|
||||
messages=[{'role': 'user', 'content': 'Why is the sky blue?'}],
|
||||
stream=True,
|
||||
)
|
||||
|
||||
for chunk in stream:
|
||||
print(chunk['message']['content'], end='', flush=True)
|
||||
```
|
||||
|
||||
## Custom client
|
||||
A custom client can be created by instantiating `Client` or `AsyncClient` from `ollama`.
|
||||
|
||||
## Using the Synchronous Client
|
||||
All extra keyword arguments are passed into the [`httpx.Client`](https://www.python-httpx.org/api/#client).
|
||||
|
||||
```python
|
||||
from ollama import Client
|
||||
message = {'role': 'user', 'content': 'Why is the sky blue?'}
|
||||
response = Client().chat(model='llama2', messages=[message])
|
||||
client = Client(
|
||||
host='http://localhost:11434',
|
||||
headers={'x-some-header': 'some-value'}
|
||||
)
|
||||
response = client.chat(model='gemma3', messages=[
|
||||
{
|
||||
'role': 'user',
|
||||
'content': 'Why is the sky blue?',
|
||||
},
|
||||
])
|
||||
```
|
||||
|
||||
Response streaming can be enabled by setting `stream=True`. This modifies the function to return a Python generator where each part is an object in the stream.
|
||||
## Async client
|
||||
|
||||
```python
|
||||
from ollama import Client
|
||||
message = {'role': 'user', 'content': 'Why is the sky blue?'}
|
||||
for part in Client().chat(model='llama2', messages=[message], stream=True):
|
||||
print(part['message']['content'], end='', flush=True)
|
||||
```
|
||||
|
||||
## Using the Asynchronous Client
|
||||
The `AsyncClient` class is used to make asynchronous requests. It can be configured with the same fields as the `Client` class.
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
@@ -50,12 +79,12 @@ from ollama import AsyncClient
|
||||
|
||||
async def chat():
|
||||
message = {'role': 'user', 'content': 'Why is the sky blue?'}
|
||||
response = await AsyncClient().chat(model='llama2', messages=[message])
|
||||
response = await AsyncClient().chat(model='gemma3', messages=[message])
|
||||
|
||||
asyncio.run(chat())
|
||||
```
|
||||
|
||||
Similar to the synchronous client, setting `stream=True` modifies the function to return a Python asynchronous generator.
|
||||
Setting `stream=True` modifies functions to return a Python asynchronous generator:
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
@@ -63,13 +92,90 @@ from ollama import AsyncClient
|
||||
|
||||
async def chat():
|
||||
message = {'role': 'user', 'content': 'Why is the sky blue?'}
|
||||
async for part in await AsyncClient().chat(model='llama2', messages=[message], stream=True):
|
||||
async for part in await AsyncClient().chat(model='gemma3', messages=[message], stream=True):
|
||||
print(part['message']['content'], end='', flush=True)
|
||||
|
||||
asyncio.run(chat())
|
||||
```
|
||||
|
||||
## Handling Errors
|
||||
## API
|
||||
|
||||
The Ollama Python library's API is designed around the [Ollama REST API](https://github.com/ollama/ollama/blob/main/docs/api.md)
|
||||
|
||||
### Chat
|
||||
|
||||
```python
|
||||
ollama.chat(model='gemma3', messages=[{'role': 'user', 'content': 'Why is the sky blue?'}])
|
||||
```
|
||||
|
||||
### Generate
|
||||
|
||||
```python
|
||||
ollama.generate(model='gemma3', prompt='Why is the sky blue?')
|
||||
```
|
||||
|
||||
### List
|
||||
|
||||
```python
|
||||
ollama.list()
|
||||
```
|
||||
|
||||
### Show
|
||||
|
||||
```python
|
||||
ollama.show('gemma3')
|
||||
```
|
||||
|
||||
### Create
|
||||
|
||||
```python
|
||||
ollama.create(model='example', from_='gemma3', system="You are Mario from Super Mario Bros.")
|
||||
```
|
||||
|
||||
### Copy
|
||||
|
||||
```python
|
||||
ollama.copy('gemma3', 'user/gemma3')
|
||||
```
|
||||
|
||||
### Delete
|
||||
|
||||
```python
|
||||
ollama.delete('gemma3')
|
||||
```
|
||||
|
||||
### Pull
|
||||
|
||||
```python
|
||||
ollama.pull('gemma3')
|
||||
```
|
||||
|
||||
### Push
|
||||
|
||||
```python
|
||||
ollama.push('user/gemma3')
|
||||
```
|
||||
|
||||
### Embed
|
||||
|
||||
```python
|
||||
ollama.embed(model='gemma3', input='The sky is blue because of rayleigh scattering')
|
||||
```
|
||||
|
||||
### Embed (batch)
|
||||
|
||||
```python
|
||||
ollama.embed(model='gemma3', input=['The sky is blue because of rayleigh scattering', 'Grass is green because of chlorophyll'])
|
||||
```
|
||||
|
||||
### Ps
|
||||
|
||||
```python
|
||||
ollama.ps()
|
||||
```
|
||||
|
||||
|
||||
## Errors
|
||||
|
||||
Errors are raised if requests return an error status or if an error is detected while streaming.
|
||||
|
||||
@@ -79,7 +185,7 @@ model = 'does-not-yet-exist'
|
||||
try:
|
||||
ollama.chat(model)
|
||||
except ollama.ResponseError as e:
|
||||
print('Error:', e.content)
|
||||
print('Error:', e.error)
|
||||
if e.status_code == 404:
|
||||
ollama.pull(model)
|
||||
```
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
# Security
|
||||
|
||||
The Ollama maintainer team takes security seriously and will actively work to resolve security issues.
|
||||
|
||||
## Reporting a vulnerability
|
||||
|
||||
If you discover a security vulnerability, please do not open a public issue. Instead, please report it by emailing hello@ollama.com. We ask that you give us sufficient time to investigate and address the vulnerability before disclosing it publicly.
|
||||
|
||||
Please include the following details in your report:
|
||||
- A description of the vulnerability
|
||||
- Steps to reproduce the issue
|
||||
- Your assessment of the potential impact
|
||||
- Any possible mitigations
|
||||
|
||||
## Security best practices
|
||||
|
||||
While the maintainer team does their best to secure Ollama, users are encouraged to implement their own security best practices, such as:
|
||||
|
||||
- Regularly updating to the latest version of Ollama
|
||||
- Securing access to hosted instances of Ollama
|
||||
- Monitoring systems for unusual activity
|
||||
|
||||
## Contact
|
||||
|
||||
For any other questions or concerns related to security, please contact us at hello@ollama.com
|
||||
@@ -0,0 +1,80 @@
|
||||
# Running Examples
|
||||
|
||||
Run the examples in this directory with:
|
||||
```sh
|
||||
# Run example
|
||||
python3 examples/<example>.py
|
||||
```
|
||||
|
||||
See [ollama/docs/api.md](https://github.com/ollama/ollama/blob/main/docs/api.md) for full API documentation
|
||||
|
||||
### Chat - Chat with a model
|
||||
- [chat.py](chat.py)
|
||||
- [async-chat.py](async-chat.py)
|
||||
- [chat-stream.py](chat-stream.py) - Streamed outputs
|
||||
- [chat-with-history.py](chat-with-history.py) - Chat with model and maintain history of the conversation
|
||||
|
||||
|
||||
### Generate - Generate text with a model
|
||||
- [generate.py](generate.py)
|
||||
- [async-generate.py](async-generate.py)
|
||||
- [generate-stream.py](generate-stream.py) - Streamed outputs
|
||||
- [fill-in-middle.py](fill-in-middle.py) - Given a prefix and suffix, fill in the middle
|
||||
|
||||
|
||||
### Tools/Function Calling - Call a function with a model
|
||||
- [tools.py](tools.py) - Simple example of Tools/Function Calling
|
||||
- [async-tools.py](async-tools.py)
|
||||
- [multi-tool.py](multi-tool.py) - Using multiple tools, with thinking enabled
|
||||
|
||||
#### gpt-oss
|
||||
- [gpt-oss-tools.py](gpt-oss-tools.py)
|
||||
- [gpt-oss-tools-stream.py](gpt-oss-tools-stream.py)
|
||||
- [gpt-oss-tools-browser.py](gpt-oss-tools-browser.py) - Using browser research tools with gpt-oss
|
||||
- [gpt-oss-tools-browser-stream.py](gpt-oss-tools-browser-stream.py) - Using browser research tools with gpt-oss, with streaming enabled
|
||||
|
||||
|
||||
### Multimodal with Images - Chat with a multimodal (image chat) model
|
||||
- [multimodal-chat.py](multimodal-chat.py)
|
||||
- [multimodal-generate.py](multimodal-generate.py)
|
||||
|
||||
|
||||
### Structured Outputs - Generate structured outputs with a model
|
||||
- [structured-outputs.py](structured-outputs.py)
|
||||
- [async-structured-outputs.py](async-structured-outputs.py)
|
||||
- [structured-outputs-image.py](structured-outputs-image.py)
|
||||
|
||||
|
||||
### Ollama List - List all downloaded models and their properties
|
||||
- [list.py](list.py)
|
||||
|
||||
|
||||
### Ollama Show - Display model properties and capabilities
|
||||
- [show.py](show.py)
|
||||
|
||||
|
||||
### Ollama ps - Show model status with CPU/GPU usage
|
||||
- [ps.py](ps.py)
|
||||
|
||||
|
||||
### Ollama Pull - Pull a model from Ollama
|
||||
Requirement: `pip install tqdm`
|
||||
- [pull.py](pull.py)
|
||||
|
||||
|
||||
### Ollama Create - Create a model from a Modelfile
|
||||
- [create.py](create.py)
|
||||
|
||||
|
||||
### Ollama Embed - Generate embeddings with a model
|
||||
- [embed.py](embed.py)
|
||||
|
||||
|
||||
### Thinking - Enable thinking mode for a model
|
||||
- [thinking.py](thinking.py)
|
||||
|
||||
### Thinking (generate) - Enable thinking mode for a model
|
||||
- [thinking-generate.py](thinking-generate.py)
|
||||
|
||||
### Thinking (levels) - Choose the thinking level
|
||||
- [thinking-levels.py](thinking-levels.py)
|
||||
@@ -0,0 +1,20 @@
|
||||
import asyncio
|
||||
|
||||
from ollama import AsyncClient
|
||||
|
||||
|
||||
async def main():
|
||||
messages = [
|
||||
{
|
||||
'role': 'user',
|
||||
'content': 'Why is the sky blue?',
|
||||
},
|
||||
]
|
||||
|
||||
client = AsyncClient()
|
||||
response = await client.chat('gemma3', messages=messages)
|
||||
print(response['message']['content'])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,16 @@
|
||||
import asyncio
|
||||
|
||||
import ollama
|
||||
|
||||
|
||||
async def main():
|
||||
client = ollama.AsyncClient()
|
||||
response = await client.generate('gemma3', 'Why is the sky blue?')
|
||||
print(response['response'])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
asyncio.run(main())
|
||||
except KeyboardInterrupt:
|
||||
print('\nGoodbye!')
|
||||
@@ -0,0 +1,34 @@
|
||||
import asyncio
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from ollama import AsyncClient
|
||||
|
||||
|
||||
# Define the schema for the response
|
||||
class FriendInfo(BaseModel):
|
||||
name: str
|
||||
age: int
|
||||
is_available: bool
|
||||
|
||||
|
||||
class FriendList(BaseModel):
|
||||
friends: list[FriendInfo]
|
||||
|
||||
|
||||
async def main():
|
||||
client = AsyncClient()
|
||||
response = await client.chat(
|
||||
model='llama3.1:8b',
|
||||
messages=[{'role': 'user', 'content': 'I have two friends. The first is Ollama 22 years old busy saving the world, and the second is Alonso 23 years old and wants to hang out. Return a list of friends in JSON format'}],
|
||||
format=FriendList.model_json_schema(), # Use Pydantic to generate the schema
|
||||
options={'temperature': 0}, # Make responses more deterministic
|
||||
)
|
||||
|
||||
# Use Pydantic to validate the response
|
||||
friends_response = FriendList.model_validate_json(response.message.content)
|
||||
print(friends_response)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,93 @@
|
||||
import asyncio
|
||||
|
||||
import ollama
|
||||
from ollama import ChatResponse
|
||||
|
||||
|
||||
def add_two_numbers(a: int, b: int) -> int:
|
||||
"""
|
||||
Add two numbers
|
||||
|
||||
Args:
|
||||
a (int): The first number
|
||||
b (int): The second number
|
||||
|
||||
Returns:
|
||||
int: The sum of the two numbers
|
||||
"""
|
||||
return a + b
|
||||
|
||||
|
||||
def subtract_two_numbers(a: int, b: int) -> int:
|
||||
"""
|
||||
Subtract two numbers
|
||||
"""
|
||||
return a - b
|
||||
|
||||
|
||||
# Tools can still be manually defined and passed into chat
|
||||
subtract_two_numbers_tool = {
|
||||
'type': 'function',
|
||||
'function': {
|
||||
'name': 'subtract_two_numbers',
|
||||
'description': 'Subtract two numbers',
|
||||
'parameters': {
|
||||
'type': 'object',
|
||||
'required': ['a', 'b'],
|
||||
'properties': {
|
||||
'a': {'type': 'integer', 'description': 'The first number'},
|
||||
'b': {'type': 'integer', 'description': 'The second number'},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
messages = [{'role': 'user', 'content': 'What is three plus one?'}]
|
||||
print('Prompt:', messages[0]['content'])
|
||||
|
||||
available_functions = {
|
||||
'add_two_numbers': add_two_numbers,
|
||||
'subtract_two_numbers': subtract_two_numbers,
|
||||
}
|
||||
|
||||
|
||||
async def main():
|
||||
client = ollama.AsyncClient()
|
||||
|
||||
response: ChatResponse = await client.chat(
|
||||
'llama3.1',
|
||||
messages=messages,
|
||||
tools=[add_two_numbers, subtract_two_numbers_tool],
|
||||
)
|
||||
|
||||
if response.message.tool_calls:
|
||||
# There may be multiple tool calls in the response
|
||||
for tool in response.message.tool_calls:
|
||||
# Ensure the function is available, and then call it
|
||||
if function_to_call := available_functions.get(tool.function.name):
|
||||
print('Calling function:', tool.function.name)
|
||||
print('Arguments:', tool.function.arguments)
|
||||
output = function_to_call(**tool.function.arguments)
|
||||
print('Function output:', output)
|
||||
else:
|
||||
print('Function', tool.function.name, 'not found')
|
||||
|
||||
# Only needed to chat with the model using the tool call results
|
||||
if response.message.tool_calls:
|
||||
# Add the function response to messages for the model to use
|
||||
messages.append(response.message)
|
||||
messages.append({'role': 'tool', 'content': str(output), 'tool_name': tool.function.name})
|
||||
|
||||
# Get final response from model with function outputs
|
||||
final_response = await client.chat('llama3.1', messages=messages)
|
||||
print('Final response:', final_response.message.content)
|
||||
|
||||
else:
|
||||
print('No tool calls returned from model')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
asyncio.run(main())
|
||||
except KeyboardInterrupt:
|
||||
print('\nGoodbye!')
|
||||
@@ -1,6 +1,5 @@
|
||||
from ollama import chat
|
||||
|
||||
|
||||
messages = [
|
||||
{
|
||||
'role': 'user',
|
||||
@@ -8,8 +7,5 @@ messages = [
|
||||
},
|
||||
]
|
||||
|
||||
for part in chat('mistral', messages=messages, stream=True):
|
||||
for part in chat('gemma3', messages=messages, stream=True):
|
||||
print(part['message']['content'], end='', flush=True)
|
||||
|
||||
# end with a newline
|
||||
print()
|
||||
@@ -0,0 +1,34 @@
|
||||
from ollama import chat
|
||||
|
||||
messages = [
|
||||
{
|
||||
'role': 'user',
|
||||
'content': 'Why is the sky blue?',
|
||||
},
|
||||
{
|
||||
'role': 'assistant',
|
||||
'content': "The sky is blue because of the way the Earth's atmosphere scatters sunlight.",
|
||||
},
|
||||
{
|
||||
'role': 'user',
|
||||
'content': 'What is the weather in Tokyo?',
|
||||
},
|
||||
{
|
||||
'role': 'assistant',
|
||||
'content': 'The weather in Tokyo is typically warm and humid during the summer months, with temperatures often exceeding 30°C (86°F). The city experiences a rainy season from June to September, with heavy rainfall and occasional typhoons. Winter is mild, with temperatures rarely dropping below freezing. The city is known for its high-tech and vibrant culture, with many popular tourist attractions such as the Tokyo Tower, Senso-ji Temple, and the bustling Shibuya district.',
|
||||
},
|
||||
]
|
||||
|
||||
while True:
|
||||
user_input = input('Chat with history: ')
|
||||
response = chat(
|
||||
'gemma3',
|
||||
messages=[*messages, {'role': 'user', 'content': user_input}],
|
||||
)
|
||||
|
||||
# Add the response to the messages to maintain the history
|
||||
messages += [
|
||||
{'role': 'user', 'content': user_input},
|
||||
{'role': 'assistant', 'content': response.message.content},
|
||||
]
|
||||
print(response.message.content + '\n')
|
||||
@@ -1,6 +1,5 @@
|
||||
from ollama import chat
|
||||
|
||||
|
||||
messages = [
|
||||
{
|
||||
'role': 'user',
|
||||
@@ -8,5 +7,5 @@ messages = [
|
||||
},
|
||||
]
|
||||
|
||||
response = chat('mistral', messages=messages)
|
||||
response = chat('gemma3', messages=messages)
|
||||
print(response['message']['content'])
|
||||
Executable
+10
@@ -0,0 +1,10 @@
|
||||
from ollama import Client
|
||||
|
||||
client = Client()
|
||||
response = client.create(
|
||||
model='my-assistant',
|
||||
from_='gemma3',
|
||||
system='You are mario from Super Mario Bros.',
|
||||
stream=False,
|
||||
)
|
||||
print(response.status)
|
||||
@@ -0,0 +1,4 @@
|
||||
from ollama import embed
|
||||
|
||||
response = embed(model='llama3.2', input='Hello, world!')
|
||||
print(response['embeddings'])
|
||||
@@ -1,16 +1,16 @@
|
||||
from ollama import generate
|
||||
|
||||
prefix = '''def remove_non_ascii(s: str) -> str:
|
||||
prompt = '''def remove_non_ascii(s: str) -> str:
|
||||
""" '''
|
||||
|
||||
suffix = """
|
||||
return result
|
||||
"""
|
||||
|
||||
|
||||
response = generate(
|
||||
model='codellama:7b-code',
|
||||
prompt=f'<PRE> {prefix} <SUF>{suffix} <MID>',
|
||||
prompt=prompt,
|
||||
suffix=suffix,
|
||||
options={
|
||||
'num_predict': 128,
|
||||
'temperature': 0,
|
||||
@@ -1,5 +1,4 @@
|
||||
from ollama import generate
|
||||
|
||||
|
||||
for part in generate('mistral', 'Why is the sky blue?', stream=True):
|
||||
for part in generate('gemma3', 'Why is the sky blue?', stream=True):
|
||||
print(part['response'], end='', flush=True)
|
||||
@@ -1,5 +1,4 @@
|
||||
from ollama import generate
|
||||
|
||||
|
||||
response = generate('mistral', 'Why is the sky blue?')
|
||||
response = generate('gemma3', 'Why is the sky blue?')
|
||||
print(response['response'])
|
||||
@@ -0,0 +1,198 @@
|
||||
# /// script
|
||||
# requires-python = ">=3.11"
|
||||
# dependencies = [
|
||||
# "gpt-oss",
|
||||
# "ollama",
|
||||
# "rich",
|
||||
# ]
|
||||
# ///
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
from typing import Iterator, Optional
|
||||
|
||||
from gpt_oss.tools.simple_browser import ExaBackend, SimpleBrowserTool
|
||||
from openai_harmony import Author, Role, TextContent
|
||||
from openai_harmony import Message as HarmonyMessage
|
||||
from rich import print
|
||||
|
||||
from ollama import Client
|
||||
from ollama._types import ChatResponse
|
||||
|
||||
_backend = ExaBackend(source='web')
|
||||
_browser_tool = SimpleBrowserTool(backend=_backend)
|
||||
|
||||
|
||||
def heading(text):
|
||||
print(text)
|
||||
print('=' * (len(text) + 3))
|
||||
|
||||
|
||||
async def _browser_search_async(query: str, topn: int = 10, source: str | None = None) -> str:
|
||||
# map Ollama message to Harmony format
|
||||
harmony_message = HarmonyMessage(
|
||||
author=Author(role=Role.USER),
|
||||
content=[TextContent(text=json.dumps({'query': query, 'topn': topn}))],
|
||||
recipient='browser.search',
|
||||
)
|
||||
|
||||
result_text: str = ''
|
||||
async for response in _browser_tool._process(harmony_message):
|
||||
if response.content:
|
||||
for content in response.content:
|
||||
if isinstance(content, TextContent):
|
||||
result_text += content.text
|
||||
return result_text or f'No results for query: {query}'
|
||||
|
||||
|
||||
async def _browser_open_async(id: int | str = -1, cursor: int = -1, loc: int = -1, num_lines: int = -1, *, view_source: bool = False, source: str | None = None) -> str:
|
||||
payload = {'id': id, 'cursor': cursor, 'loc': loc, 'num_lines': num_lines, 'view_source': view_source, 'source': source}
|
||||
|
||||
harmony_message = HarmonyMessage(
|
||||
author=Author(role=Role.USER),
|
||||
content=[TextContent(text=json.dumps(payload))],
|
||||
recipient='browser.open',
|
||||
)
|
||||
|
||||
result_text: str = ''
|
||||
async for response in _browser_tool._process(harmony_message):
|
||||
if response.content:
|
||||
for content in response.content:
|
||||
if isinstance(content, TextContent):
|
||||
result_text += content.text
|
||||
return result_text or f'Could not open: {id}'
|
||||
|
||||
|
||||
async def _browser_find_async(pattern: str, cursor: int = -1) -> str:
|
||||
payload = {'pattern': pattern, 'cursor': cursor}
|
||||
|
||||
harmony_message = HarmonyMessage(
|
||||
author=Author(role=Role.USER),
|
||||
content=[TextContent(text=json.dumps(payload))],
|
||||
recipient='browser.find',
|
||||
)
|
||||
|
||||
result_text: str = ''
|
||||
async for response in _browser_tool._process(harmony_message):
|
||||
if response.content:
|
||||
for content in response.content:
|
||||
if isinstance(content, TextContent):
|
||||
result_text += content.text
|
||||
return result_text or f'Pattern not found: {pattern}'
|
||||
|
||||
|
||||
def browser_search(query: str, topn: int = 10, source: Optional[str] = None) -> str:
|
||||
return asyncio.run(_browser_search_async(query=query, topn=topn, source=source))
|
||||
|
||||
|
||||
def browser_open(id: int | str = -1, cursor: int = -1, loc: int = -1, num_lines: int = -1, *, view_source: bool = False, source: Optional[str] = None) -> str:
|
||||
return asyncio.run(_browser_open_async(id=id, cursor=cursor, loc=loc, num_lines=num_lines, view_source=view_source, source=source))
|
||||
|
||||
|
||||
def browser_find(pattern: str, cursor: int = -1) -> str:
|
||||
return asyncio.run(_browser_find_async(pattern=pattern, cursor=cursor))
|
||||
|
||||
|
||||
# Schema definitions for each browser tool
|
||||
browser_search_schema = {
|
||||
'type': 'function',
|
||||
'function': {
|
||||
'name': 'browser.search',
|
||||
},
|
||||
}
|
||||
|
||||
browser_open_schema = {
|
||||
'type': 'function',
|
||||
'function': {
|
||||
'name': 'browser.open',
|
||||
},
|
||||
}
|
||||
|
||||
browser_find_schema = {
|
||||
'type': 'function',
|
||||
'function': {
|
||||
'name': 'browser.find',
|
||||
},
|
||||
}
|
||||
|
||||
available_tools = {
|
||||
'browser.search': browser_search,
|
||||
'browser.open': browser_open,
|
||||
'browser.find': browser_find,
|
||||
}
|
||||
|
||||
|
||||
model = 'gpt-oss:20b'
|
||||
print('Model: ', model, '\n')
|
||||
|
||||
prompt = 'What is Ollama?'
|
||||
print('You: ', prompt, '\n')
|
||||
messages = [{'role': 'user', 'content': prompt}]
|
||||
|
||||
client = Client()
|
||||
|
||||
# gpt-oss can call tools while "thinking"
|
||||
# a loop is needed to call the tools and get the results
|
||||
final = True
|
||||
while True:
|
||||
response_stream: Iterator[ChatResponse] = client.chat(
|
||||
model=model,
|
||||
messages=messages,
|
||||
tools=[browser_search_schema, browser_open_schema, browser_find_schema],
|
||||
options={'num_ctx': 8192}, # 8192 is the recommended lower limit for the context window
|
||||
stream=True,
|
||||
)
|
||||
|
||||
tool_calls = []
|
||||
thinking = ''
|
||||
content = ''
|
||||
|
||||
for chunk in response_stream:
|
||||
if chunk.message.tool_calls:
|
||||
tool_calls.extend(chunk.message.tool_calls)
|
||||
|
||||
if chunk.message.content:
|
||||
if not (chunk.message.thinking or chunk.message.thinking == '') and final:
|
||||
heading('\n\nFinal result: ')
|
||||
final = False
|
||||
print(chunk.message.content, end='', flush=True)
|
||||
|
||||
if chunk.message.thinking:
|
||||
thinking += chunk.message.thinking
|
||||
print(chunk.message.thinking, end='', flush=True)
|
||||
|
||||
if thinking != '':
|
||||
messages.append({'role': 'assistant', 'content': thinking, 'tool_calls': tool_calls})
|
||||
|
||||
print()
|
||||
|
||||
if tool_calls:
|
||||
for tool_call in tool_calls:
|
||||
tool_name = tool_call.function.name
|
||||
args = tool_call.function.arguments or {}
|
||||
function_to_call = available_tools.get(tool_name)
|
||||
|
||||
if function_to_call:
|
||||
heading(f'\nCalling tool: {tool_name}')
|
||||
if args:
|
||||
print(f'Arguments: {args}')
|
||||
|
||||
try:
|
||||
result = function_to_call(**args)
|
||||
print(f'Tool result: {result[:200]}')
|
||||
if len(result) > 200:
|
||||
heading('... [truncated]')
|
||||
print()
|
||||
|
||||
result_message = {'role': 'tool', 'content': result, 'tool_name': tool_name}
|
||||
messages.append(result_message)
|
||||
|
||||
except Exception as e:
|
||||
err = f'Error from {tool_name}: {e}'
|
||||
print(err)
|
||||
messages.append({'role': 'tool', 'content': err, 'tool_name': tool_name})
|
||||
else:
|
||||
print(f'Tool {tool_name} not found')
|
||||
else:
|
||||
# no more tool calls, we can stop the loop
|
||||
break
|
||||
@@ -0,0 +1,175 @@
|
||||
# /// script
|
||||
# requires-python = ">=3.11"
|
||||
# dependencies = [
|
||||
# "gpt-oss",
|
||||
# "ollama",
|
||||
# "rich",
|
||||
# ]
|
||||
# ///
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
from typing import Optional
|
||||
|
||||
from gpt_oss.tools.simple_browser import ExaBackend, SimpleBrowserTool
|
||||
from openai_harmony import Author, Role, TextContent
|
||||
from openai_harmony import Message as HarmonyMessage
|
||||
|
||||
from ollama import Client
|
||||
|
||||
_backend = ExaBackend(source='web')
|
||||
_browser_tool = SimpleBrowserTool(backend=_backend)
|
||||
|
||||
|
||||
def heading(text):
|
||||
print(text)
|
||||
print('=' * (len(text) + 3))
|
||||
|
||||
|
||||
async def _browser_search_async(query: str, topn: int = 10, source: str | None = None) -> str:
|
||||
# map Ollama message to Harmony format
|
||||
harmony_message = HarmonyMessage(
|
||||
author=Author(role=Role.USER),
|
||||
content=[TextContent(text=json.dumps({'query': query, 'topn': topn}))],
|
||||
recipient='browser.search',
|
||||
)
|
||||
|
||||
result_text: str = ''
|
||||
async for response in _browser_tool._process(harmony_message):
|
||||
if response.content:
|
||||
for content in response.content:
|
||||
if isinstance(content, TextContent):
|
||||
result_text += content.text
|
||||
return result_text or f'No results for query: {query}'
|
||||
|
||||
|
||||
async def _browser_open_async(id: int | str = -1, cursor: int = -1, loc: int = -1, num_lines: int = -1, *, view_source: bool = False, source: str | None = None) -> str:
|
||||
payload = {'id': id, 'cursor': cursor, 'loc': loc, 'num_lines': num_lines, 'view_source': view_source, 'source': source}
|
||||
|
||||
harmony_message = HarmonyMessage(
|
||||
author=Author(role=Role.USER),
|
||||
content=[TextContent(text=json.dumps(payload))],
|
||||
recipient='browser.open',
|
||||
)
|
||||
|
||||
result_text: str = ''
|
||||
async for response in _browser_tool._process(harmony_message):
|
||||
if response.content:
|
||||
for content in response.content:
|
||||
if isinstance(content, TextContent):
|
||||
result_text += content.text
|
||||
return result_text or f'Could not open: {id}'
|
||||
|
||||
|
||||
async def _browser_find_async(pattern: str, cursor: int = -1) -> str:
|
||||
payload = {'pattern': pattern, 'cursor': cursor}
|
||||
|
||||
harmony_message = HarmonyMessage(
|
||||
author=Author(role=Role.USER),
|
||||
content=[TextContent(text=json.dumps(payload))],
|
||||
recipient='browser.find',
|
||||
)
|
||||
|
||||
result_text: str = ''
|
||||
async for response in _browser_tool._process(harmony_message):
|
||||
if response.content:
|
||||
for content in response.content:
|
||||
if isinstance(content, TextContent):
|
||||
result_text += content.text
|
||||
return result_text or f'Pattern not found: {pattern}'
|
||||
|
||||
|
||||
def browser_search(query: str, topn: int = 10, source: Optional[str] = None) -> str:
|
||||
return asyncio.run(_browser_search_async(query=query, topn=topn, source=source))
|
||||
|
||||
|
||||
def browser_open(id: int | str = -1, cursor: int = -1, loc: int = -1, num_lines: int = -1, *, view_source: bool = False, source: Optional[str] = None) -> str:
|
||||
return asyncio.run(_browser_open_async(id=id, cursor=cursor, loc=loc, num_lines=num_lines, view_source=view_source, source=source))
|
||||
|
||||
|
||||
def browser_find(pattern: str, cursor: int = -1) -> str:
|
||||
return asyncio.run(_browser_find_async(pattern=pattern, cursor=cursor))
|
||||
|
||||
|
||||
# Schema definitions for each browser tool
|
||||
browser_search_schema = {
|
||||
'type': 'function',
|
||||
'function': {
|
||||
'name': 'browser.search',
|
||||
},
|
||||
}
|
||||
|
||||
browser_open_schema = {
|
||||
'type': 'function',
|
||||
'function': {
|
||||
'name': 'browser.open',
|
||||
},
|
||||
}
|
||||
|
||||
browser_find_schema = {
|
||||
'type': 'function',
|
||||
'function': {
|
||||
'name': 'browser.find',
|
||||
},
|
||||
}
|
||||
|
||||
available_tools = {
|
||||
'browser.search': browser_search,
|
||||
'browser.open': browser_open,
|
||||
'browser.find': browser_find,
|
||||
}
|
||||
|
||||
|
||||
model = 'gpt-oss:20b'
|
||||
print('Model: ', model, '\n')
|
||||
|
||||
prompt = 'What is Ollama?'
|
||||
print('You: ', prompt, '\n')
|
||||
messages = [{'role': 'user', 'content': prompt}]
|
||||
|
||||
client = Client()
|
||||
while True:
|
||||
response = client.chat(
|
||||
model=model,
|
||||
messages=messages,
|
||||
tools=[browser_search_schema, browser_open_schema, browser_find_schema],
|
||||
options={'num_ctx': 8192}, # 8192 is the recommended lower limit for the context window
|
||||
)
|
||||
|
||||
if hasattr(response.message, 'thinking') and response.message.thinking:
|
||||
heading('Thinking')
|
||||
print(response.message.thinking.strip() + '\n')
|
||||
|
||||
if hasattr(response.message, 'content') and response.message.content:
|
||||
heading('Assistant')
|
||||
print(response.message.content.strip() + '\n')
|
||||
|
||||
# add message to chat history
|
||||
messages.append(response.message)
|
||||
|
||||
if response.message.tool_calls:
|
||||
for tool_call in response.message.tool_calls:
|
||||
tool_name = tool_call.function.name
|
||||
args = tool_call.function.arguments or {}
|
||||
function_to_call = available_tools.get(tool_name)
|
||||
if not function_to_call:
|
||||
print(f'Unknown tool: {tool_name}')
|
||||
continue
|
||||
|
||||
try:
|
||||
result = function_to_call(**args)
|
||||
heading(f'Tool: {tool_name}')
|
||||
if args:
|
||||
print(f'Arguments: {args}')
|
||||
print(result[:200])
|
||||
if len(result) > 200:
|
||||
print('... [truncated]')
|
||||
print()
|
||||
messages.append({'role': 'tool', 'content': result, 'tool_name': tool_name})
|
||||
except Exception as e:
|
||||
err = f'Error from {tool_name}: {e}'
|
||||
print(err)
|
||||
messages.append({'role': 'tool', 'content': err, 'tool_name': tool_name})
|
||||
else:
|
||||
# break on no more tool calls
|
||||
break
|
||||
@@ -0,0 +1,105 @@
|
||||
# /// script
|
||||
# requires-python = ">=3.11"
|
||||
# dependencies = [
|
||||
# "gpt-oss",
|
||||
# "ollama",
|
||||
# "rich",
|
||||
# ]
|
||||
# ///
|
||||
import random
|
||||
from typing import Iterator
|
||||
|
||||
from rich import print
|
||||
|
||||
from ollama import Client
|
||||
from ollama._types import ChatResponse
|
||||
|
||||
|
||||
def get_weather(city: str) -> str:
|
||||
"""
|
||||
Get the current temperature for a city
|
||||
|
||||
Args:
|
||||
city (str): The name of the city
|
||||
|
||||
Returns:
|
||||
str: The current temperature
|
||||
"""
|
||||
temperatures = list(range(-10, 35))
|
||||
|
||||
temp = random.choice(temperatures)
|
||||
|
||||
return f'The temperature in {city} is {temp}°C'
|
||||
|
||||
|
||||
def get_weather_conditions(city: str) -> str:
|
||||
"""
|
||||
Get the weather conditions for a city
|
||||
|
||||
Args:
|
||||
city (str): The name of the city
|
||||
|
||||
Returns:
|
||||
str: The current weather conditions
|
||||
"""
|
||||
conditions = ['sunny', 'cloudy', 'rainy', 'snowy', 'foggy']
|
||||
return random.choice(conditions)
|
||||
|
||||
|
||||
available_tools = {'get_weather': get_weather, 'get_weather_conditions': get_weather_conditions}
|
||||
|
||||
messages = [{'role': 'user', 'content': 'What is the weather like in London? What are the conditions in Toronto?'}]
|
||||
|
||||
client = Client(
|
||||
# Ollama Turbo
|
||||
# host="https://ollama.com", headers={'Authorization': (os.getenv('OLLAMA_API_KEY'))}
|
||||
)
|
||||
|
||||
model = 'gpt-oss:20b'
|
||||
# gpt-oss can call tools while "thinking"
|
||||
# a loop is needed to call the tools and get the results
|
||||
final = True
|
||||
while True:
|
||||
response_stream: Iterator[ChatResponse] = client.chat(model=model, messages=messages, tools=[get_weather, get_weather_conditions], stream=True)
|
||||
tool_calls = []
|
||||
thinking = ''
|
||||
content = ''
|
||||
|
||||
for chunk in response_stream:
|
||||
if chunk.message.tool_calls:
|
||||
tool_calls.extend(chunk.message.tool_calls)
|
||||
|
||||
if chunk.message.content:
|
||||
if not (chunk.message.thinking or chunk.message.thinking == '') and final:
|
||||
print('\n\n' + '=' * 10)
|
||||
print('Final result: ')
|
||||
final = False
|
||||
print(chunk.message.content, end='', flush=True)
|
||||
|
||||
if chunk.message.thinking:
|
||||
# accumulate thinking
|
||||
thinking += chunk.message.thinking
|
||||
print(chunk.message.thinking, end='', flush=True)
|
||||
|
||||
if thinking != '' or content != '' or len(tool_calls) > 0:
|
||||
messages.append({'role': 'assistant', 'thinking': thinking, 'content': content, 'tool_calls': tool_calls})
|
||||
|
||||
print()
|
||||
|
||||
if tool_calls:
|
||||
for tool_call in tool_calls:
|
||||
function_to_call = available_tools.get(tool_call.function.name)
|
||||
if function_to_call:
|
||||
print('\nCalling tool:', tool_call.function.name, 'with arguments: ', tool_call.function.arguments)
|
||||
result = function_to_call(**tool_call.function.arguments)
|
||||
print('Tool result: ', result + '\n')
|
||||
|
||||
result_message = {'role': 'tool', 'content': result, 'tool_name': tool_call.function.name}
|
||||
messages.append(result_message)
|
||||
else:
|
||||
print(f'Tool {tool_call.function.name} not found')
|
||||
messages.append({'role': 'tool', 'content': f'Tool {tool_call.function.name} not found', 'tool_name': tool_call.function.name})
|
||||
|
||||
else:
|
||||
# no more tool calls, we can stop the loop
|
||||
break
|
||||
@@ -0,0 +1,84 @@
|
||||
# /// script
|
||||
# requires-python = ">=3.11"
|
||||
# dependencies = [
|
||||
# "gpt-oss",
|
||||
# "ollama",
|
||||
# "rich",
|
||||
# ]
|
||||
# ///
|
||||
import random
|
||||
|
||||
from rich import print
|
||||
|
||||
from ollama import Client
|
||||
from ollama._types import ChatResponse
|
||||
|
||||
|
||||
def get_weather(city: str) -> str:
|
||||
"""
|
||||
Get the current temperature for a city
|
||||
|
||||
Args:
|
||||
city (str): The name of the city
|
||||
|
||||
Returns:
|
||||
str: The current temperature
|
||||
"""
|
||||
temperatures = list(range(-10, 35))
|
||||
|
||||
temp = random.choice(temperatures)
|
||||
|
||||
return f'The temperature in {city} is {temp}°C'
|
||||
|
||||
|
||||
def get_weather_conditions(city: str) -> str:
|
||||
"""
|
||||
Get the weather conditions for a city
|
||||
|
||||
Args:
|
||||
city (str): The name of the city
|
||||
|
||||
Returns:
|
||||
str: The current weather conditions
|
||||
"""
|
||||
conditions = ['sunny', 'cloudy', 'rainy', 'snowy', 'foggy']
|
||||
return random.choice(conditions)
|
||||
|
||||
|
||||
available_tools = {'get_weather': get_weather, 'get_weather_conditions': get_weather_conditions}
|
||||
|
||||
messages = [{'role': 'user', 'content': 'What is the weather like in London? What are the conditions in Toronto?'}]
|
||||
|
||||
|
||||
client = Client(
|
||||
# Ollama Turbo
|
||||
# host="https://ollama.com", headers={'Authorization': (os.getenv('OLLAMA_API_KEY'))}
|
||||
)
|
||||
model = 'gpt-oss:20b'
|
||||
# gpt-oss can call tools while "thinking"
|
||||
# a loop is needed to call the tools and get the results
|
||||
while True:
|
||||
response: ChatResponse = client.chat(model=model, messages=messages, tools=[get_weather, get_weather_conditions])
|
||||
|
||||
if response.message.content:
|
||||
print('Content: ')
|
||||
print(response.message.content + '\n')
|
||||
if response.message.thinking:
|
||||
print('Thinking: ')
|
||||
print(response.message.thinking + '\n')
|
||||
|
||||
messages.append(response.message)
|
||||
|
||||
if response.message.tool_calls:
|
||||
for tool_call in response.message.tool_calls:
|
||||
function_to_call = available_tools.get(tool_call.function.name)
|
||||
if function_to_call:
|
||||
result = function_to_call(**tool_call.function.arguments)
|
||||
print('Result from tool call name: ', tool_call.function.name, 'with arguments: ', tool_call.function.arguments, 'result: ', result + '\n')
|
||||
messages.append({'role': 'tool', 'content': result, 'tool_name': tool_call.function.name})
|
||||
else:
|
||||
print(f'Tool {tool_call.function.name} not found')
|
||||
messages.append({'role': 'tool', 'content': f'Tool {tool_call.function.name} not found', 'tool_name': tool_call.function.name})
|
||||
else:
|
||||
# no more tool calls, we can stop the loop
|
||||
break
|
||||
@@ -0,0 +1,13 @@
|
||||
from ollama import ListResponse, list
|
||||
|
||||
response: ListResponse = list()
|
||||
|
||||
for model in response.models:
|
||||
print('Name:', model.model)
|
||||
print(' Size (MB):', f'{(model.size.real / 1024 / 1024):.2f}')
|
||||
if model.details:
|
||||
print(' Format:', model.details.format)
|
||||
print(' Family:', model.details.family)
|
||||
print(' Parameter Size:', model.details.parameter_size)
|
||||
print(' Quantization Level:', model.details.quantization_level)
|
||||
print('\n')
|
||||
@@ -0,0 +1,88 @@
|
||||
import random
|
||||
from typing import Iterator
|
||||
|
||||
from ollama import ChatResponse, Client
|
||||
|
||||
|
||||
def get_temperature(city: str) -> int:
|
||||
"""
|
||||
Get the temperature for a city in Celsius
|
||||
|
||||
Args:
|
||||
city (str): The name of the city
|
||||
|
||||
Returns:
|
||||
int: The current temperature in Celsius
|
||||
"""
|
||||
# This is a mock implementation - would need to use a real weather API
|
||||
import random
|
||||
|
||||
if city not in ['London', 'Paris', 'New York', 'Tokyo', 'Sydney']:
|
||||
return 'Unknown city'
|
||||
|
||||
return str(random.randint(0, 35)) + ' degrees Celsius'
|
||||
|
||||
|
||||
def get_conditions(city: str) -> str:
|
||||
"""
|
||||
Get the weather conditions for a city
|
||||
"""
|
||||
if city not in ['London', 'Paris', 'New York', 'Tokyo', 'Sydney']:
|
||||
return 'Unknown city'
|
||||
# This is a mock implementation - would need to use a real weather API
|
||||
conditions = ['sunny', 'cloudy', 'rainy', 'snowy']
|
||||
return random.choice(conditions)
|
||||
|
||||
|
||||
available_functions = {
|
||||
'get_temperature': get_temperature,
|
||||
'get_conditions': get_conditions,
|
||||
}
|
||||
|
||||
|
||||
cities = ['London', 'Paris', 'New York', 'Tokyo', 'Sydney']
|
||||
city = random.choice(cities)
|
||||
city2 = random.choice(cities)
|
||||
messages = [{'role': 'user', 'content': f'What is the temperature in {city}? and what are the weather conditions in {city2}?'}]
|
||||
print('----- Prompt:', messages[0]['content'], '\n')
|
||||
|
||||
model = 'qwen3'
|
||||
client = Client()
|
||||
response: Iterator[ChatResponse] = client.chat(model, stream=True, messages=messages, tools=[get_temperature, get_conditions], think=True)
|
||||
|
||||
for chunk in response:
|
||||
if chunk.message.thinking:
|
||||
print(chunk.message.thinking, end='', flush=True)
|
||||
if chunk.message.content:
|
||||
print(chunk.message.content, end='', flush=True)
|
||||
if chunk.message.tool_calls:
|
||||
for tool in chunk.message.tool_calls:
|
||||
if function_to_call := available_functions.get(tool.function.name):
|
||||
print('\nCalling function:', tool.function.name, 'with arguments:', tool.function.arguments)
|
||||
output = function_to_call(**tool.function.arguments)
|
||||
print('> Function output:', output, '\n')
|
||||
|
||||
# Add the assistant message and tool call result to the messages
|
||||
messages.append(chunk.message)
|
||||
messages.append({'role': 'tool', 'content': str(output), 'tool_name': tool.function.name})
|
||||
else:
|
||||
print('Function', tool.function.name, 'not found')
|
||||
|
||||
print('----- Sending result back to model \n')
|
||||
if any(msg.get('role') == 'tool' for msg in messages):
|
||||
res = client.chat(model, stream=True, tools=[get_temperature, get_conditions], messages=messages, think=True)
|
||||
done_thinking = False
|
||||
for chunk in res:
|
||||
if chunk.message.thinking:
|
||||
print(chunk.message.thinking, end='', flush=True)
|
||||
if chunk.message.content:
|
||||
if not done_thinking:
|
||||
print('\n----- Final result:')
|
||||
done_thinking = True
|
||||
print(chunk.message.content, end='', flush=True)
|
||||
if chunk.message.tool_calls:
|
||||
# Model should be explaining the tool calls and the results in this output
|
||||
print('Model returned tool calls:')
|
||||
print(chunk.message.tool_calls)
|
||||
else:
|
||||
print('No tool calls returned')
|
||||
@@ -0,0 +1,24 @@
|
||||
from ollama import chat
|
||||
|
||||
# from pathlib import Path
|
||||
|
||||
# Pass in the path to the image
|
||||
path = input('Please enter the path to the image: ')
|
||||
|
||||
# You can also pass in base64 encoded image data
|
||||
# img = base64.b64encode(Path(path).read_bytes()).decode()
|
||||
# or the raw bytes
|
||||
# img = Path(path).read_bytes()
|
||||
|
||||
response = chat(
|
||||
model='gemma3',
|
||||
messages=[
|
||||
{
|
||||
'role': 'user',
|
||||
'content': 'What is in this image? Be concise.',
|
||||
'images': [path],
|
||||
}
|
||||
],
|
||||
)
|
||||
|
||||
print(response.message.content)
|
||||
@@ -1,17 +1,14 @@
|
||||
import sys
|
||||
import random
|
||||
import sys
|
||||
|
||||
import httpx
|
||||
|
||||
from ollama import generate
|
||||
|
||||
|
||||
latest = httpx.get('https://xkcd.com/info.0.json')
|
||||
latest.raise_for_status()
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
num = int(sys.argv[1])
|
||||
else:
|
||||
num = random.randint(1, latest.json().get('num'))
|
||||
num = int(sys.argv[1]) if len(sys.argv) > 1 else random.randint(1, latest.json().get('num'))
|
||||
|
||||
comic = httpx.get(f'https://xkcd.com/{num}/info.0.json')
|
||||
comic.raise_for_status()
|
||||
@@ -0,0 +1,27 @@
|
||||
from ollama import ProcessResponse, chat, ps, pull
|
||||
|
||||
# Ensure at least one model is loaded
|
||||
response = pull('gemma3', stream=True)
|
||||
progress_states = set()
|
||||
for progress in response:
|
||||
if progress.get('status') in progress_states:
|
||||
continue
|
||||
progress_states.add(progress.get('status'))
|
||||
print(progress.get('status'))
|
||||
|
||||
print('\n')
|
||||
|
||||
print('Waiting for model to load... \n')
|
||||
chat(model='gemma3', messages=[{'role': 'user', 'content': 'Why is the sky blue?'}])
|
||||
|
||||
|
||||
response: ProcessResponse = ps()
|
||||
for model in response.models:
|
||||
print('Model: ', model.model)
|
||||
print(' Digest: ', model.digest)
|
||||
print(' Expires at: ', model.expires_at)
|
||||
print(' Size: ', model.size)
|
||||
print(' Size vram: ', model.size_vram)
|
||||
print(' Details: ', model.details)
|
||||
print(' Context length: ', model.context_length)
|
||||
print('\n')
|
||||
@@ -0,0 +1,21 @@
|
||||
from tqdm import tqdm
|
||||
|
||||
from ollama import pull
|
||||
|
||||
current_digest, bars = '', {}
|
||||
for progress in pull('gemma3', stream=True):
|
||||
digest = progress.get('digest', '')
|
||||
if digest != current_digest and current_digest in bars:
|
||||
bars[current_digest].close()
|
||||
|
||||
if not digest:
|
||||
print(progress.get('status'))
|
||||
continue
|
||||
|
||||
if digest not in bars and (total := progress.get('total')):
|
||||
bars[digest] = tqdm(total=total, desc=f'pulling {digest[7:19]}', unit='B', unit_scale=True)
|
||||
|
||||
if completed := progress.get('completed'):
|
||||
bars[digest].update(completed - bars[digest].n)
|
||||
|
||||
current_digest = digest
|
||||
@@ -0,0 +1,12 @@
|
||||
from ollama import ShowResponse, show
|
||||
|
||||
response: ShowResponse = show('gemma3')
|
||||
print('Model Information:')
|
||||
print(f'Modified at: {response.modified_at}')
|
||||
print(f'Template: {response.template}')
|
||||
print(f'Modelfile: {response.modelfile}')
|
||||
print(f'License: {response.license}')
|
||||
print(f'Details: {response.details}')
|
||||
print(f'Model Info: {response.modelinfo}')
|
||||
print(f'Parameters: {response.parameters}')
|
||||
print(f'Capabilities: {response.capabilities}')
|
||||
@@ -0,0 +1,51 @@
|
||||
from pathlib import Path
|
||||
from typing import Literal
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from ollama import chat
|
||||
|
||||
|
||||
# Define the schema for image objects
|
||||
class Object(BaseModel):
|
||||
name: str
|
||||
confidence: float
|
||||
attributes: str
|
||||
|
||||
|
||||
class ImageDescription(BaseModel):
|
||||
summary: str
|
||||
objects: list[Object]
|
||||
scene: str
|
||||
colors: list[str]
|
||||
time_of_day: Literal['Morning', 'Afternoon', 'Evening', 'Night']
|
||||
setting: Literal['Indoor', 'Outdoor', 'Unknown']
|
||||
text_content: str | None = None
|
||||
|
||||
|
||||
# Get path from user input
|
||||
path = input('Enter the path to your image: ')
|
||||
path = Path(path)
|
||||
|
||||
# Verify the file exists
|
||||
if not path.exists():
|
||||
raise FileNotFoundError(f'Image not found at: {path}')
|
||||
|
||||
# Set up chat as usual
|
||||
response = chat(
|
||||
model='gemma3',
|
||||
format=ImageDescription.model_json_schema(), # Pass in the schema for the response
|
||||
messages=[
|
||||
{
|
||||
'role': 'user',
|
||||
'content': 'Analyze this image and return a detailed JSON description including objects, scene, colors and any text detected. If you cannot determine certain details, leave those fields empty.',
|
||||
'images': [path],
|
||||
},
|
||||
],
|
||||
options={'temperature': 0}, # Set temperature to 0 for more deterministic output
|
||||
)
|
||||
|
||||
|
||||
# Convert received content to the schema
|
||||
image_analysis = ImageDescription.model_validate_json(response.message.content)
|
||||
print(image_analysis)
|
||||
@@ -0,0 +1,27 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
from ollama import chat
|
||||
|
||||
|
||||
# Define the schema for the response
|
||||
class FriendInfo(BaseModel):
|
||||
name: str
|
||||
age: int
|
||||
is_available: bool
|
||||
|
||||
|
||||
class FriendList(BaseModel):
|
||||
friends: list[FriendInfo]
|
||||
|
||||
|
||||
# schema = {'type': 'object', 'properties': {'friends': {'type': 'array', 'items': {'type': 'object', 'properties': {'name': {'type': 'string'}, 'age': {'type': 'integer'}, 'is_available': {'type': 'boolean'}}, 'required': ['name', 'age', 'is_available']}}}, 'required': ['friends']}
|
||||
response = chat(
|
||||
model='llama3.1:8b',
|
||||
messages=[{'role': 'user', 'content': 'I have two friends. The first is Ollama 22 years old busy saving the world, and the second is Alonso 23 years old and wants to hang out. Return a list of friends in JSON format'}],
|
||||
format=FriendList.model_json_schema(), # Use Pydantic to generate the schema or format=schema
|
||||
options={'temperature': 0}, # Make responses more deterministic
|
||||
)
|
||||
|
||||
# Use Pydantic to validate the response
|
||||
friends_response = FriendList.model_validate_json(response.message.content)
|
||||
print(friends_response)
|
||||
@@ -0,0 +1,6 @@
|
||||
from ollama import generate
|
||||
|
||||
response = generate('deepseek-r1', 'why is the sky blue', think=True)
|
||||
|
||||
print('Thinking:\n========\n\n' + response.thinking)
|
||||
print('\nResponse:\n========\n\n' + response.response)
|
||||
@@ -0,0 +1,26 @@
|
||||
from ollama import chat
|
||||
|
||||
|
||||
def heading(text):
|
||||
print(text)
|
||||
print('=' * len(text))
|
||||
|
||||
|
||||
messages = [
|
||||
{'role': 'user', 'content': 'What is 10 + 23?'},
|
||||
]
|
||||
|
||||
# gpt-oss supports 'low', 'medium', 'high'
|
||||
levels = ['low', 'medium', 'high']
|
||||
for i, level in enumerate(levels):
|
||||
response = chat('gpt-oss:20b', messages=messages, think=level)
|
||||
|
||||
heading(f'Thinking ({level})')
|
||||
print(response.message.thinking)
|
||||
print('\n')
|
||||
heading('Response')
|
||||
print(response.message.content)
|
||||
print('\n')
|
||||
if i < len(levels) - 1:
|
||||
print('-' * 20)
|
||||
print('\n')
|
||||
@@ -0,0 +1,13 @@
|
||||
from ollama import chat
|
||||
|
||||
messages = [
|
||||
{
|
||||
'role': 'user',
|
||||
'content': 'What is 10 + 23?',
|
||||
},
|
||||
]
|
||||
|
||||
response = chat('deepseek-r1', messages=messages, think=True)
|
||||
|
||||
print('Thinking:\n========\n\n' + response.message.thinking)
|
||||
print('\nResponse:\n========\n\n' + response.message.content)
|
||||
@@ -0,0 +1,84 @@
|
||||
from ollama import ChatResponse, chat
|
||||
|
||||
|
||||
def add_two_numbers(a: int, b: int) -> int:
|
||||
"""
|
||||
Add two numbers
|
||||
|
||||
Args:
|
||||
a (int): The first number
|
||||
b (int): The second number
|
||||
|
||||
Returns:
|
||||
int: The sum of the two numbers
|
||||
"""
|
||||
|
||||
# The cast is necessary as returned tool call arguments don't always conform exactly to schema
|
||||
# E.g. this would prevent "what is 30 + 12" to produce '3012' instead of 42
|
||||
return int(a) + int(b)
|
||||
|
||||
|
||||
def subtract_two_numbers(a: int, b: int) -> int:
|
||||
"""
|
||||
Subtract two numbers
|
||||
"""
|
||||
|
||||
# The cast is necessary as returned tool call arguments don't always conform exactly to schema
|
||||
return int(a) - int(b)
|
||||
|
||||
|
||||
# Tools can still be manually defined and passed into chat
|
||||
subtract_two_numbers_tool = {
|
||||
'type': 'function',
|
||||
'function': {
|
||||
'name': 'subtract_two_numbers',
|
||||
'description': 'Subtract two numbers',
|
||||
'parameters': {
|
||||
'type': 'object',
|
||||
'required': ['a', 'b'],
|
||||
'properties': {
|
||||
'a': {'type': 'integer', 'description': 'The first number'},
|
||||
'b': {'type': 'integer', 'description': 'The second number'},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
messages = [{'role': 'user', 'content': 'What is three plus one?'}]
|
||||
print('Prompt:', messages[0]['content'])
|
||||
|
||||
available_functions = {
|
||||
'add_two_numbers': add_two_numbers,
|
||||
'subtract_two_numbers': subtract_two_numbers,
|
||||
}
|
||||
|
||||
response: ChatResponse = chat(
|
||||
'llama3.1',
|
||||
messages=messages,
|
||||
tools=[add_two_numbers, subtract_two_numbers_tool],
|
||||
)
|
||||
|
||||
if response.message.tool_calls:
|
||||
# There may be multiple tool calls in the response
|
||||
for tool in response.message.tool_calls:
|
||||
# Ensure the function is available, and then call it
|
||||
if function_to_call := available_functions.get(tool.function.name):
|
||||
print('Calling function:', tool.function.name)
|
||||
print('Arguments:', tool.function.arguments)
|
||||
output = function_to_call(**tool.function.arguments)
|
||||
print('Function output:', output)
|
||||
else:
|
||||
print('Function', tool.function.name, 'not found')
|
||||
|
||||
# Only needed to chat with the model using the tool call results
|
||||
if response.message.tool_calls:
|
||||
# Add the function response to messages for the model to use
|
||||
messages.append(response.message)
|
||||
messages.append({'role': 'tool', 'content': str(output), 'tool_name': tool.function.name})
|
||||
|
||||
# Get final response from model with function outputs
|
||||
final_response = chat('llama3.1', messages=messages)
|
||||
print('Final response:', final_response.message.content)
|
||||
|
||||
else:
|
||||
print('No tool calls returned from model')
|
||||
+24
-16
@@ -1,40 +1,47 @@
|
||||
from ollama._client import Client, AsyncClient
|
||||
from ollama._client import AsyncClient, Client
|
||||
from ollama._types import (
|
||||
GenerateResponse,
|
||||
ChatResponse,
|
||||
ProgressResponse,
|
||||
EmbeddingsResponse,
|
||||
EmbedResponse,
|
||||
GenerateResponse,
|
||||
Image,
|
||||
ListResponse,
|
||||
Message,
|
||||
Options,
|
||||
ProcessResponse,
|
||||
ProgressResponse,
|
||||
RequestError,
|
||||
ResponseError,
|
||||
ShowResponse,
|
||||
StatusResponse,
|
||||
Tool,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
'Client',
|
||||
'AsyncClient',
|
||||
'GenerateResponse',
|
||||
'ChatResponse',
|
||||
'ProgressResponse',
|
||||
'Client',
|
||||
'EmbedResponse',
|
||||
'EmbeddingsResponse',
|
||||
'GenerateResponse',
|
||||
'Image',
|
||||
'ListResponse',
|
||||
'Message',
|
||||
'Options',
|
||||
'ProcessResponse',
|
||||
'ProgressResponse',
|
||||
'RequestError',
|
||||
'ResponseError',
|
||||
'generate',
|
||||
'chat',
|
||||
'embeddings',
|
||||
'pull',
|
||||
'push',
|
||||
'create',
|
||||
'delete',
|
||||
'list',
|
||||
'copy',
|
||||
'show',
|
||||
'ShowResponse',
|
||||
'StatusResponse',
|
||||
'Tool',
|
||||
]
|
||||
|
||||
_client = Client()
|
||||
|
||||
generate = _client.generate
|
||||
chat = _client.chat
|
||||
embed = _client.embed
|
||||
embeddings = _client.embeddings
|
||||
pull = _client.pull
|
||||
push = _client.push
|
||||
@@ -43,3 +50,4 @@ delete = _client.delete
|
||||
list = _client.list
|
||||
copy = _client.copy
|
||||
show = _client.show
|
||||
ps = _client.ps
|
||||
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
import base64
|
||||
import os
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
|
||||
|
||||
class OllamaAuth:
|
||||
def __init__(self, key_path: Optional[str] = None):
|
||||
"""Initialize the OllamaAuth class.
|
||||
|
||||
Args:
|
||||
key_path: Optional path to the private key file. If not provided,
|
||||
defaults to ~/.ollama/id_ed25519
|
||||
"""
|
||||
if key_path is None:
|
||||
home = str(Path.home())
|
||||
self.key_path = os.path.join(home, '.ollama', 'id_ed25519')
|
||||
else:
|
||||
# Expand ~ and environment variables in the path
|
||||
self.key_path = os.path.expanduser(os.path.expandvars(key_path))
|
||||
|
||||
def load_private_key(self):
|
||||
"""Read and load the private key.
|
||||
|
||||
Returns:
|
||||
The loaded Ed25519 private key.
|
||||
|
||||
Raises:
|
||||
FileNotFoundError: If the key file doesn't exist
|
||||
ValueError: If the key file is invalid
|
||||
"""
|
||||
try:
|
||||
with open(self.key_path, 'rb') as f:
|
||||
private_key_data = f.read()
|
||||
|
||||
private_key = serialization.load_ssh_private_key(
|
||||
private_key_data,
|
||||
password=None,
|
||||
)
|
||||
return private_key
|
||||
except FileNotFoundError:
|
||||
raise FileNotFoundError(f"Could not find Ollama private key at {self.key_path}. Please generate one using: ssh-keygen -t ed25519 -f ~/.ollama/id_ed25519 -N ''")
|
||||
except Exception as e:
|
||||
raise ValueError(f'Invalid private key at {self.key_path}: {e!s}')
|
||||
|
||||
def get_public_key_b64(self, private_key):
|
||||
"""Get the base64 encoded public key.
|
||||
|
||||
Args:
|
||||
private_key: The Ed25519 private key
|
||||
|
||||
Returns:
|
||||
Base64 encoded public key string
|
||||
"""
|
||||
# Get the public key in OpenSSH format and extract the second field (base64-encoded key)
|
||||
public_key = private_key.public_key()
|
||||
openssh_pub = (
|
||||
public_key.public_bytes(
|
||||
encoding=serialization.Encoding.OpenSSH,
|
||||
format=serialization.PublicFormat.OpenSSH,
|
||||
)
|
||||
.decode('utf-8')
|
||||
.strip()
|
||||
)
|
||||
parts = openssh_pub.split(' ')
|
||||
if len(parts) < 2:
|
||||
raise ValueError('Malformed OpenSSH public key')
|
||||
public_key_b64 = parts[1]
|
||||
return public_key_b64
|
||||
|
||||
def sign_request(self, method: str, path: str):
|
||||
"""Sign an HTTP request.
|
||||
|
||||
Args:
|
||||
method: The HTTP method (e.g. 'GET', 'POST')
|
||||
path: The request path (e.g. '/api/chat')
|
||||
|
||||
Returns:
|
||||
A tuple of (auth_token, timestamp) where auth_token is the
|
||||
authorization header value and timestamp is the request timestamp.
|
||||
|
||||
Raises:
|
||||
FileNotFoundError: If the key file doesn't exist
|
||||
ValueError: If the key file is invalid
|
||||
"""
|
||||
timestamp = str(int(time.time()))
|
||||
path_with_ts = f'{path}&ts={timestamp}' if '?' in path else f'{path}?ts={timestamp}'
|
||||
challenge = f'{method},{path_with_ts}'
|
||||
|
||||
private_key = self.load_private_key()
|
||||
signature = private_key.sign(challenge.encode())
|
||||
|
||||
public_key_b64 = self.get_public_key_b64(private_key)
|
||||
|
||||
auth_token = f'{public_key_b64}:{base64.b64encode(signature).decode("utf-8")}'
|
||||
|
||||
return auth_token, timestamp
|
||||
+1019
-331
File diff suppressed because it is too large
Load Diff
+505
-92
@@ -1,41 +1,246 @@
|
||||
import contextlib
|
||||
import json
|
||||
from typing import Any, TypedDict, Sequence, Literal
|
||||
from base64 import b64decode, b64encode
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Mapping, Optional, Sequence, Union
|
||||
|
||||
import sys
|
||||
|
||||
if sys.version_info < (3, 11):
|
||||
from typing_extensions import NotRequired
|
||||
else:
|
||||
from typing import NotRequired
|
||||
from pydantic import (
|
||||
BaseModel,
|
||||
ByteSize,
|
||||
ConfigDict,
|
||||
Field,
|
||||
model_serializer,
|
||||
)
|
||||
from pydantic.json_schema import JsonSchemaValue
|
||||
from typing_extensions import Annotated, Literal
|
||||
|
||||
|
||||
class BaseGenerateResponse(TypedDict):
|
||||
model: str
|
||||
"Model used to generate response."
|
||||
class SubscriptableBaseModel(BaseModel):
|
||||
def __getitem__(self, key: str) -> Any:
|
||||
"""
|
||||
>>> msg = Message(role='user')
|
||||
>>> msg['role']
|
||||
'user'
|
||||
>>> msg = Message(role='user')
|
||||
>>> msg['nonexistent']
|
||||
Traceback (most recent call last):
|
||||
KeyError: 'nonexistent'
|
||||
"""
|
||||
if key in self:
|
||||
return getattr(self, key)
|
||||
|
||||
created_at: str
|
||||
"Time when the request was created."
|
||||
raise KeyError(key)
|
||||
|
||||
done: bool
|
||||
"True if response is complete, otherwise False. Useful for streaming to detect the final response."
|
||||
def __setitem__(self, key: str, value: Any) -> None:
|
||||
"""
|
||||
>>> msg = Message(role='user')
|
||||
>>> msg['role'] = 'assistant'
|
||||
>>> msg['role']
|
||||
'assistant'
|
||||
>>> tool_call = Message.ToolCall(function=Message.ToolCall.Function(name='foo', arguments={}))
|
||||
>>> msg = Message(role='user', content='hello')
|
||||
>>> msg['tool_calls'] = [tool_call]
|
||||
>>> msg['tool_calls'][0]['function']['name']
|
||||
'foo'
|
||||
"""
|
||||
setattr(self, key, value)
|
||||
|
||||
total_duration: int
|
||||
"Total duration in nanoseconds."
|
||||
def __contains__(self, key: str) -> bool:
|
||||
"""
|
||||
>>> msg = Message(role='user')
|
||||
>>> 'nonexistent' in msg
|
||||
False
|
||||
>>> 'role' in msg
|
||||
True
|
||||
>>> 'content' in msg
|
||||
False
|
||||
>>> msg.content = 'hello!'
|
||||
>>> 'content' in msg
|
||||
True
|
||||
>>> msg = Message(role='user', content='hello!')
|
||||
>>> 'content' in msg
|
||||
True
|
||||
>>> 'tool_calls' in msg
|
||||
False
|
||||
>>> msg['tool_calls'] = []
|
||||
>>> 'tool_calls' in msg
|
||||
True
|
||||
>>> msg['tool_calls'] = [Message.ToolCall(function=Message.ToolCall.Function(name='foo', arguments={}))]
|
||||
>>> 'tool_calls' in msg
|
||||
True
|
||||
>>> msg['tool_calls'] = None
|
||||
>>> 'tool_calls' in msg
|
||||
True
|
||||
>>> tool = Tool()
|
||||
>>> 'type' in tool
|
||||
True
|
||||
"""
|
||||
if key in self.model_fields_set:
|
||||
return True
|
||||
|
||||
load_duration: int
|
||||
"Load duration in nanoseconds."
|
||||
if value := self.__class__.model_fields.get(key):
|
||||
return value.default is not None
|
||||
|
||||
prompt_eval_count: int
|
||||
"Number of tokens evaluated in the prompt."
|
||||
return False
|
||||
|
||||
prompt_eval_duration: int
|
||||
"Duration of evaluating the prompt in nanoseconds."
|
||||
def get(self, key: str, default: Any = None) -> Any:
|
||||
"""
|
||||
>>> msg = Message(role='user')
|
||||
>>> msg.get('role')
|
||||
'user'
|
||||
>>> msg = Message(role='user')
|
||||
>>> msg.get('nonexistent')
|
||||
>>> msg = Message(role='user')
|
||||
>>> msg.get('nonexistent', 'default')
|
||||
'default'
|
||||
>>> msg = Message(role='user', tool_calls=[ Message.ToolCall(function=Message.ToolCall.Function(name='foo', arguments={}))])
|
||||
>>> msg.get('tool_calls')[0]['function']['name']
|
||||
'foo'
|
||||
"""
|
||||
return getattr(self, key) if hasattr(self, key) else default
|
||||
|
||||
eval_count: int
|
||||
"Number of tokens evaluated in inference."
|
||||
|
||||
eval_duration: int
|
||||
"Duration of evaluating inference in nanoseconds."
|
||||
class Options(SubscriptableBaseModel):
|
||||
# load time options
|
||||
numa: Optional[bool] = None
|
||||
num_ctx: Optional[int] = None
|
||||
num_batch: Optional[int] = None
|
||||
num_gpu: Optional[int] = None
|
||||
main_gpu: Optional[int] = None
|
||||
low_vram: Optional[bool] = None
|
||||
f16_kv: Optional[bool] = None
|
||||
logits_all: Optional[bool] = None
|
||||
vocab_only: Optional[bool] = None
|
||||
use_mmap: Optional[bool] = None
|
||||
use_mlock: Optional[bool] = None
|
||||
embedding_only: Optional[bool] = None
|
||||
num_thread: Optional[int] = None
|
||||
|
||||
# runtime options
|
||||
num_keep: Optional[int] = None
|
||||
seed: Optional[int] = None
|
||||
num_predict: Optional[int] = None
|
||||
top_k: Optional[int] = None
|
||||
top_p: Optional[float] = None
|
||||
tfs_z: Optional[float] = None
|
||||
typical_p: Optional[float] = None
|
||||
repeat_last_n: Optional[int] = None
|
||||
temperature: Optional[float] = None
|
||||
repeat_penalty: Optional[float] = None
|
||||
presence_penalty: Optional[float] = None
|
||||
frequency_penalty: Optional[float] = None
|
||||
mirostat: Optional[int] = None
|
||||
mirostat_tau: Optional[float] = None
|
||||
mirostat_eta: Optional[float] = None
|
||||
penalize_newline: Optional[bool] = None
|
||||
stop: Optional[Sequence[str]] = None
|
||||
|
||||
|
||||
class BaseRequest(SubscriptableBaseModel):
|
||||
model: Annotated[str, Field(min_length=1)]
|
||||
'Model to use for the request.'
|
||||
|
||||
|
||||
class BaseStreamableRequest(BaseRequest):
|
||||
stream: Optional[bool] = None
|
||||
'Stream response.'
|
||||
|
||||
|
||||
class BaseGenerateRequest(BaseStreamableRequest):
|
||||
options: Optional[Union[Mapping[str, Any], Options]] = None
|
||||
'Options to use for the request.'
|
||||
|
||||
format: Optional[Union[Literal['', 'json'], JsonSchemaValue]] = None
|
||||
'Format of the response.'
|
||||
|
||||
keep_alive: Optional[Union[float, str]] = None
|
||||
'Keep model alive for the specified duration.'
|
||||
|
||||
|
||||
class Image(BaseModel):
|
||||
value: Union[str, bytes, Path]
|
||||
|
||||
@model_serializer
|
||||
def serialize_model(self):
|
||||
if isinstance(self.value, (Path, bytes)):
|
||||
return b64encode(self.value.read_bytes() if isinstance(self.value, Path) else self.value).decode()
|
||||
|
||||
if isinstance(self.value, str):
|
||||
try:
|
||||
if Path(self.value).exists():
|
||||
return b64encode(Path(self.value).read_bytes()).decode()
|
||||
except Exception:
|
||||
# Long base64 string can't be wrapped in Path, so try to treat as base64 string
|
||||
pass
|
||||
|
||||
# String might be a file path, but might not exist
|
||||
if self.value.split('.')[-1] in ('png', 'jpg', 'jpeg', 'webp'):
|
||||
raise ValueError(f'File {self.value} does not exist')
|
||||
|
||||
try:
|
||||
# Try to decode to check if it's already base64
|
||||
b64decode(self.value)
|
||||
return self.value
|
||||
except Exception:
|
||||
raise ValueError('Invalid image data, expected base64 string or path to image file') from Exception
|
||||
|
||||
|
||||
class GenerateRequest(BaseGenerateRequest):
|
||||
prompt: Optional[str] = None
|
||||
'Prompt to generate response from.'
|
||||
|
||||
suffix: Optional[str] = None
|
||||
'Suffix to append to the response.'
|
||||
|
||||
system: Optional[str] = None
|
||||
'System prompt to prepend to the prompt.'
|
||||
|
||||
template: Optional[str] = None
|
||||
'Template to use for the response.'
|
||||
|
||||
context: Optional[Sequence[int]] = None
|
||||
'Tokenized history to use for the response.'
|
||||
|
||||
raw: Optional[bool] = None
|
||||
|
||||
images: Optional[Sequence[Image]] = None
|
||||
'Image data for multimodal models.'
|
||||
|
||||
think: Optional[Union[bool, Literal['low', 'medium', 'high']]] = None
|
||||
'Enable thinking mode (for thinking models).'
|
||||
|
||||
|
||||
class BaseGenerateResponse(SubscriptableBaseModel):
|
||||
model: Optional[str] = None
|
||||
'Model used to generate response.'
|
||||
|
||||
created_at: Optional[str] = None
|
||||
'Time when the request was created.'
|
||||
|
||||
done: Optional[bool] = None
|
||||
'True if response is complete, otherwise False. Useful for streaming to detect the final response.'
|
||||
|
||||
done_reason: Optional[str] = None
|
||||
'Reason for completion. Only present when done is True.'
|
||||
|
||||
total_duration: Optional[int] = None
|
||||
'Total duration in nanoseconds.'
|
||||
|
||||
load_duration: Optional[int] = None
|
||||
'Load duration in nanoseconds.'
|
||||
|
||||
prompt_eval_count: Optional[int] = None
|
||||
'Number of tokens evaluated in the prompt.'
|
||||
|
||||
prompt_eval_duration: Optional[int] = None
|
||||
'Duration of evaluating the prompt in nanoseconds.'
|
||||
|
||||
eval_count: Optional[int] = None
|
||||
'Number of tokens evaluated in inference.'
|
||||
|
||||
eval_duration: Optional[int] = None
|
||||
'Duration of evaluating inference in nanoseconds.'
|
||||
|
||||
|
||||
class GenerateResponse(BaseGenerateResponse):
|
||||
@@ -44,24 +249,30 @@ class GenerateResponse(BaseGenerateResponse):
|
||||
"""
|
||||
|
||||
response: str
|
||||
"Response content. When streaming, this contains a fragment of the response."
|
||||
'Response content. When streaming, this contains a fragment of the response.'
|
||||
|
||||
context: Sequence[int]
|
||||
"Tokenized history up to the point of the response."
|
||||
thinking: Optional[str] = None
|
||||
'Thinking content. Only present when thinking is enabled.'
|
||||
|
||||
context: Optional[Sequence[int]] = None
|
||||
'Tokenized history up to the point of the response.'
|
||||
|
||||
|
||||
class Message(TypedDict):
|
||||
class Message(SubscriptableBaseModel):
|
||||
"""
|
||||
Chat message.
|
||||
"""
|
||||
|
||||
role: Literal['user', 'assistant', 'system']
|
||||
"Assumed role of the message. Response messages always has role 'assistant'."
|
||||
role: str
|
||||
"Assumed role of the message. Response messages has role 'assistant' or 'tool'."
|
||||
|
||||
content: str
|
||||
"Content of the message. Response messages contains message fragments when streaming."
|
||||
content: Optional[str] = None
|
||||
'Content of the message. Response messages contains message fragments when streaming.'
|
||||
|
||||
images: NotRequired[Sequence[Any]]
|
||||
thinking: Optional[str] = None
|
||||
'Thinking content. Only present when thinking is enabled.'
|
||||
|
||||
images: Optional[Sequence[Image]] = None
|
||||
"""
|
||||
Optional list of image data for multimodal models.
|
||||
|
||||
@@ -73,6 +284,82 @@ class Message(TypedDict):
|
||||
Valid image formats depend on the model. See the model card for more information.
|
||||
"""
|
||||
|
||||
tool_name: Optional[str] = None
|
||||
'Name of the executed tool.'
|
||||
|
||||
class ToolCall(SubscriptableBaseModel):
|
||||
"""
|
||||
Model tool calls.
|
||||
"""
|
||||
|
||||
class Function(SubscriptableBaseModel):
|
||||
"""
|
||||
Tool call function.
|
||||
"""
|
||||
|
||||
name: str
|
||||
'Name of the function.'
|
||||
|
||||
arguments: Mapping[str, Any]
|
||||
'Arguments of the function.'
|
||||
|
||||
function: Function
|
||||
'Function to be called.'
|
||||
|
||||
tool_calls: Optional[Sequence[ToolCall]] = None
|
||||
"""
|
||||
Tools calls to be made by the model.
|
||||
"""
|
||||
|
||||
|
||||
class Tool(SubscriptableBaseModel):
|
||||
type: Optional[str] = 'function'
|
||||
|
||||
class Function(SubscriptableBaseModel):
|
||||
name: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
|
||||
class Parameters(SubscriptableBaseModel):
|
||||
model_config = ConfigDict(populate_by_name=True)
|
||||
type: Optional[Literal['object']] = 'object'
|
||||
defs: Optional[Any] = Field(None, alias='$defs')
|
||||
items: Optional[Any] = None
|
||||
required: Optional[Sequence[str]] = None
|
||||
|
||||
class Property(SubscriptableBaseModel):
|
||||
model_config = ConfigDict(arbitrary_types_allowed=True)
|
||||
|
||||
type: Optional[Union[str, Sequence[str]]] = None
|
||||
items: Optional[Any] = None
|
||||
description: Optional[str] = None
|
||||
enum: Optional[Sequence[Any]] = None
|
||||
|
||||
properties: Optional[Mapping[str, Property]] = None
|
||||
|
||||
parameters: Optional[Parameters] = None
|
||||
|
||||
function: Optional[Function] = None
|
||||
|
||||
|
||||
class ChatRequest(BaseGenerateRequest):
|
||||
@model_serializer(mode='wrap')
|
||||
def serialize_model(self, nxt):
|
||||
output = nxt(self)
|
||||
if output.get('tools'):
|
||||
for tool in output['tools']:
|
||||
if 'function' in tool and 'parameters' in tool['function'] and 'defs' in tool['function']['parameters']:
|
||||
tool['function']['parameters']['$defs'] = tool['function']['parameters'].pop('defs')
|
||||
return output
|
||||
|
||||
messages: Optional[Sequence[Union[Mapping[str, Any], Message]]] = None
|
||||
'Messages to chat with.'
|
||||
|
||||
tools: Optional[Sequence[Tool]] = None
|
||||
'Tools to use for the chat.'
|
||||
|
||||
think: Optional[Union[bool, Literal['low', 'medium', 'high']]] = None
|
||||
'Enable thinking mode (for thinking models).'
|
||||
|
||||
|
||||
class ChatResponse(BaseGenerateResponse):
|
||||
"""
|
||||
@@ -80,53 +367,178 @@ class ChatResponse(BaseGenerateResponse):
|
||||
"""
|
||||
|
||||
message: Message
|
||||
"Response message."
|
||||
'Response message.'
|
||||
|
||||
|
||||
class ProgressResponse(TypedDict):
|
||||
status: str
|
||||
completed: int
|
||||
total: int
|
||||
digest: str
|
||||
class EmbedRequest(BaseRequest):
|
||||
input: Union[str, Sequence[str]]
|
||||
'Input text to embed.'
|
||||
|
||||
truncate: Optional[bool] = None
|
||||
'Truncate the input to the maximum token length.'
|
||||
|
||||
options: Optional[Union[Mapping[str, Any], Options]] = None
|
||||
'Options to use for the request.'
|
||||
|
||||
keep_alive: Optional[Union[float, str]] = None
|
||||
|
||||
dimensions: Optional[int] = None
|
||||
'Dimensions truncates the output embedding to the specified dimension.'
|
||||
|
||||
|
||||
class Options(TypedDict, total=False):
|
||||
# load time options
|
||||
numa: bool
|
||||
num_ctx: int
|
||||
num_batch: int
|
||||
num_gqa: int
|
||||
num_gpu: int
|
||||
main_gpu: int
|
||||
low_vram: bool
|
||||
f16_kv: bool
|
||||
logits_all: bool
|
||||
vocab_only: bool
|
||||
use_mmap: bool
|
||||
use_mlock: bool
|
||||
embedding_only: bool
|
||||
rope_frequency_base: float
|
||||
rope_frequency_scale: float
|
||||
num_thread: int
|
||||
class EmbedResponse(BaseGenerateResponse):
|
||||
"""
|
||||
Response returned by embed requests.
|
||||
"""
|
||||
|
||||
# runtime options
|
||||
num_keep: int
|
||||
seed: int
|
||||
num_predict: int
|
||||
top_k: int
|
||||
top_p: float
|
||||
tfs_z: float
|
||||
typical_p: float
|
||||
repeat_last_n: int
|
||||
temperature: float
|
||||
repeat_penalty: float
|
||||
presence_penalty: float
|
||||
frequency_penalty: float
|
||||
mirostat: int
|
||||
mirostat_tau: float
|
||||
mirostat_eta: float
|
||||
penalize_newline: bool
|
||||
stop: Sequence[str]
|
||||
embeddings: Sequence[Sequence[float]]
|
||||
'Embeddings of the inputs.'
|
||||
|
||||
|
||||
class EmbeddingsRequest(BaseRequest):
|
||||
prompt: Optional[str] = None
|
||||
'Prompt to generate embeddings from.'
|
||||
|
||||
options: Optional[Union[Mapping[str, Any], Options]] = None
|
||||
'Options to use for the request.'
|
||||
|
||||
keep_alive: Optional[Union[float, str]] = None
|
||||
|
||||
|
||||
class EmbeddingsResponse(SubscriptableBaseModel):
|
||||
"""
|
||||
Response returned by embeddings requests.
|
||||
"""
|
||||
|
||||
embedding: Sequence[float]
|
||||
'Embedding of the prompt.'
|
||||
|
||||
|
||||
class PullRequest(BaseStreamableRequest):
|
||||
"""
|
||||
Request to pull the model.
|
||||
"""
|
||||
|
||||
insecure: Optional[bool] = None
|
||||
'Allow insecure (HTTP) connections.'
|
||||
|
||||
|
||||
class PushRequest(BaseStreamableRequest):
|
||||
"""
|
||||
Request to pull the model.
|
||||
"""
|
||||
|
||||
insecure: Optional[bool] = None
|
||||
'Allow insecure (HTTP) connections.'
|
||||
|
||||
|
||||
class CreateRequest(BaseStreamableRequest):
|
||||
@model_serializer(mode='wrap')
|
||||
def serialize_model(self, nxt):
|
||||
output = nxt(self)
|
||||
if 'from_' in output:
|
||||
output['from'] = output.pop('from_')
|
||||
return output
|
||||
|
||||
"""
|
||||
Request to create a new model.
|
||||
"""
|
||||
quantize: Optional[str] = None
|
||||
from_: Optional[str] = None
|
||||
files: Optional[Dict[str, str]] = None
|
||||
adapters: Optional[Dict[str, str]] = None
|
||||
template: Optional[str] = None
|
||||
license: Optional[Union[str, List[str]]] = None
|
||||
system: Optional[str] = None
|
||||
parameters: Optional[Union[Mapping[str, Any], Options]] = None
|
||||
messages: Optional[Sequence[Union[Mapping[str, Any], Message]]] = None
|
||||
|
||||
|
||||
class ModelDetails(SubscriptableBaseModel):
|
||||
parent_model: Optional[str] = None
|
||||
format: Optional[str] = None
|
||||
family: Optional[str] = None
|
||||
families: Optional[Sequence[str]] = None
|
||||
parameter_size: Optional[str] = None
|
||||
quantization_level: Optional[str] = None
|
||||
|
||||
|
||||
class ListResponse(SubscriptableBaseModel):
|
||||
class Model(SubscriptableBaseModel):
|
||||
model: Optional[str] = None
|
||||
modified_at: Optional[datetime] = None
|
||||
digest: Optional[str] = None
|
||||
size: Optional[ByteSize] = None
|
||||
details: Optional[ModelDetails] = None
|
||||
|
||||
models: Sequence[Model]
|
||||
'List of models.'
|
||||
|
||||
|
||||
class DeleteRequest(BaseRequest):
|
||||
"""
|
||||
Request to delete a model.
|
||||
"""
|
||||
|
||||
|
||||
class CopyRequest(BaseModel):
|
||||
"""
|
||||
Request to copy a model.
|
||||
"""
|
||||
|
||||
source: str
|
||||
'Source model to copy.'
|
||||
|
||||
destination: str
|
||||
'Destination model to copy to.'
|
||||
|
||||
|
||||
class StatusResponse(SubscriptableBaseModel):
|
||||
status: Optional[str] = None
|
||||
|
||||
|
||||
class ProgressResponse(StatusResponse):
|
||||
completed: Optional[int] = None
|
||||
total: Optional[int] = None
|
||||
digest: Optional[str] = None
|
||||
|
||||
|
||||
class ShowRequest(BaseRequest):
|
||||
"""
|
||||
Request to show model information.
|
||||
"""
|
||||
|
||||
|
||||
class ShowResponse(SubscriptableBaseModel):
|
||||
modified_at: Optional[datetime] = None
|
||||
|
||||
template: Optional[str] = None
|
||||
|
||||
modelfile: Optional[str] = None
|
||||
|
||||
license: Optional[str] = None
|
||||
|
||||
details: Optional[ModelDetails] = None
|
||||
|
||||
modelinfo: Optional[Mapping[str, Any]] = Field(alias='model_info')
|
||||
|
||||
parameters: Optional[str] = None
|
||||
|
||||
capabilities: Optional[List[str]] = None
|
||||
|
||||
|
||||
class ProcessResponse(SubscriptableBaseModel):
|
||||
class Model(SubscriptableBaseModel):
|
||||
model: Optional[str] = None
|
||||
name: Optional[str] = None
|
||||
digest: Optional[str] = None
|
||||
expires_at: Optional[datetime] = None
|
||||
size: Optional[ByteSize] = None
|
||||
size_vram: Optional[ByteSize] = None
|
||||
details: Optional[ModelDetails] = None
|
||||
context_length: Optional[int] = None
|
||||
|
||||
models: Sequence[Model]
|
||||
|
||||
|
||||
class RequestError(Exception):
|
||||
@@ -134,10 +546,10 @@ class RequestError(Exception):
|
||||
Common class for request errors.
|
||||
"""
|
||||
|
||||
def __init__(self, content: str):
|
||||
super().__init__(content)
|
||||
self.content = content
|
||||
"Reason for the error."
|
||||
def __init__(self, error: str):
|
||||
super().__init__(error)
|
||||
self.error = error
|
||||
'Reason for the error.'
|
||||
|
||||
|
||||
class ResponseError(Exception):
|
||||
@@ -145,17 +557,18 @@ class ResponseError(Exception):
|
||||
Common class for response errors.
|
||||
"""
|
||||
|
||||
def __init__(self, content: str, status_code: int = -1):
|
||||
try:
|
||||
# try to parse content as JSON and extract 'error'
|
||||
# fallback to raw content if JSON parsing fails
|
||||
content = json.loads(content).get('error', content)
|
||||
except json.JSONDecodeError:
|
||||
...
|
||||
def __init__(self, error: str, status_code: int = -1):
|
||||
# try to parse content as JSON and extract 'error'
|
||||
# fallback to raw content if JSON parsing fails
|
||||
with contextlib.suppress(json.JSONDecodeError):
|
||||
error = json.loads(error).get('error', error)
|
||||
|
||||
super().__init__(content)
|
||||
self.content = content
|
||||
"Reason for the error."
|
||||
super().__init__(error)
|
||||
self.error = error
|
||||
'Reason for the error.'
|
||||
|
||||
self.status_code = status_code
|
||||
"HTTP status code of the response."
|
||||
'HTTP status code of the response.'
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f'{self.error} (status code: {self.status_code})'
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import inspect
|
||||
import re
|
||||
from collections import defaultdict
|
||||
from typing import Callable, Union
|
||||
|
||||
import pydantic
|
||||
|
||||
from ollama._types import Tool
|
||||
|
||||
|
||||
def _parse_docstring(doc_string: Union[str, None]) -> dict[str, str]:
|
||||
parsed_docstring = defaultdict(str)
|
||||
if not doc_string:
|
||||
return parsed_docstring
|
||||
|
||||
key = str(hash(doc_string))
|
||||
for line in doc_string.splitlines():
|
||||
lowered_line = line.lower().strip()
|
||||
if lowered_line.startswith('args:'):
|
||||
key = 'args'
|
||||
elif lowered_line.startswith(('returns:', 'yields:', 'raises:')):
|
||||
key = '_'
|
||||
|
||||
else:
|
||||
# maybe change to a list and join later
|
||||
parsed_docstring[key] += f'{line.strip()}\n'
|
||||
|
||||
last_key = None
|
||||
for line in parsed_docstring['args'].splitlines():
|
||||
line = line.strip()
|
||||
if ':' in line:
|
||||
# Split the line on either:
|
||||
# 1. A parenthetical expression like (integer) - captured in group 1
|
||||
# 2. A colon :
|
||||
# Followed by optional whitespace. Only split on first occurrence.
|
||||
parts = re.split(r'(?:\(([^)]*)\)|:)\s*', line, maxsplit=1)
|
||||
|
||||
arg_name = parts[0].strip()
|
||||
last_key = arg_name
|
||||
|
||||
# Get the description - will be in parts[1] if parenthetical or parts[-1] if after colon
|
||||
arg_description = parts[-1].strip()
|
||||
if len(parts) > 2 and parts[1]: # Has parenthetical content
|
||||
arg_description = parts[-1].split(':', 1)[-1].strip()
|
||||
|
||||
parsed_docstring[last_key] = arg_description
|
||||
|
||||
elif last_key and line:
|
||||
parsed_docstring[last_key] += ' ' + line
|
||||
|
||||
return parsed_docstring
|
||||
|
||||
|
||||
def convert_function_to_tool(func: Callable) -> Tool:
|
||||
doc_string_hash = str(hash(inspect.getdoc(func)))
|
||||
parsed_docstring = _parse_docstring(inspect.getdoc(func))
|
||||
schema = type(
|
||||
func.__name__,
|
||||
(pydantic.BaseModel,),
|
||||
{
|
||||
'__annotations__': {k: v.annotation if v.annotation != inspect._empty else str for k, v in inspect.signature(func).parameters.items()},
|
||||
'__signature__': inspect.signature(func),
|
||||
'__doc__': parsed_docstring[doc_string_hash],
|
||||
},
|
||||
).model_json_schema()
|
||||
|
||||
for k, v in schema.get('properties', {}).items():
|
||||
# If type is missing, the default is string
|
||||
types = {t.get('type', 'string') for t in v.get('anyOf')} if 'anyOf' in v else {v.get('type', 'string')}
|
||||
if 'null' in types:
|
||||
schema['required'].remove(k)
|
||||
types.discard('null')
|
||||
|
||||
schema['properties'][k] = {
|
||||
'description': parsed_docstring[k],
|
||||
'type': ', '.join(types),
|
||||
}
|
||||
|
||||
tool = Tool(
|
||||
type='function',
|
||||
function=Tool.Function(
|
||||
name=func.__name__,
|
||||
description=schema.get('description', ''),
|
||||
parameters=Tool.Function.Parameters(**schema),
|
||||
),
|
||||
)
|
||||
|
||||
return Tool.model_validate(tool)
|
||||
Generated
-519
@@ -1,519 +0,0 @@
|
||||
# This file is automatically @generated by Poetry 1.7.0 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "anyio"
|
||||
version = "4.2.0"
|
||||
description = "High level compatibility layer for multiple asynchronous event loop implementations"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "anyio-4.2.0-py3-none-any.whl", hash = "sha256:745843b39e829e108e518c489b31dc757de7d2131d53fac32bd8df268227bfee"},
|
||||
{file = "anyio-4.2.0.tar.gz", hash = "sha256:e1875bb4b4e2de1669f4bc7869b6d3f54231cdced71605e6e64c9be77e3be50f"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""}
|
||||
idna = ">=2.8"
|
||||
sniffio = ">=1.1"
|
||||
typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""}
|
||||
|
||||
[package.extras]
|
||||
doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"]
|
||||
test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"]
|
||||
trio = ["trio (>=0.23)"]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2023.11.17"
|
||||
description = "Python package for providing Mozilla's CA Bundle."
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "certifi-2023.11.17-py3-none-any.whl", hash = "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474"},
|
||||
{file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.6"
|
||||
description = "Cross-platform colored terminal text."
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
|
||||
files = [
|
||||
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
|
||||
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "coverage"
|
||||
version = "7.3.4"
|
||||
description = "Code coverage measurement for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "coverage-7.3.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:aff2bd3d585969cc4486bfc69655e862028b689404563e6b549e6a8244f226df"},
|
||||
{file = "coverage-7.3.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4353923f38d752ecfbd3f1f20bf7a3546993ae5ecd7c07fd2f25d40b4e54571"},
|
||||
{file = "coverage-7.3.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea473c37872f0159294f7073f3fa72f68b03a129799f3533b2bb44d5e9fa4f82"},
|
||||
{file = "coverage-7.3.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5214362abf26e254d749fc0c18af4c57b532a4bfde1a057565616dd3b8d7cc94"},
|
||||
{file = "coverage-7.3.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f99b7d3f7a7adfa3d11e3a48d1a91bb65739555dd6a0d3fa68aa5852d962e5b1"},
|
||||
{file = "coverage-7.3.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:74397a1263275bea9d736572d4cf338efaade2de9ff759f9c26bcdceb383bb49"},
|
||||
{file = "coverage-7.3.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:f154bd866318185ef5865ace5be3ac047b6d1cc0aeecf53bf83fe846f4384d5d"},
|
||||
{file = "coverage-7.3.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e0d84099ea7cba9ff467f9c6f747e3fc3906e2aadac1ce7b41add72e8d0a3712"},
|
||||
{file = "coverage-7.3.4-cp310-cp310-win32.whl", hash = "sha256:3f477fb8a56e0c603587b8278d9dbd32e54bcc2922d62405f65574bd76eba78a"},
|
||||
{file = "coverage-7.3.4-cp310-cp310-win_amd64.whl", hash = "sha256:c75738ce13d257efbb6633a049fb2ed8e87e2e6c2e906c52d1093a4d08d67c6b"},
|
||||
{file = "coverage-7.3.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:997aa14b3e014339d8101b9886063c5d06238848905d9ad6c6eabe533440a9a7"},
|
||||
{file = "coverage-7.3.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8a9c5bc5db3eb4cd55ecb8397d8e9b70247904f8eca718cc53c12dcc98e59fc8"},
|
||||
{file = "coverage-7.3.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27ee94f088397d1feea3cb524e4313ff0410ead7d968029ecc4bc5a7e1d34fbf"},
|
||||
{file = "coverage-7.3.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ce03e25e18dd9bf44723e83bc202114817f3367789052dc9e5b5c79f40cf59d"},
|
||||
{file = "coverage-7.3.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85072e99474d894e5df582faec04abe137b28972d5e466999bc64fc37f564a03"},
|
||||
{file = "coverage-7.3.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a877810ef918d0d345b783fc569608804f3ed2507bf32f14f652e4eaf5d8f8d0"},
|
||||
{file = "coverage-7.3.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:9ac17b94ab4ca66cf803f2b22d47e392f0977f9da838bf71d1f0db6c32893cb9"},
|
||||
{file = "coverage-7.3.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:36d75ef2acab74dc948d0b537ef021306796da551e8ac8b467810911000af66a"},
|
||||
{file = "coverage-7.3.4-cp311-cp311-win32.whl", hash = "sha256:47ee56c2cd445ea35a8cc3ad5c8134cb9bece3a5cb50bb8265514208d0a65928"},
|
||||
{file = "coverage-7.3.4-cp311-cp311-win_amd64.whl", hash = "sha256:11ab62d0ce5d9324915726f611f511a761efcca970bd49d876cf831b4de65be5"},
|
||||
{file = "coverage-7.3.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:33e63c578f4acce1b6cd292a66bc30164495010f1091d4b7529d014845cd9bee"},
|
||||
{file = "coverage-7.3.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:782693b817218169bfeb9b9ba7f4a9f242764e180ac9589b45112571f32a0ba6"},
|
||||
{file = "coverage-7.3.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c4277ddaad9293454da19121c59f2d850f16bcb27f71f89a5c4836906eb35ef"},
|
||||
{file = "coverage-7.3.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3d892a19ae24b9801771a5a989fb3e850bd1ad2e2b6e83e949c65e8f37bc67a1"},
|
||||
{file = "coverage-7.3.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3024ec1b3a221bd10b5d87337d0373c2bcaf7afd86d42081afe39b3e1820323b"},
|
||||
{file = "coverage-7.3.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a1c3e9d2bbd6f3f79cfecd6f20854f4dc0c6e0ec317df2b265266d0dc06535f1"},
|
||||
{file = "coverage-7.3.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:e91029d7f151d8bf5ab7d8bfe2c3dbefd239759d642b211a677bc0709c9fdb96"},
|
||||
{file = "coverage-7.3.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:6879fe41c60080aa4bb59703a526c54e0412b77e649a0d06a61782ecf0853ee1"},
|
||||
{file = "coverage-7.3.4-cp312-cp312-win32.whl", hash = "sha256:fd2f8a641f8f193968afdc8fd1697e602e199931012b574194052d132a79be13"},
|
||||
{file = "coverage-7.3.4-cp312-cp312-win_amd64.whl", hash = "sha256:d1d0ce6c6947a3a4aa5479bebceff2c807b9f3b529b637e2b33dea4468d75fc7"},
|
||||
{file = "coverage-7.3.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:36797b3625d1da885b369bdaaa3b0d9fb8865caed3c2b8230afaa6005434aa2f"},
|
||||
{file = "coverage-7.3.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bfed0ec4b419fbc807dec417c401499ea869436910e1ca524cfb4f81cf3f60e7"},
|
||||
{file = "coverage-7.3.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f97ff5a9fc2ca47f3383482858dd2cb8ddbf7514427eecf5aa5f7992d0571429"},
|
||||
{file = "coverage-7.3.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:607b6c6b35aa49defaebf4526729bd5238bc36fe3ef1a417d9839e1d96ee1e4c"},
|
||||
{file = "coverage-7.3.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8e258dcc335055ab59fe79f1dec217d9fb0cdace103d6b5c6df6b75915e7959"},
|
||||
{file = "coverage-7.3.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a02ac7c51819702b384fea5ee033a7c202f732a2a2f1fe6c41e3d4019828c8d3"},
|
||||
{file = "coverage-7.3.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b710869a15b8caf02e31d16487a931dbe78335462a122c8603bb9bd401ff6fb2"},
|
||||
{file = "coverage-7.3.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c6a23ae9348a7a92e7f750f9b7e828448e428e99c24616dec93a0720342f241d"},
|
||||
{file = "coverage-7.3.4-cp38-cp38-win32.whl", hash = "sha256:758ebaf74578b73f727acc4e8ab4b16ab6f22a5ffd7dd254e5946aba42a4ce76"},
|
||||
{file = "coverage-7.3.4-cp38-cp38-win_amd64.whl", hash = "sha256:309ed6a559bc942b7cc721f2976326efbfe81fc2b8f601c722bff927328507dc"},
|
||||
{file = "coverage-7.3.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:aefbb29dc56317a4fcb2f3857d5bce9b881038ed7e5aa5d3bcab25bd23f57328"},
|
||||
{file = "coverage-7.3.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:183c16173a70caf92e2dfcfe7c7a576de6fa9edc4119b8e13f91db7ca33a7923"},
|
||||
{file = "coverage-7.3.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a4184dcbe4f98d86470273e758f1d24191ca095412e4335ff27b417291f5964"},
|
||||
{file = "coverage-7.3.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93698ac0995516ccdca55342599a1463ed2e2d8942316da31686d4d614597ef9"},
|
||||
{file = "coverage-7.3.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb220b3596358a86361139edce40d97da7458412d412e1e10c8e1970ee8c09ab"},
|
||||
{file = "coverage-7.3.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d5b14abde6f8d969e6b9dd8c7a013d9a2b52af1235fe7bebef25ad5c8f47fa18"},
|
||||
{file = "coverage-7.3.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:610afaf929dc0e09a5eef6981edb6a57a46b7eceff151947b836d869d6d567c1"},
|
||||
{file = "coverage-7.3.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d6ed790728fb71e6b8247bd28e77e99d0c276dff952389b5388169b8ca7b1c28"},
|
||||
{file = "coverage-7.3.4-cp39-cp39-win32.whl", hash = "sha256:c15fdfb141fcf6a900e68bfa35689e1256a670db32b96e7a931cab4a0e1600e5"},
|
||||
{file = "coverage-7.3.4-cp39-cp39-win_amd64.whl", hash = "sha256:38d0b307c4d99a7aca4e00cad4311b7c51b7ac38fb7dea2abe0d182dd4008e05"},
|
||||
{file = "coverage-7.3.4-pp38.pp39.pp310-none-any.whl", hash = "sha256:b1e0f25ae99cf247abfb3f0fac7ae25739e4cd96bf1afa3537827c576b4847e5"},
|
||||
{file = "coverage-7.3.4.tar.gz", hash = "sha256:020d56d2da5bc22a0e00a5b0d54597ee91ad72446fa4cf1b97c35022f6b6dbf0"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""}
|
||||
|
||||
[package.extras]
|
||||
toml = ["tomli"]
|
||||
|
||||
[[package]]
|
||||
name = "exceptiongroup"
|
||||
version = "1.2.0"
|
||||
description = "Backport of PEP 654 (exception groups)"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"},
|
||||
{file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
test = ["pytest (>=6)"]
|
||||
|
||||
[[package]]
|
||||
name = "h11"
|
||||
version = "0.14.0"
|
||||
description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"},
|
||||
{file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httpcore"
|
||||
version = "1.0.2"
|
||||
description = "A minimal low-level HTTP client."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "httpcore-1.0.2-py3-none-any.whl", hash = "sha256:096cc05bca73b8e459a1fc3dcf585148f63e534eae4339559c9b8a8d6399acc7"},
|
||||
{file = "httpcore-1.0.2.tar.gz", hash = "sha256:9fc092e4799b26174648e54b74ed5f683132a464e95643b226e00c2ed2fa6535"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
certifi = "*"
|
||||
h11 = ">=0.13,<0.15"
|
||||
|
||||
[package.extras]
|
||||
asyncio = ["anyio (>=4.0,<5.0)"]
|
||||
http2 = ["h2 (>=3,<5)"]
|
||||
socks = ["socksio (==1.*)"]
|
||||
trio = ["trio (>=0.22.0,<0.23.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "httpx"
|
||||
version = "0.25.2"
|
||||
description = "The next generation HTTP client."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "httpx-0.25.2-py3-none-any.whl", hash = "sha256:a05d3d052d9b2dfce0e3896636467f8a5342fb2b902c819428e1ac65413ca118"},
|
||||
{file = "httpx-0.25.2.tar.gz", hash = "sha256:8b8fcaa0c8ea7b05edd69a094e63a2094c4efcb48129fb757361bc423c0ad9e8"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
anyio = "*"
|
||||
certifi = "*"
|
||||
httpcore = "==1.*"
|
||||
idna = "*"
|
||||
sniffio = "*"
|
||||
|
||||
[package.extras]
|
||||
brotli = ["brotli", "brotlicffi"]
|
||||
cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"]
|
||||
http2 = ["h2 (>=3,<5)"]
|
||||
socks = ["socksio (==1.*)"]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.6"
|
||||
description = "Internationalized Domain Names in Applications (IDNA)"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
files = [
|
||||
{file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"},
|
||||
{file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iniconfig"
|
||||
version = "2.0.0"
|
||||
description = "brain-dead simple config-ini parsing"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
|
||||
{file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "markupsafe"
|
||||
version = "2.1.3"
|
||||
description = "Safely add untrusted strings to HTML/XML markup."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"},
|
||||
{file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"},
|
||||
{file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"},
|
||||
{file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"},
|
||||
{file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"},
|
||||
{file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"},
|
||||
{file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"},
|
||||
{file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"},
|
||||
{file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"},
|
||||
{file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"},
|
||||
{file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"},
|
||||
{file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"},
|
||||
{file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"},
|
||||
{file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"},
|
||||
{file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"},
|
||||
{file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"},
|
||||
{file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"},
|
||||
{file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"},
|
||||
{file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"},
|
||||
{file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"},
|
||||
{file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"},
|
||||
{file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"},
|
||||
{file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"},
|
||||
{file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"},
|
||||
{file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"},
|
||||
{file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"},
|
||||
{file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"},
|
||||
{file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"},
|
||||
{file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"},
|
||||
{file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"},
|
||||
{file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"},
|
||||
{file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"},
|
||||
{file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"},
|
||||
{file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"},
|
||||
{file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"},
|
||||
{file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"},
|
||||
{file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"},
|
||||
{file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"},
|
||||
{file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"},
|
||||
{file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"},
|
||||
{file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"},
|
||||
{file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"},
|
||||
{file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"},
|
||||
{file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"},
|
||||
{file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"},
|
||||
{file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"},
|
||||
{file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"},
|
||||
{file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"},
|
||||
{file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"},
|
||||
{file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"},
|
||||
{file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"},
|
||||
{file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"},
|
||||
{file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"},
|
||||
{file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"},
|
||||
{file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"},
|
||||
{file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"},
|
||||
{file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"},
|
||||
{file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"},
|
||||
{file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"},
|
||||
{file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "23.2"
|
||||
description = "Core utilities for Python packages"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"},
|
||||
{file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pillow"
|
||||
version = "10.1.0"
|
||||
description = "Python Imaging Library (Fork)"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "Pillow-10.1.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1ab05f3db77e98f93964697c8efc49c7954b08dd61cff526b7f2531a22410106"},
|
||||
{file = "Pillow-10.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6932a7652464746fcb484f7fc3618e6503d2066d853f68a4bd97193a3996e273"},
|
||||
{file = "Pillow-10.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f63b5a68daedc54c7c3464508d8c12075e56dcfbd42f8c1bf40169061ae666"},
|
||||
{file = "Pillow-10.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0949b55eb607898e28eaccb525ab104b2d86542a85c74baf3a6dc24002edec2"},
|
||||
{file = "Pillow-10.1.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:ae88931f93214777c7a3aa0a8f92a683f83ecde27f65a45f95f22d289a69e593"},
|
||||
{file = "Pillow-10.1.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:b0eb01ca85b2361b09480784a7931fc648ed8b7836f01fb9241141b968feb1db"},
|
||||
{file = "Pillow-10.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d27b5997bdd2eb9fb199982bb7eb6164db0426904020dc38c10203187ae2ff2f"},
|
||||
{file = "Pillow-10.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7df5608bc38bd37ef585ae9c38c9cd46d7c81498f086915b0f97255ea60c2818"},
|
||||
{file = "Pillow-10.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:41f67248d92a5e0a2076d3517d8d4b1e41a97e2df10eb8f93106c89107f38b57"},
|
||||
{file = "Pillow-10.1.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1fb29c07478e6c06a46b867e43b0bcdb241b44cc52be9bc25ce5944eed4648e7"},
|
||||
{file = "Pillow-10.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2cdc65a46e74514ce742c2013cd4a2d12e8553e3a2563c64879f7c7e4d28bce7"},
|
||||
{file = "Pillow-10.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50d08cd0a2ecd2a8657bd3d82c71efd5a58edb04d9308185d66c3a5a5bed9610"},
|
||||
{file = "Pillow-10.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:062a1610e3bc258bff2328ec43f34244fcec972ee0717200cb1425214fe5b839"},
|
||||
{file = "Pillow-10.1.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:61f1a9d247317fa08a308daaa8ee7b3f760ab1809ca2da14ecc88ae4257d6172"},
|
||||
{file = "Pillow-10.1.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a646e48de237d860c36e0db37ecaecaa3619e6f3e9d5319e527ccbc8151df061"},
|
||||
{file = "Pillow-10.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:47e5bf85b80abc03be7455c95b6d6e4896a62f6541c1f2ce77a7d2bb832af262"},
|
||||
{file = "Pillow-10.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a92386125e9ee90381c3369f57a2a50fa9e6aa8b1cf1d9c4b200d41a7dd8e992"},
|
||||
{file = "Pillow-10.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:0f7c276c05a9767e877a0b4c5050c8bee6a6d960d7f0c11ebda6b99746068c2a"},
|
||||
{file = "Pillow-10.1.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:a89b8312d51715b510a4fe9fc13686283f376cfd5abca8cd1c65e4c76e21081b"},
|
||||
{file = "Pillow-10.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:00f438bb841382b15d7deb9a05cc946ee0f2c352653c7aa659e75e592f6fa17d"},
|
||||
{file = "Pillow-10.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d929a19f5469b3f4df33a3df2983db070ebb2088a1e145e18facbc28cae5b27"},
|
||||
{file = "Pillow-10.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a92109192b360634a4489c0c756364c0c3a2992906752165ecb50544c251312"},
|
||||
{file = "Pillow-10.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:0248f86b3ea061e67817c47ecbe82c23f9dd5d5226200eb9090b3873d3ca32de"},
|
||||
{file = "Pillow-10.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:9882a7451c680c12f232a422730f986a1fcd808da0fd428f08b671237237d651"},
|
||||
{file = "Pillow-10.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1c3ac5423c8c1da5928aa12c6e258921956757d976405e9467c5f39d1d577a4b"},
|
||||
{file = "Pillow-10.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:806abdd8249ba3953c33742506fe414880bad78ac25cc9a9b1c6ae97bedd573f"},
|
||||
{file = "Pillow-10.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:eaed6977fa73408b7b8a24e8b14e59e1668cfc0f4c40193ea7ced8e210adf996"},
|
||||
{file = "Pillow-10.1.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:fe1e26e1ffc38be097f0ba1d0d07fcade2bcfd1d023cda5b29935ae8052bd793"},
|
||||
{file = "Pillow-10.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7a7e3daa202beb61821c06d2517428e8e7c1aab08943e92ec9e5755c2fc9ba5e"},
|
||||
{file = "Pillow-10.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24fadc71218ad2b8ffe437b54876c9382b4a29e030a05a9879f615091f42ffc2"},
|
||||
{file = "Pillow-10.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa1d323703cfdac2036af05191b969b910d8f115cf53093125e4058f62012c9a"},
|
||||
{file = "Pillow-10.1.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:912e3812a1dbbc834da2b32299b124b5ddcb664ed354916fd1ed6f193f0e2d01"},
|
||||
{file = "Pillow-10.1.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:7dbaa3c7de82ef37e7708521be41db5565004258ca76945ad74a8e998c30af8d"},
|
||||
{file = "Pillow-10.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9d7bc666bd8c5a4225e7ac71f2f9d12466ec555e89092728ea0f5c0c2422ea80"},
|
||||
{file = "Pillow-10.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:baada14941c83079bf84c037e2d8b7506ce201e92e3d2fa0d1303507a8538212"},
|
||||
{file = "Pillow-10.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:2ef6721c97894a7aa77723740a09547197533146fba8355e86d6d9a4a1056b14"},
|
||||
{file = "Pillow-10.1.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0a026c188be3b443916179f5d04548092e253beb0c3e2ee0a4e2cdad72f66099"},
|
||||
{file = "Pillow-10.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:04f6f6149f266a100374ca3cc368b67fb27c4af9f1cc8cb6306d849dcdf12616"},
|
||||
{file = "Pillow-10.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb40c011447712d2e19cc261c82655f75f32cb724788df315ed992a4d65696bb"},
|
||||
{file = "Pillow-10.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a8413794b4ad9719346cd9306118450b7b00d9a15846451549314a58ac42219"},
|
||||
{file = "Pillow-10.1.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:c9aeea7b63edb7884b031a35305629a7593272b54f429a9869a4f63a1bf04c34"},
|
||||
{file = "Pillow-10.1.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b4005fee46ed9be0b8fb42be0c20e79411533d1fd58edabebc0dd24626882cfd"},
|
||||
{file = "Pillow-10.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4d0152565c6aa6ebbfb1e5d8624140a440f2b99bf7afaafbdbf6430426497f28"},
|
||||
{file = "Pillow-10.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d921bc90b1defa55c9917ca6b6b71430e4286fc9e44c55ead78ca1a9f9eba5f2"},
|
||||
{file = "Pillow-10.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:cfe96560c6ce2f4c07d6647af2d0f3c54cc33289894ebd88cfbb3bcd5391e256"},
|
||||
{file = "Pillow-10.1.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:937bdc5a7f5343d1c97dc98149a0be7eb9704e937fe3dc7140e229ae4fc572a7"},
|
||||
{file = "Pillow-10.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1c25762197144e211efb5f4e8ad656f36c8d214d390585d1d21281f46d556ba"},
|
||||
{file = "Pillow-10.1.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:afc8eef765d948543a4775f00b7b8c079b3321d6b675dde0d02afa2ee23000b4"},
|
||||
{file = "Pillow-10.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:883f216eac8712b83a63f41b76ddfb7b2afab1b74abbb413c5df6680f071a6b9"},
|
||||
{file = "Pillow-10.1.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b920e4d028f6442bea9a75b7491c063f0b9a3972520731ed26c83e254302eb1e"},
|
||||
{file = "Pillow-10.1.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c41d960babf951e01a49c9746f92c5a7e0d939d1652d7ba30f6b3090f27e412"},
|
||||
{file = "Pillow-10.1.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1fafabe50a6977ac70dfe829b2d5735fd54e190ab55259ec8aea4aaea412fa0b"},
|
||||
{file = "Pillow-10.1.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3b834f4b16173e5b92ab6566f0473bfb09f939ba14b23b8da1f54fa63e4b623f"},
|
||||
{file = "Pillow-10.1.0.tar.gz", hash = "sha256:e6bf8de6c36ed96c86ea3b6e1d5273c53f46ef518a062464cd7ef5dd2cf92e38"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"]
|
||||
tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"]
|
||||
|
||||
[[package]]
|
||||
name = "pluggy"
|
||||
version = "1.3.0"
|
||||
description = "plugin and hook calling mechanisms for python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"},
|
||||
{file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
dev = ["pre-commit", "tox"]
|
||||
testing = ["pytest", "pytest-benchmark"]
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
version = "7.4.3"
|
||||
description = "pytest: simple powerful testing with Python"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "pytest-7.4.3-py3-none-any.whl", hash = "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac"},
|
||||
{file = "pytest-7.4.3.tar.gz", hash = "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
colorama = {version = "*", markers = "sys_platform == \"win32\""}
|
||||
exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
|
||||
iniconfig = "*"
|
||||
packaging = "*"
|
||||
pluggy = ">=0.12,<2.0"
|
||||
tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""}
|
||||
|
||||
[package.extras]
|
||||
testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
|
||||
|
||||
[[package]]
|
||||
name = "pytest-asyncio"
|
||||
version = "0.23.2"
|
||||
description = "Pytest support for asyncio"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pytest-asyncio-0.23.2.tar.gz", hash = "sha256:c16052382554c7b22d48782ab3438d5b10f8cf7a4bdcae7f0f67f097d95beecc"},
|
||||
{file = "pytest_asyncio-0.23.2-py3-none-any.whl", hash = "sha256:ea9021364e32d58f0be43b91c6233fb8d2224ccef2398d6837559e587682808f"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
pytest = ">=7.0.0"
|
||||
|
||||
[package.extras]
|
||||
docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"]
|
||||
testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "pytest-cov"
|
||||
version = "4.1.0"
|
||||
description = "Pytest plugin for measuring coverage."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"},
|
||||
{file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
coverage = {version = ">=5.2.1", extras = ["toml"]}
|
||||
pytest = ">=4.6"
|
||||
|
||||
[package.extras]
|
||||
testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"]
|
||||
|
||||
[[package]]
|
||||
name = "pytest-httpserver"
|
||||
version = "1.0.8"
|
||||
description = "pytest-httpserver is a httpserver for pytest"
|
||||
optional = false
|
||||
python-versions = ">=3.8,<4.0"
|
||||
files = [
|
||||
{file = "pytest_httpserver-1.0.8-py3-none-any.whl", hash = "sha256:24cd3d9f6a0b927c7bfc400d0b3fda7442721b8267ce29942bf307b190f0bb09"},
|
||||
{file = "pytest_httpserver-1.0.8.tar.gz", hash = "sha256:e052f69bc8a9073db02484681e8e47004dd1fb3763b0ae833bd899e5895c559a"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
Werkzeug = ">=2.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.1.8"
|
||||
description = "An extremely fast Python linter and code formatter, written in Rust."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "ruff-0.1.8-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:7de792582f6e490ae6aef36a58d85df9f7a0cfd1b0d4fe6b4fb51803a3ac96fa"},
|
||||
{file = "ruff-0.1.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:c8e3255afd186c142eef4ec400d7826134f028a85da2146102a1172ecc7c3696"},
|
||||
{file = "ruff-0.1.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff78a7583020da124dd0deb835ece1d87bb91762d40c514ee9b67a087940528b"},
|
||||
{file = "ruff-0.1.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd8ee69b02e7bdefe1e5da2d5b6eaaddcf4f90859f00281b2333c0e3a0cc9cd6"},
|
||||
{file = "ruff-0.1.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a05b0ddd7ea25495e4115a43125e8a7ebed0aa043c3d432de7e7d6e8e8cd6448"},
|
||||
{file = "ruff-0.1.8-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:e6f08ca730f4dc1b76b473bdf30b1b37d42da379202a059eae54ec7fc1fbcfed"},
|
||||
{file = "ruff-0.1.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f35960b02df6b827c1b903091bb14f4b003f6cf102705efc4ce78132a0aa5af3"},
|
||||
{file = "ruff-0.1.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7d076717c67b34c162da7c1a5bda16ffc205e0e0072c03745275e7eab888719f"},
|
||||
{file = "ruff-0.1.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6a21ab023124eafb7cef6d038f835cb1155cd5ea798edd8d9eb2f8b84be07d9"},
|
||||
{file = "ruff-0.1.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ce697c463458555027dfb194cb96d26608abab920fa85213deb5edf26e026664"},
|
||||
{file = "ruff-0.1.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:db6cedd9ffed55548ab313ad718bc34582d394e27a7875b4b952c2d29c001b26"},
|
||||
{file = "ruff-0.1.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:05ffe9dbd278965271252704eddb97b4384bf58b971054d517decfbf8c523f05"},
|
||||
{file = "ruff-0.1.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5daaeaf00ae3c1efec9742ff294b06c3a2a9db8d3db51ee4851c12ad385cda30"},
|
||||
{file = "ruff-0.1.8-py3-none-win32.whl", hash = "sha256:e49fbdfe257fa41e5c9e13c79b9e79a23a79bd0e40b9314bc53840f520c2c0b3"},
|
||||
{file = "ruff-0.1.8-py3-none-win_amd64.whl", hash = "sha256:f41f692f1691ad87f51708b823af4bb2c5c87c9248ddd3191c8f088e66ce590a"},
|
||||
{file = "ruff-0.1.8-py3-none-win_arm64.whl", hash = "sha256:aa8ee4f8440023b0a6c3707f76cadce8657553655dcbb5fc9b2f9bb9bee389f6"},
|
||||
{file = "ruff-0.1.8.tar.gz", hash = "sha256:f7ee467677467526cfe135eab86a40a0e8db43117936ac4f9b469ce9cdb3fb62"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sniffio"
|
||||
version = "1.3.0"
|
||||
description = "Sniff out which async library your code is running under"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"},
|
||||
{file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tomli"
|
||||
version = "2.0.1"
|
||||
description = "A lil' TOML parser"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
|
||||
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.9.0"
|
||||
description = "Backported and Experimental Type Hints for Python 3.8+"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"},
|
||||
{file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "werkzeug"
|
||||
version = "3.0.1"
|
||||
description = "The comprehensive WSGI web application library."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "werkzeug-3.0.1-py3-none-any.whl", hash = "sha256:90a285dc0e42ad56b34e696398b8122ee4c681833fb35b8334a095d82c56da10"},
|
||||
{file = "werkzeug-3.0.1.tar.gz", hash = "sha256:507e811ecea72b18a404947aded4b3390e1db8f826b494d76550ef45bb3b1dcc"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
MarkupSafe = ">=2.1.1"
|
||||
|
||||
[package.extras]
|
||||
watchdog = ["watchdog (>=2.3)"]
|
||||
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.8"
|
||||
content-hash = "9416a897c95d3c80cf1bfd3cc61cd19f0143c9bd0bc7c219fcb31ee27c497c9d"
|
||||
+55
-25
@@ -1,37 +1,67 @@
|
||||
[tool.poetry]
|
||||
name = "ollama"
|
||||
version = "0.0.0"
|
||||
description = "The official Python client for Ollama."
|
||||
authors = ["Ollama <hello@ollama.com>"]
|
||||
[project]
|
||||
name = 'ollama'
|
||||
description = 'The official Python client for Ollama.'
|
||||
authors = [
|
||||
{ email = 'hello@ollama.com' },
|
||||
]
|
||||
readme = 'README.md'
|
||||
requires-python = '>=3.8'
|
||||
dependencies = [
|
||||
'httpx>=0.27',
|
||||
'pydantic>=2.9',
|
||||
]
|
||||
dynamic = [ 'version' ]
|
||||
license = "MIT"
|
||||
readme = "README.md"
|
||||
homepage = "https://ollama.ai"
|
||||
repository = "https://github.com/jmorganca/ollama-python"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.8"
|
||||
httpx = "^0.25.2"
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
pytest = "^7.4.3"
|
||||
pytest-asyncio = "^0.23.2"
|
||||
pytest-cov = "^4.1.0"
|
||||
pytest-httpserver = "^1.0.8"
|
||||
pillow = "^10.1.0"
|
||||
ruff = "^0.1.8"
|
||||
[project.urls]
|
||||
homepage = 'https://ollama.com'
|
||||
repository = 'https://github.com/ollama/ollama-python'
|
||||
issues = 'https://github.com/ollama/ollama-python/issues'
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
requires = [ 'hatchling', 'hatch-vcs' ]
|
||||
build-backend = 'hatchling.build'
|
||||
|
||||
[tool.hatch.version]
|
||||
source = 'vcs'
|
||||
|
||||
[tool.hatch.envs.hatch-test]
|
||||
default-args = ['ollama', 'tests']
|
||||
extra-dependencies = [
|
||||
'pytest-anyio',
|
||||
'pytest-httpserver',
|
||||
]
|
||||
|
||||
[tool.hatch.envs.hatch-static-analysis]
|
||||
dependencies = [ 'ruff>=0.9.1' ]
|
||||
config-path = 'none'
|
||||
|
||||
[tool.ruff]
|
||||
line-length = 999
|
||||
indent-width = 2
|
||||
|
||||
[tool.ruff.format]
|
||||
quote-style = "single"
|
||||
indent-style = "space"
|
||||
quote-style = 'single'
|
||||
indent-style = 'space'
|
||||
docstring-code-format = false
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = ["E", "F", "B"]
|
||||
ignore = ["E501"]
|
||||
select = [
|
||||
'F', # pyflakes
|
||||
'E', # pycodestyle errors
|
||||
'W', # pycodestyle warnings
|
||||
'I', # sort imports
|
||||
'N', # pep8-naming
|
||||
'ASYNC', # flake8-async
|
||||
'FBT', # flake8-boolean-trap
|
||||
'B', # flake8-bugbear
|
||||
'C4', # flake8-comprehensions
|
||||
'PIE', # flake8-pie
|
||||
'SIM', # flake8-simplify
|
||||
'FLY', # flynt
|
||||
'RUF', # ruff-specific rules
|
||||
]
|
||||
ignore = ['FBT001'] # Boolean-typed positional argument in function definition
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
addopts = ['--doctest-modules']
|
||||
|
||||
+159
-25
@@ -1,27 +1,161 @@
|
||||
anyio==4.2.0 ; python_version >= "3.8" and python_version < "4.0" \
|
||||
--hash=sha256:745843b39e829e108e518c489b31dc757de7d2131d53fac32bd8df268227bfee \
|
||||
--hash=sha256:e1875bb4b4e2de1669f4bc7869b6d3f54231cdced71605e6e64c9be77e3be50f
|
||||
certifi==2023.11.17 ; python_version >= "3.8" and python_version < "4.0" \
|
||||
--hash=sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1 \
|
||||
--hash=sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474
|
||||
exceptiongroup==1.2.0 ; python_version >= "3.8" and python_version < "3.11" \
|
||||
--hash=sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14 \
|
||||
--hash=sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68
|
||||
h11==0.14.0 ; python_version >= "3.8" and python_version < "4.0" \
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv export
|
||||
-e .
|
||||
annotated-types==0.7.0 \
|
||||
--hash=sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53 \
|
||||
--hash=sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89
|
||||
# via pydantic
|
||||
anyio==4.5.2 ; python_full_version < '3.9' \
|
||||
--hash=sha256:23009af4ed04ce05991845451e11ef02fc7c5ed29179ac9a420e5ad0ac7ddc5b \
|
||||
--hash=sha256:c011ee36bc1e8ba40e5a81cb9df91925c218fe9b778554e0b56a21e1b5d4716f
|
||||
# via httpx
|
||||
anyio==4.8.0 ; python_full_version >= '3.9' \
|
||||
--hash=sha256:1d9fe889df5212298c0c0723fa20479d1b94883a2df44bd3897aa91083316f7a \
|
||||
--hash=sha256:b5011f270ab5eb0abf13385f851315585cc37ef330dd88e27ec3d34d651fd47a
|
||||
# via httpx
|
||||
certifi==2025.1.31 \
|
||||
--hash=sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651 \
|
||||
--hash=sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe
|
||||
# via
|
||||
# httpcore
|
||||
# httpx
|
||||
exceptiongroup==1.2.2 ; python_full_version < '3.11' \
|
||||
--hash=sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b \
|
||||
--hash=sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc
|
||||
# via anyio
|
||||
h11==0.14.0 \
|
||||
--hash=sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d \
|
||||
--hash=sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761
|
||||
httpcore==1.0.2 ; python_version >= "3.8" and python_version < "4.0" \
|
||||
--hash=sha256:096cc05bca73b8e459a1fc3dcf585148f63e534eae4339559c9b8a8d6399acc7 \
|
||||
--hash=sha256:9fc092e4799b26174648e54b74ed5f683132a464e95643b226e00c2ed2fa6535
|
||||
httpx==0.25.2 ; python_version >= "3.8" and python_version < "4.0" \
|
||||
--hash=sha256:8b8fcaa0c8ea7b05edd69a094e63a2094c4efcb48129fb757361bc423c0ad9e8 \
|
||||
--hash=sha256:a05d3d052d9b2dfce0e3896636467f8a5342fb2b902c819428e1ac65413ca118
|
||||
idna==3.6 ; python_version >= "3.8" and python_version < "4.0" \
|
||||
--hash=sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca \
|
||||
--hash=sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f
|
||||
sniffio==1.3.0 ; python_version >= "3.8" and python_version < "4.0" \
|
||||
--hash=sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101 \
|
||||
--hash=sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384
|
||||
typing-extensions==4.9.0 ; python_version >= "3.8" and python_version < "3.11" \
|
||||
--hash=sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783 \
|
||||
--hash=sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd
|
||||
# via httpcore
|
||||
httpcore==1.0.7 \
|
||||
--hash=sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c \
|
||||
--hash=sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd
|
||||
# via httpx
|
||||
httpx==0.28.1 \
|
||||
--hash=sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc \
|
||||
--hash=sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad
|
||||
# via ollama
|
||||
idna==3.10 \
|
||||
--hash=sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9 \
|
||||
--hash=sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3
|
||||
# via
|
||||
# anyio
|
||||
# httpx
|
||||
pydantic==2.10.6 \
|
||||
--hash=sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584 \
|
||||
--hash=sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236
|
||||
# via ollama
|
||||
pydantic-core==2.27.2 \
|
||||
--hash=sha256:00bad2484fa6bda1e216e7345a798bd37c68fb2d97558edd584942aa41b7d278 \
|
||||
--hash=sha256:0296abcb83a797db256b773f45773da397da75a08f5fcaef41f2044adec05f50 \
|
||||
--hash=sha256:03d0f86ea3184a12f41a2d23f7ccb79cdb5a18e06993f8a45baa8dfec746f0e9 \
|
||||
--hash=sha256:044a50963a614ecfae59bb1eaf7ea7efc4bc62f49ed594e18fa1e5d953c40e9f \
|
||||
--hash=sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6 \
|
||||
--hash=sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc \
|
||||
--hash=sha256:097830ed52fd9e427942ff3b9bc17fab52913b2f50f2880dc4a5611446606a54 \
|
||||
--hash=sha256:0d1e85068e818c73e048fe28cfc769040bb1f475524f4745a5dc621f75ac7630 \
|
||||
--hash=sha256:0d75070718e369e452075a6017fbf187f788e17ed67a3abd47fa934d001863d9 \
|
||||
--hash=sha256:14d4a5c49d2f009d62a2a7140d3064f686d17a5d1a268bc641954ba181880236 \
|
||||
--hash=sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7 \
|
||||
--hash=sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee \
|
||||
--hash=sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b \
|
||||
--hash=sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048 \
|
||||
--hash=sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc \
|
||||
--hash=sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130 \
|
||||
--hash=sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4 \
|
||||
--hash=sha256:251136cdad0cb722e93732cb45ca5299fb56e1344a833640bf93b2803f8d1bfd \
|
||||
--hash=sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4 \
|
||||
--hash=sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7 \
|
||||
--hash=sha256:280d219beebb0752699480fe8f1dc61ab6615c2046d76b7ab7ee38858de0a4e7 \
|
||||
--hash=sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4 \
|
||||
--hash=sha256:2bf14caea37e91198329b828eae1618c068dfb8ef17bb33287a7ad4b61ac314e \
|
||||
--hash=sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa \
|
||||
--hash=sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6 \
|
||||
--hash=sha256:337b443af21d488716f8d0b6164de833e788aa6bd7e3a39c005febc1284f4962 \
|
||||
--hash=sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b \
|
||||
--hash=sha256:3d591580c34f4d731592f0e9fe40f9cc1b430d297eecc70b962e93c5c668f15f \
|
||||
--hash=sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474 \
|
||||
--hash=sha256:3de9961f2a346257caf0aa508a4da705467f53778e9ef6fe744c038119737ef5 \
|
||||
--hash=sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459 \
|
||||
--hash=sha256:42c5f762659e47fdb7b16956c71598292f60a03aa92f8b6351504359dbdba6cf \
|
||||
--hash=sha256:47956ae78b6422cbd46f772f1746799cbb862de838fd8d1fbd34a82e05b0983a \
|
||||
--hash=sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c \
|
||||
--hash=sha256:4c9775e339e42e79ec99c441d9730fccf07414af63eac2f0e48e08fd38a64d76 \
|
||||
--hash=sha256:4e0b4220ba5b40d727c7f879eac379b822eee5d8fff418e9d3381ee45b3b0362 \
|
||||
--hash=sha256:50a68f3e3819077be2c98110c1f9dcb3817e93f267ba80a2c05bb4f8799e2ff4 \
|
||||
--hash=sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934 \
|
||||
--hash=sha256:521eb9b7f036c9b6187f0b47318ab0d7ca14bd87f776240b90b21c1f4f149320 \
|
||||
--hash=sha256:57762139821c31847cfb2df63c12f725788bd9f04bc2fb392790959b8f70f118 \
|
||||
--hash=sha256:5e4f4bb20d75e9325cc9696c6802657b58bc1dbbe3022f32cc2b2b632c3fbb96 \
|
||||
--hash=sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306 \
|
||||
--hash=sha256:669e193c1c576a58f132e3158f9dfa9662969edb1a250c54d8fa52590045f046 \
|
||||
--hash=sha256:688d3fd9fcb71f41c4c015c023d12a79d1c4c0732ec9eb35d96e3388a120dcf3 \
|
||||
--hash=sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2 \
|
||||
--hash=sha256:7041c36f5680c6e0f08d922aed302e98b3745d97fe1589db0a3eebf6624523af \
|
||||
--hash=sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9 \
|
||||
--hash=sha256:77d1bca19b0f7021b3a982e6f903dcd5b2b06076def36a652e3907f596e29f67 \
|
||||
--hash=sha256:7969e133a6f183be60e9f6f56bfae753585680f3b7307a8e555a948d443cc05a \
|
||||
--hash=sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27 \
|
||||
--hash=sha256:7d0c8399fcc1848491f00e0314bd59fb34a9c008761bcb422a057670c3f65e35 \
|
||||
--hash=sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b \
|
||||
--hash=sha256:7e17b560be3c98a8e3aa66ce828bdebb9e9ac6ad5466fba92eb74c4c95cb1151 \
|
||||
--hash=sha256:8083d4e875ebe0b864ffef72a4304827015cff328a1be6e22cc850753bfb122b \
|
||||
--hash=sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154 \
|
||||
--hash=sha256:82f986faf4e644ffc189a7f1aafc86e46ef70372bb153e7001e8afccc6e54133 \
|
||||
--hash=sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef \
|
||||
--hash=sha256:85210c4d99a0114f5a9481b44560d7d1e35e32cc5634c656bc48e590b669b145 \
|
||||
--hash=sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15 \
|
||||
--hash=sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4 \
|
||||
--hash=sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc \
|
||||
--hash=sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee \
|
||||
--hash=sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c \
|
||||
--hash=sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0 \
|
||||
--hash=sha256:9fdbe7629b996647b99c01b37f11170a57ae675375b14b8c13b8518b8320ced5 \
|
||||
--hash=sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57 \
|
||||
--hash=sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b \
|
||||
--hash=sha256:b0cb791f5b45307caae8810c2023a184c74605ec3bcbb67d13846c28ff731ff8 \
|
||||
--hash=sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1 \
|
||||
--hash=sha256:bca101c00bff0adb45a833f8451b9105d9df18accb8743b08107d7ada14bd7da \
|
||||
--hash=sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e \
|
||||
--hash=sha256:bec317a27290e2537f922639cafd54990551725fc844249e64c523301d0822fc \
|
||||
--hash=sha256:c10eb4f1659290b523af58fa7cffb452a61ad6ae5613404519aee4bfbf1df993 \
|
||||
--hash=sha256:c33939a82924da9ed65dab5a65d427205a73181d8098e79b6b426bdf8ad4e656 \
|
||||
--hash=sha256:c61709a844acc6bf0b7dce7daae75195a10aac96a596ea1b776996414791ede4 \
|
||||
--hash=sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c \
|
||||
--hash=sha256:c817e2b40aba42bac6f457498dacabc568c3b7a986fc9ba7c8d9d260b71485fb \
|
||||
--hash=sha256:cabb9bcb7e0d97f74df8646f34fc76fbf793b7f6dc2438517d7a9e50eee4f14d \
|
||||
--hash=sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9 \
|
||||
--hash=sha256:cca63613e90d001b9f2f9a9ceb276c308bfa2a43fafb75c8031c4f66039e8c6e \
|
||||
--hash=sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1 \
|
||||
--hash=sha256:d2088237af596f0a524d3afc39ab3b036e8adb054ee57cbb1dcf8e09da5b29cc \
|
||||
--hash=sha256:d262606bf386a5ba0b0af3b97f37c83d7011439e3dc1a9298f21efb292e42f1a \
|
||||
--hash=sha256:d2d63f1215638d28221f664596b1ccb3944f6e25dd18cd3b86b0a4c408d5ebb9 \
|
||||
--hash=sha256:d3e8d504bdd3f10835468f29008d72fc8359d95c9c415ce6e767203db6127506 \
|
||||
--hash=sha256:d4041c0b966a84b4ae7a09832eb691a35aec90910cd2dbe7a208de59be77965b \
|
||||
--hash=sha256:d716e2e30c6f140d7560ef1538953a5cd1a87264c737643d481f2779fc247fe1 \
|
||||
--hash=sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d \
|
||||
--hash=sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99 \
|
||||
--hash=sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3 \
|
||||
--hash=sha256:e0fd26b16394ead34a424eecf8a31a1f5137094cabe84a1bcb10fa6ba39d3d31 \
|
||||
--hash=sha256:e2bb4d3e5873c37bb3dd58714d4cd0b0e6238cebc4177ac8fe878f8b3aa8e74c \
|
||||
--hash=sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39 \
|
||||
--hash=sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a \
|
||||
--hash=sha256:ef592d4bad47296fb11f96cd7dc898b92e795032b4894dfb4076cfccd43a9308 \
|
||||
--hash=sha256:f141ee28a0ad2123b6611b6ceff018039df17f32ada8b534e6aa039545a3efb2 \
|
||||
--hash=sha256:f66d89ba397d92f840f8654756196d93804278457b5fbede59598a1f9f90b228 \
|
||||
--hash=sha256:f6f8e111843bbb0dee4cb6594cdc73e79b3329b526037ec242a3e49012495b3b \
|
||||
--hash=sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9 \
|
||||
--hash=sha256:fd1aea04935a508f62e0d0ef1f5ae968774a32afc306fb8545e06f5ff5cdf3ad
|
||||
# via pydantic
|
||||
sniffio==1.3.1 \
|
||||
--hash=sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2 \
|
||||
--hash=sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc
|
||||
# via anyio
|
||||
typing-extensions==4.12.2 \
|
||||
--hash=sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d \
|
||||
--hash=sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8
|
||||
# via
|
||||
# annotated-types
|
||||
# anyio
|
||||
# pydantic
|
||||
# pydantic-core
|
||||
|
||||
+701
-283
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,94 @@
|
||||
import tempfile
|
||||
from base64 import b64encode
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from ollama._types import CreateRequest, Image
|
||||
|
||||
|
||||
def test_image_serialization_bytes():
|
||||
image_bytes = b'test image bytes'
|
||||
encoded_string = b64encode(image_bytes).decode()
|
||||
img = Image(value=image_bytes)
|
||||
assert img.model_dump() == encoded_string
|
||||
|
||||
|
||||
def test_image_serialization_base64_string():
|
||||
b64_str = 'dGVzdCBiYXNlNjQgc3RyaW5n'
|
||||
img = Image(value=b64_str)
|
||||
assert img.model_dump() == b64_str # Should return as-is if valid base64
|
||||
|
||||
|
||||
def test_image_serialization_long_base64_string():
|
||||
b64_str = 'dGVzdCBiYXNlNjQgc3RyaW5n' * 1000
|
||||
img = Image(value=b64_str)
|
||||
assert img.model_dump() == b64_str # Should return as-is if valid base64
|
||||
|
||||
|
||||
def test_image_serialization_plain_string():
|
||||
img = Image(value='not a path or base64')
|
||||
assert img.model_dump() == 'not a path or base64' # Should return as-is
|
||||
|
||||
|
||||
def test_image_serialization_path():
|
||||
with tempfile.NamedTemporaryFile() as temp_file:
|
||||
temp_file.write(b'test file content')
|
||||
temp_file.flush()
|
||||
img = Image(value=Path(temp_file.name))
|
||||
assert img.model_dump() == b64encode(b'test file content').decode()
|
||||
|
||||
|
||||
def test_image_serialization_string_path():
|
||||
with tempfile.NamedTemporaryFile() as temp_file:
|
||||
temp_file.write(b'test file content')
|
||||
temp_file.flush()
|
||||
img = Image(value=temp_file.name)
|
||||
assert img.model_dump() == b64encode(b'test file content').decode()
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
img = Image(value='some_path/that/does/not/exist.png')
|
||||
img.model_dump()
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
img = Image(value='not an image')
|
||||
img.model_dump()
|
||||
|
||||
|
||||
def test_create_request_serialization():
|
||||
request = CreateRequest(model='test-model', from_='base-model', quantize='q4_0', files={'file1': 'content1'}, adapters={'adapter1': 'content1'}, template='test template', license='MIT', system='test system', parameters={'param1': 'value1'})
|
||||
|
||||
serialized = request.model_dump()
|
||||
assert serialized['from'] == 'base-model'
|
||||
assert 'from_' not in serialized
|
||||
assert serialized['quantize'] == 'q4_0'
|
||||
assert serialized['files'] == {'file1': 'content1'}
|
||||
assert serialized['adapters'] == {'adapter1': 'content1'}
|
||||
assert serialized['template'] == 'test template'
|
||||
assert serialized['license'] == 'MIT'
|
||||
assert serialized['system'] == 'test system'
|
||||
assert serialized['parameters'] == {'param1': 'value1'}
|
||||
|
||||
|
||||
def test_create_request_serialization_exclude_none_true():
|
||||
request = CreateRequest(model='test-model', from_=None, quantize=None)
|
||||
serialized = request.model_dump(exclude_none=True)
|
||||
assert serialized == {'model': 'test-model'}
|
||||
assert 'from' not in serialized
|
||||
assert 'from_' not in serialized
|
||||
assert 'quantize' not in serialized
|
||||
|
||||
|
||||
def test_create_request_serialization_exclude_none_false():
|
||||
request = CreateRequest(model='test-model', from_=None, quantize=None)
|
||||
serialized = request.model_dump(exclude_none=False)
|
||||
assert 'from' in serialized
|
||||
assert 'quantize' in serialized
|
||||
assert 'adapters' in serialized
|
||||
assert 'from_' not in serialized
|
||||
|
||||
|
||||
def test_create_request_serialization_license_list():
|
||||
request = CreateRequest(model='test-model', license=['MIT', 'Apache-2.0'])
|
||||
serialized = request.model_dump()
|
||||
assert serialized['license'] == ['MIT', 'Apache-2.0']
|
||||
@@ -0,0 +1,258 @@
|
||||
import json
|
||||
import sys
|
||||
from typing import Dict, List, Mapping, Sequence, Set, Tuple, Union
|
||||
|
||||
from ollama._utils import convert_function_to_tool
|
||||
|
||||
|
||||
def test_function_to_tool_conversion():
|
||||
def add_numbers(x: int, y: Union[int, None] = None) -> int:
|
||||
"""Add two numbers together.
|
||||
args:
|
||||
x (integer): The first number
|
||||
y (integer, optional): The second number
|
||||
|
||||
Returns:
|
||||
integer: The sum of x and y
|
||||
"""
|
||||
return x + y
|
||||
|
||||
tool = convert_function_to_tool(add_numbers).model_dump()
|
||||
|
||||
assert tool['type'] == 'function'
|
||||
assert tool['function']['name'] == 'add_numbers'
|
||||
assert tool['function']['description'] == 'Add two numbers together.'
|
||||
assert tool['function']['parameters']['type'] == 'object'
|
||||
assert tool['function']['parameters']['properties']['x']['type'] == 'integer'
|
||||
assert tool['function']['parameters']['properties']['x']['description'] == 'The first number'
|
||||
assert tool['function']['parameters']['required'] == ['x']
|
||||
|
||||
|
||||
def test_function_with_no_args():
|
||||
def simple_func():
|
||||
"""
|
||||
A simple function with no arguments.
|
||||
Args:
|
||||
None
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
|
||||
tool = convert_function_to_tool(simple_func).model_dump()
|
||||
assert tool['function']['name'] == 'simple_func'
|
||||
assert tool['function']['description'] == 'A simple function with no arguments.'
|
||||
assert tool['function']['parameters']['properties'] == {}
|
||||
|
||||
|
||||
def test_function_with_all_types():
|
||||
if sys.version_info >= (3, 10):
|
||||
|
||||
def all_types(
|
||||
x: int,
|
||||
y: str,
|
||||
z: list[int],
|
||||
w: dict[str, int],
|
||||
v: int | str | None,
|
||||
) -> int | dict[str, int] | str | list[int] | None:
|
||||
"""
|
||||
A function with all types.
|
||||
Args:
|
||||
x (integer): The first number
|
||||
y (string): The second number
|
||||
z (array): The third number
|
||||
w (object): The fourth number
|
||||
v (integer | string | None): The fifth number
|
||||
"""
|
||||
else:
|
||||
|
||||
def all_types(
|
||||
x: int,
|
||||
y: str,
|
||||
z: Sequence,
|
||||
w: Mapping[str, int],
|
||||
d: Dict[str, int],
|
||||
s: Set[int],
|
||||
t: Tuple[int, str],
|
||||
l: List[int], # noqa: E741
|
||||
o: Union[int, None],
|
||||
) -> Union[Mapping[str, int], str, None]:
|
||||
"""
|
||||
A function with all types.
|
||||
Args:
|
||||
x (integer): The first number
|
||||
y (string): The second number
|
||||
z (array): The third number
|
||||
w (object): The fourth number
|
||||
d (object): The fifth number
|
||||
s (array): The sixth number
|
||||
t (array): The seventh number
|
||||
l (array): The eighth number
|
||||
o (integer | None): The ninth number
|
||||
"""
|
||||
|
||||
tool_json = convert_function_to_tool(all_types).model_dump_json()
|
||||
tool = json.loads(tool_json)
|
||||
assert tool['function']['parameters']['properties']['x']['type'] == 'integer'
|
||||
assert tool['function']['parameters']['properties']['y']['type'] == 'string'
|
||||
|
||||
if sys.version_info >= (3, 10):
|
||||
assert tool['function']['parameters']['properties']['z']['type'] == 'array'
|
||||
assert tool['function']['parameters']['properties']['w']['type'] == 'object'
|
||||
assert {x.strip().strip("'") for x in tool['function']['parameters']['properties']['v']['type'].removeprefix('[').removesuffix(']').split(',')} == {'string', 'integer'}
|
||||
assert tool['function']['parameters']['properties']['v']['type'] != 'null'
|
||||
assert tool['function']['parameters']['required'] == ['x', 'y', 'z', 'w']
|
||||
else:
|
||||
assert tool['function']['parameters']['properties']['z']['type'] == 'array'
|
||||
assert tool['function']['parameters']['properties']['w']['type'] == 'object'
|
||||
assert tool['function']['parameters']['properties']['d']['type'] == 'object'
|
||||
assert tool['function']['parameters']['properties']['s']['type'] == 'array'
|
||||
assert tool['function']['parameters']['properties']['t']['type'] == 'array'
|
||||
assert tool['function']['parameters']['properties']['l']['type'] == 'array'
|
||||
assert tool['function']['parameters']['properties']['o']['type'] == 'integer'
|
||||
assert tool['function']['parameters']['properties']['o']['type'] != 'null'
|
||||
assert tool['function']['parameters']['required'] == ['x', 'y', 'z', 'w', 'd', 's', 't', 'l']
|
||||
|
||||
|
||||
def test_function_docstring_parsing():
|
||||
from typing import Any, Dict, List
|
||||
|
||||
def func_with_complex_docs(x: int, y: List[str]) -> Dict[str, Any]:
|
||||
"""
|
||||
Test function with complex docstring.
|
||||
|
||||
Args:
|
||||
x (integer): A number
|
||||
with multiple lines
|
||||
y (array of string): A list
|
||||
with multiple lines
|
||||
|
||||
Returns:
|
||||
object: A dictionary
|
||||
with multiple lines
|
||||
"""
|
||||
|
||||
tool = convert_function_to_tool(func_with_complex_docs).model_dump()
|
||||
assert tool['function']['description'] == 'Test function with complex docstring.'
|
||||
assert tool['function']['parameters']['properties']['x']['description'] == 'A number with multiple lines'
|
||||
assert tool['function']['parameters']['properties']['y']['description'] == 'A list with multiple lines'
|
||||
|
||||
|
||||
def test_skewed_docstring_parsing():
|
||||
def add_two_numbers(x: int, y: int) -> int:
|
||||
"""
|
||||
Add two numbers together.
|
||||
Args:
|
||||
x (integer): : The first number
|
||||
|
||||
|
||||
|
||||
|
||||
y (integer ): The second number
|
||||
Returns:
|
||||
integer: The sum of x and y
|
||||
"""
|
||||
|
||||
tool = convert_function_to_tool(add_two_numbers).model_dump()
|
||||
assert tool['function']['parameters']['properties']['x']['description'] == ': The first number'
|
||||
assert tool['function']['parameters']['properties']['y']['description'] == 'The second number'
|
||||
|
||||
|
||||
def test_function_with_no_docstring():
|
||||
def no_docstring(): ...
|
||||
|
||||
def no_docstring_with_args(x: int, y: int): ...
|
||||
|
||||
tool = convert_function_to_tool(no_docstring).model_dump()
|
||||
assert tool['function']['description'] == ''
|
||||
|
||||
tool = convert_function_to_tool(no_docstring_with_args).model_dump()
|
||||
assert tool['function']['description'] == ''
|
||||
assert tool['function']['parameters']['properties']['x']['description'] == ''
|
||||
assert tool['function']['parameters']['properties']['y']['description'] == ''
|
||||
|
||||
|
||||
def test_function_with_only_description():
|
||||
def only_description():
|
||||
"""
|
||||
A function with only a description.
|
||||
"""
|
||||
|
||||
tool = convert_function_to_tool(only_description).model_dump()
|
||||
assert tool['function']['description'] == 'A function with only a description.'
|
||||
assert tool['function']['parameters'] == {'type': 'object', 'defs': None, 'items': None, 'required': None, 'properties': {}}
|
||||
|
||||
def only_description_with_args(x: int, y: int):
|
||||
"""
|
||||
A function with only a description.
|
||||
"""
|
||||
|
||||
tool = convert_function_to_tool(only_description_with_args).model_dump()
|
||||
assert tool['function']['description'] == 'A function with only a description.'
|
||||
assert tool['function']['parameters'] == {
|
||||
'type': 'object',
|
||||
'defs': None,
|
||||
'items': None,
|
||||
'properties': {
|
||||
'x': {'type': 'integer', 'description': '', 'enum': None, 'items': None},
|
||||
'y': {'type': 'integer', 'description': '', 'enum': None, 'items': None},
|
||||
},
|
||||
'required': ['x', 'y'],
|
||||
}
|
||||
|
||||
|
||||
def test_function_with_yields():
|
||||
def function_with_yields(x: int, y: int):
|
||||
"""
|
||||
A function with yields section.
|
||||
|
||||
Args:
|
||||
x: the first number
|
||||
y: the second number
|
||||
|
||||
Yields:
|
||||
The sum of x and y
|
||||
"""
|
||||
|
||||
tool = convert_function_to_tool(function_with_yields).model_dump()
|
||||
assert tool['function']['description'] == 'A function with yields section.'
|
||||
assert tool['function']['parameters']['properties']['x']['description'] == 'the first number'
|
||||
assert tool['function']['parameters']['properties']['y']['description'] == 'the second number'
|
||||
|
||||
|
||||
def test_function_with_no_types():
|
||||
def no_types(a, b):
|
||||
"""
|
||||
A function with no types.
|
||||
"""
|
||||
|
||||
tool = convert_function_to_tool(no_types).model_dump()
|
||||
assert tool['function']['parameters']['properties']['a']['type'] == 'string'
|
||||
assert tool['function']['parameters']['properties']['b']['type'] == 'string'
|
||||
|
||||
|
||||
def test_function_with_parentheses():
|
||||
def func_with_parentheses(a: int, b: int) -> int:
|
||||
"""
|
||||
A function with parentheses.
|
||||
Args:
|
||||
a: First (:thing) number to add
|
||||
b: Second number to add
|
||||
Returns:
|
||||
int: The sum of a and b
|
||||
"""
|
||||
|
||||
def func_with_parentheses_and_args(a: int, b: int):
|
||||
"""
|
||||
A function with parentheses and args.
|
||||
Args:
|
||||
a(integer) : First (:thing) number to add
|
||||
b(integer) :Second number to add
|
||||
"""
|
||||
|
||||
tool = convert_function_to_tool(func_with_parentheses).model_dump()
|
||||
assert tool['function']['parameters']['properties']['a']['description'] == 'First (:thing) number to add'
|
||||
assert tool['function']['parameters']['properties']['b']['description'] == 'Second number to add'
|
||||
|
||||
tool = convert_function_to_tool(func_with_parentheses_and_args).model_dump()
|
||||
assert tool['function']['parameters']['properties']['a']['description'] == 'First (:thing) number to add'
|
||||
assert tool['function']['parameters']['properties']['b']['description'] == 'Second number to add'
|
||||
@@ -0,0 +1,275 @@
|
||||
version = 1
|
||||
requires-python = ">=3.8"
|
||||
resolution-markers = [
|
||||
"python_full_version >= '3.9'",
|
||||
"python_full_version < '3.9'",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "annotated-types"
|
||||
version = "0.7.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.9'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyio"
|
||||
version = "4.5.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
resolution-markers = [
|
||||
"python_full_version < '3.9'",
|
||||
]
|
||||
dependencies = [
|
||||
{ name = "exceptiongroup", marker = "python_full_version < '3.9'" },
|
||||
{ name = "idna", marker = "python_full_version < '3.9'" },
|
||||
{ name = "sniffio", marker = "python_full_version < '3.9'" },
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.9'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/4d/f9/9a7ce600ebe7804daf90d4d48b1c0510a4561ddce43a596be46676f82343/anyio-4.5.2.tar.gz", hash = "sha256:23009af4ed04ce05991845451e11ef02fc7c5ed29179ac9a420e5ad0ac7ddc5b", size = 171293 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/b4/f7e396030e3b11394436358ca258a81d6010106582422f23443c16ca1873/anyio-4.5.2-py3-none-any.whl", hash = "sha256:c011ee36bc1e8ba40e5a81cb9df91925c218fe9b778554e0b56a21e1b5d4716f", size = 89766 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyio"
|
||||
version = "4.8.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
resolution-markers = [
|
||||
"python_full_version >= '3.9'",
|
||||
]
|
||||
dependencies = [
|
||||
{ name = "exceptiongroup", marker = "python_full_version >= '3.9' and python_full_version < '3.11'" },
|
||||
{ name = "idna", marker = "python_full_version >= '3.9'" },
|
||||
{ name = "sniffio", marker = "python_full_version >= '3.9'" },
|
||||
{ name = "typing-extensions", marker = "python_full_version >= '3.9' and python_full_version < '3.13'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a3/73/199a98fc2dae33535d6b8e8e6ec01f8c1d76c9adb096c6b7d64823038cde/anyio-4.8.0.tar.gz", hash = "sha256:1d9fe889df5212298c0c0723fa20479d1b94883a2df44bd3897aa91083316f7a", size = 181126 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/46/eb/e7f063ad1fec6b3178a3cd82d1a3c4de82cccf283fc42746168188e1cdd5/anyio-4.8.0-py3-none-any.whl", hash = "sha256:b5011f270ab5eb0abf13385f851315585cc37ef330dd88e27ec3d34d651fd47a", size = 96041 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2025.1.31"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "exceptiongroup"
|
||||
version = "1.2.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", size = 28883 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h11"
|
||||
version = "0.14.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httpcore"
|
||||
version = "1.0.7"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi" },
|
||||
{ name = "h11" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/6a/41/d7d0a89eb493922c37d343b607bc1b5da7f5be7e383740b4753ad8943e90/httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c", size = 85196 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/87/f5/72347bc88306acb359581ac4d52f23c0ef445b57157adedb9aee0cd689d2/httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd", size = 78551 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httpx"
|
||||
version = "0.28.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio", version = "4.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
|
||||
{ name = "anyio", version = "4.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" },
|
||||
{ name = "certifi" },
|
||||
{ name = "httpcore" },
|
||||
{ name = "idna" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.10"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ollama"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "httpx" },
|
||||
{ name = "pydantic" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "httpx", specifier = ">=0.27" },
|
||||
{ name = "pydantic", specifier = ">=2.9" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "2.10.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "annotated-types" },
|
||||
{ name = "pydantic-core" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b7/ae/d5220c5c52b158b1de7ca89fc5edb72f304a70a4c540c84c8844bf4008de/pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236", size = 761681 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/3c/8cc1cc84deffa6e25d2d0c688ebb80635dfdbf1dbea3e30c541c8cf4d860/pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584", size = 431696 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic-core"
|
||||
version = "2.27.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/fc/01/f3e5ac5e7c25833db5eb555f7b7ab24cd6f8c322d3a3ad2d67a952dc0abc/pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39", size = 413443 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/3a/bc/fed5f74b5d802cf9a03e83f60f18864e90e3aed7223adaca5ffb7a8d8d64/pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa", size = 1895938 },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/2a/185aff24ce844e39abb8dd680f4e959f0006944f4a8a0ea372d9f9ae2e53/pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c", size = 1815684 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/43/fafabd3d94d159d4f1ed62e383e264f146a17dd4d48453319fd782e7979e/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7969e133a6f183be60e9f6f56bfae753585680f3b7307a8e555a948d443cc05a", size = 1829169 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/d1/f2dfe1a2a637ce6800b799aa086d079998959f6f1215eb4497966efd2274/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3de9961f2a346257caf0aa508a4da705467f53778e9ef6fe744c038119737ef5", size = 1867227 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/39/e06fcbcc1c785daa3160ccf6c1c38fea31f5754b756e34b65f74e99780b5/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2bb4d3e5873c37bb3dd58714d4cd0b0e6238cebc4177ac8fe878f8b3aa8e74c", size = 2037695 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/67/61291ee98e07f0650eb756d44998214231f50751ba7e13f4f325d95249ab/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:280d219beebb0752699480fe8f1dc61ab6615c2046d76b7ab7ee38858de0a4e7", size = 2741662 },
|
||||
{ url = "https://files.pythonhosted.org/packages/32/90/3b15e31b88ca39e9e626630b4c4a1f5a0dfd09076366f4219429e6786076/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47956ae78b6422cbd46f772f1746799cbb862de838fd8d1fbd34a82e05b0983a", size = 1993370 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/83/c06d333ee3a67e2e13e07794995c1535565132940715931c1c43bfc85b11/pydantic_core-2.27.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:14d4a5c49d2f009d62a2a7140d3064f686d17a5d1a268bc641954ba181880236", size = 1996813 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/f7/89be1c8deb6e22618a74f0ca0d933fdcb8baa254753b26b25ad3acff8f74/pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:337b443af21d488716f8d0b6164de833e788aa6bd7e3a39c005febc1284f4962", size = 2005287 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/7d/8eb3e23206c00ef7feee17b83a4ffa0a623eb1a9d382e56e4aa46fd15ff2/pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:03d0f86ea3184a12f41a2d23f7ccb79cdb5a18e06993f8a45baa8dfec746f0e9", size = 2128414 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4e/99/fe80f3ff8dd71a3ea15763878d464476e6cb0a2db95ff1c5c554133b6b83/pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7041c36f5680c6e0f08d922aed302e98b3745d97fe1589db0a3eebf6624523af", size = 2155301 },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/a3/e50460b9a5789ca1451b70d4f52546fa9e2b420ba3bfa6100105c0559238/pydantic_core-2.27.2-cp310-cp310-win32.whl", hash = "sha256:50a68f3e3819077be2c98110c1f9dcb3817e93f267ba80a2c05bb4f8799e2ff4", size = 1816685 },
|
||||
{ url = "https://files.pythonhosted.org/packages/57/4c/a8838731cb0f2c2a39d3535376466de6049034d7b239c0202a64aaa05533/pydantic_core-2.27.2-cp310-cp310-win_amd64.whl", hash = "sha256:e0fd26b16394ead34a424eecf8a31a1f5137094cabe84a1bcb10fa6ba39d3d31", size = 1982876 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/89/f3450af9d09d44eea1f2c369f49e8f181d742f28220f88cc4dfaae91ea6e/pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc", size = 1893421 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/e3/71fe85af2021f3f386da42d291412e5baf6ce7716bd7101ea49c810eda90/pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7", size = 1814998 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/3c/724039e0d848fd69dbf5806894e26479577316c6f0f112bacaf67aa889ac/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15", size = 1826167 },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/5b/1b29e8c1fb5f3199a9a57c1452004ff39f494bbe9bdbe9a81e18172e40d3/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306", size = 1865071 },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/6c/3985203863d76bb7d7266e36970d7e3b6385148c18a68cc8915fd8c84d57/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99", size = 2036244 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/41/f15316858a246b5d723f7d7f599f79e37493b2e84bfc789e58d88c209f8a/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459", size = 2737470 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/7c/b860618c25678bbd6d1d99dbdfdf0510ccb50790099b963ff78a124b754f/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048", size = 1992291 },
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/73/42c3742a391eccbeab39f15213ecda3104ae8682ba3c0c28069fbcb8c10d/pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d", size = 1994613 },
|
||||
{ url = "https://files.pythonhosted.org/packages/94/7a/941e89096d1175d56f59340f3a8ebaf20762fef222c298ea96d36a6328c5/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b", size = 2002355 },
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/95/2359937a73d49e336a5a19848713555605d4d8d6940c3ec6c6c0ca4dcf25/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474", size = 2126661 },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/4c/ca02b7bdb6012a1adef21a50625b14f43ed4d11f1fc237f9d7490aa5078c/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6", size = 2153261 },
|
||||
{ url = "https://files.pythonhosted.org/packages/72/9d/a241db83f973049a1092a079272ffe2e3e82e98561ef6214ab53fe53b1c7/pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c", size = 1812361 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/ef/013f07248041b74abd48a385e2110aa3a9bbfef0fbd97d4e6d07d2f5b89a/pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc", size = 1982484 },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/1c/16b3a3e3398fd29dca77cea0a1d998d6bde3902fa2706985191e2313cc76/pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4", size = 1867102 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d6/74/51c8a5482ca447871c93e142d9d4a92ead74de6c8dc5e66733e22c9bba89/pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0", size = 1893127 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/f3/c97e80721735868313c58b89d2de85fa80fe8dfeeed84dc51598b92a135e/pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef", size = 1811340 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/91/840ec1375e686dbae1bd80a9e46c26a1e0083e1186abc610efa3d9a36180/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7", size = 1822900 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/31/4240bc96025035500c18adc149aa6ffdf1a0062a4b525c932065ceb4d868/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934", size = 1869177 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/20/02fbaadb7808be578317015c462655c317a77a7c8f0ef274bc016a784c54/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6", size = 2038046 },
|
||||
{ url = "https://files.pythonhosted.org/packages/06/86/7f306b904e6c9eccf0668248b3f272090e49c275bc488a7b88b0823444a4/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c", size = 2685386 },
|
||||
{ url = "https://files.pythonhosted.org/packages/8d/f0/49129b27c43396581a635d8710dae54a791b17dfc50c70164866bbf865e3/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2", size = 1997060 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/0f/943b4af7cd416c477fd40b187036c4f89b416a33d3cc0ab7b82708a667aa/pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4", size = 2004870 },
|
||||
{ url = "https://files.pythonhosted.org/packages/35/40/aea70b5b1a63911c53a4c8117c0a828d6790483f858041f47bab0b779f44/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3", size = 1999822 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/b3/807b94fd337d58effc5498fd1a7a4d9d59af4133e83e32ae39a96fddec9d/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4", size = 2130364 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/df/791c827cd4ee6efd59248dca9369fb35e80a9484462c33c6649a8d02b565/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57", size = 2158303 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/67/4e197c300976af185b7cef4c02203e175fb127e414125916bf1128b639a9/pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc", size = 1834064 },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/ea/cd7209a889163b8dcca139fe32b9687dd05249161a3edda62860430457a5/pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9", size = 1989046 },
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/49/c54baab2f4658c26ac633d798dab66b4c3a9bbf47cff5284e9c182f4137a/pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b", size = 1885092 },
|
||||
{ url = "https://files.pythonhosted.org/packages/41/b1/9bc383f48f8002f99104e3acff6cba1231b29ef76cfa45d1506a5cad1f84/pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b", size = 1892709 },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/6c/e62b8657b834f3eb2961b49ec8e301eb99946245e70bf42c8817350cbefc/pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154", size = 1811273 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/15/52cfe49c8c986e081b863b102d6b859d9defc63446b642ccbbb3742bf371/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9", size = 1823027 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b1/1c/b6f402cfc18ec0024120602bdbcebc7bdd5b856528c013bd4d13865ca473/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9", size = 1868888 },
|
||||
{ url = "https://files.pythonhosted.org/packages/bd/7b/8cb75b66ac37bc2975a3b7de99f3c6f355fcc4d89820b61dffa8f1e81677/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1", size = 2037738 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c8/f1/786d8fe78970a06f61df22cba58e365ce304bf9b9f46cc71c8c424e0c334/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a", size = 2685138 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/74/d12b2cd841d8724dc8ffb13fc5cef86566a53ed358103150209ecd5d1999/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e", size = 1997025 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/6e/940bcd631bc4d9a06c9539b51f070b66e8f370ed0933f392db6ff350d873/pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4", size = 2004633 },
|
||||
{ url = "https://files.pythonhosted.org/packages/50/cc/a46b34f1708d82498c227d5d80ce615b2dd502ddcfd8376fc14a36655af1/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27", size = 1999404 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ca/2d/c365cfa930ed23bc58c41463bae347d1005537dc8db79e998af8ba28d35e/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee", size = 2130130 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/d7/eb64d015c350b7cdb371145b54d96c919d4db516817f31cd1c650cae3b21/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1", size = 2157946 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a4/99/bddde3ddde76c03b65dfd5a66ab436c4e58ffc42927d4ff1198ffbf96f5f/pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130", size = 1834387 },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/47/82b5e846e01b26ac6f1893d3c5f9f3a2eb6ba79be26eef0b759b4fe72946/pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee", size = 1990453 },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/b2/b2b50d5ecf21acf870190ae5d093602d95f66c9c31f9d5de6062eb329ad1/pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b", size = 1885186 },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/53/13e9917fc69c0a4aea06fd63ed6a8d6cda9cf140ca9584d49c1650b0ef5e/pydantic_core-2.27.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d3e8d504bdd3f10835468f29008d72fc8359d95c9c415ce6e767203db6127506", size = 1899595 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/20/26c549249769ed84877f862f7bb93f89a6ee08b4bee1ed8781616b7fbb5e/pydantic_core-2.27.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:521eb9b7f036c9b6187f0b47318ab0d7ca14bd87f776240b90b21c1f4f149320", size = 1775010 },
|
||||
{ url = "https://files.pythonhosted.org/packages/35/eb/8234e05452d92d2b102ffa1b56d801c3567e628fdc63f02080fdfc68fd5e/pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85210c4d99a0114f5a9481b44560d7d1e35e32cc5634c656bc48e590b669b145", size = 1830727 },
|
||||
{ url = "https://files.pythonhosted.org/packages/8f/df/59f915c8b929d5f61e5a46accf748a87110ba145156f9326d1a7d28912b2/pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d716e2e30c6f140d7560ef1538953a5cd1a87264c737643d481f2779fc247fe1", size = 1868393 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/52/81cf4071dca654d485c277c581db368b0c95b2b883f4d7b736ab54f72ddf/pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f66d89ba397d92f840f8654756196d93804278457b5fbede59598a1f9f90b228", size = 2040300 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/00/05197ce1614f5c08d7a06e1d39d5d8e704dc81971b2719af134b844e2eaf/pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:669e193c1c576a58f132e3158f9dfa9662969edb1a250c54d8fa52590045f046", size = 2738785 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/a3/5f19bc495793546825ab160e530330c2afcee2281c02b5ffafd0b32ac05e/pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdbe7629b996647b99c01b37f11170a57ae675375b14b8c13b8518b8320ced5", size = 1996493 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ed/e8/e0102c2ec153dc3eed88aea03990e1b06cfbca532916b8a48173245afe60/pydantic_core-2.27.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d262606bf386a5ba0b0af3b97f37c83d7011439e3dc1a9298f21efb292e42f1a", size = 1998544 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/a3/4be70845b555bd80aaee9f9812a7cf3df81550bce6dadb3cfee9c5d8421d/pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cabb9bcb7e0d97f74df8646f34fc76fbf793b7f6dc2438517d7a9e50eee4f14d", size = 2007449 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e3/9f/b779ed2480ba355c054e6d7ea77792467631d674b13d8257085a4bc7dcda/pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_armv7l.whl", hash = "sha256:d2d63f1215638d28221f664596b1ccb3944f6e25dd18cd3b86b0a4c408d5ebb9", size = 2129460 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/f0/a6ab0681f6e95260c7fbf552874af7302f2ea37b459f9b7f00698f875492/pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bca101c00bff0adb45a833f8451b9105d9df18accb8743b08107d7ada14bd7da", size = 2159609 },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/2b/e1059506795104349712fbca647b18b3f4a7fd541c099e6259717441e1e0/pydantic_core-2.27.2-cp38-cp38-win32.whl", hash = "sha256:f6f8e111843bbb0dee4cb6594cdc73e79b3329b526037ec242a3e49012495b3b", size = 1819886 },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/6d/df49c17f024dfc58db0bacc7b03610058018dd2ea2eaf748ccbada4c3d06/pydantic_core-2.27.2-cp38-cp38-win_amd64.whl", hash = "sha256:fd1aea04935a508f62e0d0ef1f5ae968774a32afc306fb8545e06f5ff5cdf3ad", size = 1980773 },
|
||||
{ url = "https://files.pythonhosted.org/packages/27/97/3aef1ddb65c5ccd6eda9050036c956ff6ecbfe66cb7eb40f280f121a5bb0/pydantic_core-2.27.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c10eb4f1659290b523af58fa7cffb452a61ad6ae5613404519aee4bfbf1df993", size = 1896475 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ad/d3/5668da70e373c9904ed2f372cb52c0b996426f302e0dee2e65634c92007d/pydantic_core-2.27.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ef592d4bad47296fb11f96cd7dc898b92e795032b4894dfb4076cfccd43a9308", size = 1772279 },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/9e/e44b8cb0edf04a2f0a1f6425a65ee089c1d6f9c4c2dcab0209127b6fdfc2/pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c61709a844acc6bf0b7dce7daae75195a10aac96a596ea1b776996414791ede4", size = 1829112 },
|
||||
{ url = "https://files.pythonhosted.org/packages/1c/90/1160d7ac700102effe11616e8119e268770f2a2aa5afb935f3ee6832987d/pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c5f762659e47fdb7b16956c71598292f60a03aa92f8b6351504359dbdba6cf", size = 1866780 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/33/13983426df09a36d22c15980008f8d9c77674fc319351813b5a2739b70f3/pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c9775e339e42e79ec99c441d9730fccf07414af63eac2f0e48e08fd38a64d76", size = 2037943 },
|
||||
{ url = "https://files.pythonhosted.org/packages/01/d7/ced164e376f6747e9158c89988c293cd524ab8d215ae4e185e9929655d5c/pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57762139821c31847cfb2df63c12f725788bd9f04bc2fb392790959b8f70f118", size = 2740492 },
|
||||
{ url = "https://files.pythonhosted.org/packages/8b/1f/3dc6e769d5b7461040778816aab2b00422427bcaa4b56cc89e9c653b2605/pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d1e85068e818c73e048fe28cfc769040bb1f475524f4745a5dc621f75ac7630", size = 1995714 },
|
||||
{ url = "https://files.pythonhosted.org/packages/07/d7/a0bd09bc39283530b3f7c27033a814ef254ba3bd0b5cfd040b7abf1fe5da/pydantic_core-2.27.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:097830ed52fd9e427942ff3b9bc17fab52913b2f50f2880dc4a5611446606a54", size = 1997163 },
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/bb/2db4ad1762e1c5699d9b857eeb41959191980de6feb054e70f93085e1bcd/pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:044a50963a614ecfae59bb1eaf7ea7efc4bc62f49ed594e18fa1e5d953c40e9f", size = 2005217 },
|
||||
{ url = "https://files.pythonhosted.org/packages/53/5f/23a5a3e7b8403f8dd8fc8a6f8b49f6b55c7d715b77dcf1f8ae919eeb5628/pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:4e0b4220ba5b40d727c7f879eac379b822eee5d8fff418e9d3381ee45b3b0362", size = 2127899 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/ae/aa38bb8dd3d89c2f1d8362dd890ee8f3b967330821d03bbe08fa01ce3766/pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5e4f4bb20d75e9325cc9696c6802657b58bc1dbbe3022f32cc2b2b632c3fbb96", size = 2155726 },
|
||||
{ url = "https://files.pythonhosted.org/packages/98/61/4f784608cc9e98f70839187117ce840480f768fed5d386f924074bf6213c/pydantic_core-2.27.2-cp39-cp39-win32.whl", hash = "sha256:cca63613e90d001b9f2f9a9ceb276c308bfa2a43fafb75c8031c4f66039e8c6e", size = 1817219 },
|
||||
{ url = "https://files.pythonhosted.org/packages/57/82/bb16a68e4a1a858bb3768c2c8f1ff8d8978014e16598f001ea29a25bf1d1/pydantic_core-2.27.2-cp39-cp39-win_amd64.whl", hash = "sha256:77d1bca19b0f7021b3a982e6f903dcd5b2b06076def36a652e3907f596e29f67", size = 1985382 },
|
||||
{ url = "https://files.pythonhosted.org/packages/46/72/af70981a341500419e67d5cb45abe552a7c74b66326ac8877588488da1ac/pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2bf14caea37e91198329b828eae1618c068dfb8ef17bb33287a7ad4b61ac314e", size = 1891159 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ad/3d/c5913cccdef93e0a6a95c2d057d2c2cba347815c845cda79ddd3c0f5e17d/pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b0cb791f5b45307caae8810c2023a184c74605ec3bcbb67d13846c28ff731ff8", size = 1768331 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/f0/a3ae8fbee269e4934f14e2e0e00928f9346c5943174f2811193113e58252/pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:688d3fd9fcb71f41c4c015c023d12a79d1c4c0732ec9eb35d96e3388a120dcf3", size = 1822467 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/7a/7bbf241a04e9f9ea24cd5874354a83526d639b02674648af3f350554276c/pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d591580c34f4d731592f0e9fe40f9cc1b430d297eecc70b962e93c5c668f15f", size = 1979797 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/5f/4784c6107731f89e0005a92ecb8a2efeafdb55eb992b8e9d0a2be5199335/pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:82f986faf4e644ffc189a7f1aafc86e46ef70372bb153e7001e8afccc6e54133", size = 1987839 },
|
||||
{ url = "https://files.pythonhosted.org/packages/6d/a7/61246562b651dff00de86a5f01b6e4befb518df314c54dec187a78d81c84/pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:bec317a27290e2537f922639cafd54990551725fc844249e64c523301d0822fc", size = 1998861 },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/aa/837821ecf0c022bbb74ca132e117c358321e72e7f9702d1b6a03758545e2/pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:0296abcb83a797db256b773f45773da397da75a08f5fcaef41f2044adec05f50", size = 2116582 },
|
||||
{ url = "https://files.pythonhosted.org/packages/81/b0/5e74656e95623cbaa0a6278d16cf15e10a51f6002e3ec126541e95c29ea3/pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0d75070718e369e452075a6017fbf187f788e17ed67a3abd47fa934d001863d9", size = 2151985 },
|
||||
{ url = "https://files.pythonhosted.org/packages/63/37/3e32eeb2a451fddaa3898e2163746b0cffbbdbb4740d38372db0490d67f3/pydantic_core-2.27.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7e17b560be3c98a8e3aa66ce828bdebb9e9ac6ad5466fba92eb74c4c95cb1151", size = 2004715 },
|
||||
{ url = "https://files.pythonhosted.org/packages/29/0e/dcaea00c9dbd0348b723cae82b0e0c122e0fa2b43fa933e1622fd237a3ee/pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c33939a82924da9ed65dab5a65d427205a73181d8098e79b6b426bdf8ad4e656", size = 1891733 },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/d3/e797bba8860ce650272bda6383a9d8cad1d1c9a75a640c9d0e848076f85e/pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:00bad2484fa6bda1e216e7345a798bd37c68fb2d97558edd584942aa41b7d278", size = 1768375 },
|
||||
{ url = "https://files.pythonhosted.org/packages/41/f7/f847b15fb14978ca2b30262548f5fc4872b2724e90f116393eb69008299d/pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c817e2b40aba42bac6f457498dacabc568c3b7a986fc9ba7c8d9d260b71485fb", size = 1822307 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/63/ed80ec8255b587b2f108e514dc03eed1546cd00f0af281e699797f373f38/pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:251136cdad0cb722e93732cb45ca5299fb56e1344a833640bf93b2803f8d1bfd", size = 1979971 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a9/6d/6d18308a45454a0de0e975d70171cadaf454bc7a0bf86b9c7688e313f0bb/pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d2088237af596f0a524d3afc39ab3b036e8adb054ee57cbb1dcf8e09da5b29cc", size = 1987616 },
|
||||
{ url = "https://files.pythonhosted.org/packages/82/8a/05f8780f2c1081b800a7ca54c1971e291c2d07d1a50fb23c7e4aef4ed403/pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d4041c0b966a84b4ae7a09832eb691a35aec90910cd2dbe7a208de59be77965b", size = 1998943 },
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/3e/fe5b6613d9e4c0038434396b46c5303f5ade871166900b357ada4766c5b7/pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:8083d4e875ebe0b864ffef72a4304827015cff328a1be6e22cc850753bfb122b", size = 2116654 },
|
||||
{ url = "https://files.pythonhosted.org/packages/db/ad/28869f58938fad8cc84739c4e592989730bfb69b7c90a8fff138dff18e1e/pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f141ee28a0ad2123b6611b6ceff018039df17f32ada8b534e6aa039545a3efb2", size = 2152292 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/0c/c5c5cd3689c32ed1fe8c5d234b079c12c281c051759770c05b8bed6412b5/pydantic_core-2.27.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7d0c8399fcc1848491f00e0314bd59fb34a9c008761bcb422a057670c3f65e35", size = 2004961 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sniffio"
|
||||
version = "1.3.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.12.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 },
|
||||
]
|
||||
Reference in New Issue
Block a user