fllme 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fllme-0.2.0/.github/workflows/pypi.yml +20 -0
- fllme-0.2.0/.github/workflows/release.yml +33 -0
- fllme-0.2.0/.github/workflows/tests.yml +24 -0
- fllme-0.2.0/.gitignore +11 -0
- fllme-0.2.0/.python-version +1 -0
- fllme-0.2.0/Makefile +22 -0
- fllme-0.2.0/PKG-INFO +270 -0
- fllme-0.2.0/README.md +257 -0
- fllme-0.2.0/fllme/__init__.py +87 -0
- fllme-0.2.0/fllme/__main__.py +0 -0
- fllme-0.2.0/fllme/auth/__init__.py +42 -0
- fllme-0.2.0/fllme/auth/api_key.py +32 -0
- fllme-0.2.0/fllme/auth/google_adc.py +52 -0
- fllme-0.2.0/fllme/auth/protocol.py +11 -0
- fllme-0.2.0/fllme/errors.py +35 -0
- fllme-0.2.0/fllme/generate.py +86 -0
- fllme-0.2.0/fllme/http.py +19 -0
- fllme-0.2.0/fllme/media.py +99 -0
- fllme-0.2.0/fllme/models/__init__.py +103 -0
- fllme-0.2.0/fllme/models/auth.py +28 -0
- fllme-0.2.0/fllme/models/deployment.py +21 -0
- fllme-0.2.0/fllme/models/input/__init__.py +31 -0
- fllme-0.2.0/fllme/models/input/image.py +37 -0
- fllme-0.2.0/fllme/models/input/main.py +45 -0
- fllme-0.2.0/fllme/models/input/tools.py +21 -0
- fllme-0.2.0/fllme/models/message.py +95 -0
- fllme-0.2.0/fllme/models/model.py +16 -0
- fllme-0.2.0/fllme/models/output/__init__.py +28 -0
- fllme-0.2.0/fllme/models/output/citation.py +24 -0
- fllme-0.2.0/fllme/models/output/main.py +26 -0
- fllme-0.2.0/fllme/models/output/safety.py +33 -0
- fllme-0.2.0/fllme/models/output/stream.py +20 -0
- fllme-0.2.0/fllme/models/output/usage.py +14 -0
- fllme-0.2.0/fllme/providers/__init__.py +35 -0
- fllme-0.2.0/fllme/providers/anthropic/__init__.py +4 -0
- fllme-0.2.0/fllme/providers/anthropic/vertex_v1.py +346 -0
- fllme-0.2.0/fllme/providers/anthropic/vertex_v2.py +29 -0
- fllme-0.2.0/fllme/providers/base.py +38 -0
- fllme-0.2.0/fllme/providers/gemini/__init__.py +3 -0
- fllme-0.2.0/fllme/providers/gemini/vertex_v1.py +281 -0
- fllme-0.2.0/fllme/providers/mistral/__init__.py +3 -0
- fllme-0.2.0/fllme/providers/mistral/vertex_v1.py +284 -0
- fllme-0.2.0/fllme/providers/openai/__init__.py +5 -0
- fllme-0.2.0/fllme/providers/openai/azure_v1.py +303 -0
- fllme-0.2.0/fllme/providers/openai/azure_v2.py +19 -0
- fllme-0.2.0/fllme/providers/openai/azure_v3.py +20 -0
- fllme-0.2.0/fllme/service.py +111 -0
- fllme-0.2.0/fllme/store/__init__.py +9 -0
- fllme-0.2.0/fllme/store/deployments/__init__.py +9 -0
- fllme-0.2.0/fllme/store/deployments/protocol.py +47 -0
- fllme-0.2.0/fllme/store/deployments/sqlite.py +268 -0
- fllme-0.2.0/func_llm.egg-info/PKG-INFO +257 -0
- fllme-0.2.0/func_llm.egg-info/SOURCES.txt +53 -0
- fllme-0.2.0/func_llm.egg-info/dependency_links.txt +1 -0
- fllme-0.2.0/func_llm.egg-info/requires.txt +5 -0
- fllme-0.2.0/func_llm.egg-info/top_level.txt +1 -0
- fllme-0.2.0/pyproject.toml +43 -0
- fllme-0.2.0/tests/__init__.py +0 -0
- fllme-0.2.0/tests/test_auth.py +201 -0
- fllme-0.2.0/tests/test_gemini_adapter.py +505 -0
- fllme-0.2.0/tests/test_generate.py +34 -0
- fllme-0.2.0/tests/test_models.py +95 -0
- fllme-0.2.0/tests/test_openai_adapter.py +581 -0
- fllme-0.2.0/tests/test_service.py +177 -0
- fllme-0.2.0/tests/test_store_sqlite.py +214 -0
- fllme-0.2.0/uv.lock +1386 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
name: "Publish on PyPi"
|
|
2
|
+
on:
|
|
3
|
+
workflow_call:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v[0-9]+.[0-9]+.[0-9]+"
|
|
7
|
+
jobs:
|
|
8
|
+
publish:
|
|
9
|
+
environment: "pypi"
|
|
10
|
+
permissions:
|
|
11
|
+
id-token: write
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
- uses: actions/setup-python@v5
|
|
16
|
+
with:
|
|
17
|
+
python-version: "3.13"
|
|
18
|
+
- run: pip install uv
|
|
19
|
+
- run: uv build
|
|
20
|
+
- run: uv publish
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
name: "Release"
|
|
2
|
+
on:
|
|
3
|
+
workflow_dispatch:
|
|
4
|
+
permissions:
|
|
5
|
+
contents: write
|
|
6
|
+
id-token: write
|
|
7
|
+
jobs:
|
|
8
|
+
tests:
|
|
9
|
+
uses: ./.github/workflows/tests.yml
|
|
10
|
+
release:
|
|
11
|
+
environment: "release"
|
|
12
|
+
needs:
|
|
13
|
+
- tests
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
- uses: actions/setup-python@v5
|
|
18
|
+
with:
|
|
19
|
+
python-version: "3.13"
|
|
20
|
+
- run: pip install uv
|
|
21
|
+
- id: readVersion
|
|
22
|
+
run: echo "value=$(uv version --short)" >> "$GITHUB_OUTPUT"
|
|
23
|
+
- run: git config --global user.name 'OPSIE.DEV'
|
|
24
|
+
- run: git config --global user.email "opsie-dev@users.noreply.github.com"
|
|
25
|
+
- run: git tag v${{ steps.readVersion.outputs.value }}
|
|
26
|
+
- run: git push origin v${{ steps.readVersion.outputs.value }}
|
|
27
|
+
publish:
|
|
28
|
+
needs:
|
|
29
|
+
- release
|
|
30
|
+
permissions:
|
|
31
|
+
contents: read
|
|
32
|
+
id-token: write
|
|
33
|
+
uses: ./.github/workflows/pypi.yml
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
name: "Tests"
|
|
2
|
+
on:
|
|
3
|
+
pull_request:
|
|
4
|
+
push:
|
|
5
|
+
workflow_call:
|
|
6
|
+
jobs:
|
|
7
|
+
tests:
|
|
8
|
+
runs-on: ubuntu-latest
|
|
9
|
+
strategy:
|
|
10
|
+
matrix:
|
|
11
|
+
python-version:
|
|
12
|
+
- "3.11"
|
|
13
|
+
- "3.12"
|
|
14
|
+
- "3.13"
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
- uses: actions/setup-python@v5
|
|
18
|
+
with:
|
|
19
|
+
python-version: ${{ matrix.python-version }}
|
|
20
|
+
- run: pip install uv
|
|
21
|
+
- run: uv sync
|
|
22
|
+
- run: uv run ruff format --check
|
|
23
|
+
- run: uv run mypy --strict fllme/ tests/
|
|
24
|
+
- run: uv run pytest tests/
|
fllme-0.2.0/.gitignore
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.13
|
fllme-0.2.0/Makefile
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
SRC := fllme
|
|
2
|
+
TESTS := tests
|
|
3
|
+
|
|
4
|
+
release:
|
|
5
|
+
git tag v$$(uv version --short)
|
|
6
|
+
git push origin v$$(uv version --short)
|
|
7
|
+
|
|
8
|
+
ci-dependencies:
|
|
9
|
+
pip install uv
|
|
10
|
+
uv sync --no-install-project --all-extras
|
|
11
|
+
|
|
12
|
+
ci-test:
|
|
13
|
+
uv run ruff format --check
|
|
14
|
+
uv run ruff check
|
|
15
|
+
uv run mypy --strict $(SRC) $(TESTS)
|
|
16
|
+
uv run pytest $(TESTS)
|
|
17
|
+
|
|
18
|
+
ci-build:
|
|
19
|
+
uv build
|
|
20
|
+
|
|
21
|
+
ci-publish:
|
|
22
|
+
uv publish --index "va-pypi"
|
fllme-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fllme
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: A functionnal oriented Python CLI for LLM calling with multi provider and multi model
|
|
5
|
+
Author-email: Romain BLANCHARD <romain.blanchard.math@gmail.com>
|
|
6
|
+
Requires-Python: >=3.11
|
|
7
|
+
Requires-Dist: aiosqlite>=0.20.0
|
|
8
|
+
Requires-Dist: google-auth>=2.55.1
|
|
9
|
+
Requires-Dist: httpx>=0.28.1
|
|
10
|
+
Requires-Dist: pydantic>=2.13.4
|
|
11
|
+
Requires-Dist: requests>=2.32.4
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# Func-LLM
|
|
15
|
+
|
|
16
|
+
A functional-oriented Python library for LLM calling with multi-provider and multi-model support.
|
|
17
|
+
|
|
18
|
+
Provides a unified, provider-agnostic interface over **Anthropic**, **Gemini**, **Mistral** (Vertex AI), and **OpenAI** (Azure) through a single data-driven configuration layer.
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
import asyncio
|
|
24
|
+
import fllme
|
|
25
|
+
|
|
26
|
+
async def main():
|
|
27
|
+
# 1. Create the service (default SQLite backend)
|
|
28
|
+
service = await fllme.DeploymentService.from_sqlite("deployments.db")
|
|
29
|
+
fllme.configure(service)
|
|
30
|
+
|
|
31
|
+
# 2. Register a model and its deployment
|
|
32
|
+
await service.add_model(fllme.LLMModel(
|
|
33
|
+
id="claude-sonnet-4",
|
|
34
|
+
name="Claude Sonnet 4",
|
|
35
|
+
provider=fllme.Provider.ANTHROPIC,
|
|
36
|
+
))
|
|
37
|
+
await service.add_deployment(fllme.Deployment(
|
|
38
|
+
id="claude-vertex-euw1",
|
|
39
|
+
url="https://europe-west1-aiplatform.googleapis.com/v1/projects/my-project/...",
|
|
40
|
+
model_id="claude-sonnet-4",
|
|
41
|
+
adapter=fllme.AdapterType.ANTHROPIC_VERTEX_V1,
|
|
42
|
+
auth_id="google_adc",
|
|
43
|
+
))
|
|
44
|
+
|
|
45
|
+
# 3. Generate
|
|
46
|
+
gen_input = fllme.GenerationInput(
|
|
47
|
+
model="claude-sonnet-4",
|
|
48
|
+
conversation=[fllme.Message(source="user", contents=[...])],
|
|
49
|
+
)
|
|
50
|
+
output = await fllme.generate(gen_input)
|
|
51
|
+
|
|
52
|
+
asyncio.run(main())
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Architecture
|
|
56
|
+
|
|
57
|
+
The design is **data-driven**: models, deployments, and auth are stored as data (not coded as classes). Users register entries in a database; the library resolves them at generation time.
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
LLMModel ──┐
|
|
61
|
+
├── DeploymentService ── generate()
|
|
62
|
+
Deployment ─┤ │
|
|
63
|
+
│ ├── resolves model → deployment → adapter
|
|
64
|
+
AuthPrinciple ┘ └── resolves auth → headers
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
The flow: `GenerationInput` → `DeploymentService` resolves the deployment → `Adapter` serializes → HTTP call → `Adapter` deserializes → `GenerationOutput`.
|
|
68
|
+
|
|
69
|
+
## Project Structure
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
fllme/
|
|
73
|
+
models/
|
|
74
|
+
model.py # LLMModel (id, name)
|
|
75
|
+
deployment.py # AdapterType enum, Deployment (url, model_id, adapter, auth_id)
|
|
76
|
+
auth.py # AuthPrinciple, built-in principles (google_adc, api_key)
|
|
77
|
+
message.py # Message, Content blocks (text, media, tool calls, thinking, errors)
|
|
78
|
+
input/
|
|
79
|
+
main.py # ThinkingLevel, LLMConfig, BasicOutputType, GenerationInput
|
|
80
|
+
tools.py # ToolsCallingMode, Tool, ToolsConfig
|
|
81
|
+
image.py # Ratio, Resolution, PersonGeneration, MimeType, ImageConfig
|
|
82
|
+
output/
|
|
83
|
+
main.py # FinishReason, GenerationOutput
|
|
84
|
+
usage.py # CacheUsage, Usage
|
|
85
|
+
citation.py # CitationType, TextSpan, Citation
|
|
86
|
+
safety.py # SafetyCategory, SafetySeverity, SafetyRating, SafetyResult
|
|
87
|
+
stream.py # StreamEventType, TextDelta, ThinkingDelta, StreamDelta
|
|
88
|
+
auth/
|
|
89
|
+
protocol.py # AuthResolver protocol
|
|
90
|
+
google_adc.py # Google ADC resolver (asyncio.to_thread wrapping google.auth)
|
|
91
|
+
api_key.py # API key resolver (reads env var, returns header)
|
|
92
|
+
store/
|
|
93
|
+
deployments/
|
|
94
|
+
protocol.py # ModelRepository, DeploymentRepository, AuthRepository protocols
|
|
95
|
+
sqlite.py # SQLiteStore — default async SQLite implementation
|
|
96
|
+
providers/
|
|
97
|
+
base.py # Adapter protocol (serialize, parse_stream)
|
|
98
|
+
anthropic/vertex_v1.py
|
|
99
|
+
gemini/vertex_v1.py
|
|
100
|
+
mistral/vertex_v1.py
|
|
101
|
+
openai/azure_v1.py
|
|
102
|
+
service.py # DeploymentService — composes repos + auth resolution
|
|
103
|
+
generate.py # configure(), generate() — main entry point
|
|
104
|
+
media.py # MediaResolver protocol, resolve_references(), store_media()
|
|
105
|
+
http.py # Shared httpx async client
|
|
106
|
+
errors.py # FuncLLMError hierarchy
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Domain Models
|
|
110
|
+
|
|
111
|
+
### LLMModel
|
|
112
|
+
|
|
113
|
+
Simple identity — an ID, a display name, and the provider:
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
LLMModel(id="claude-sonnet-4", name="Claude Sonnet 4", provider=Provider.ANTHROPIC)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Deployment
|
|
120
|
+
|
|
121
|
+
Links a model to a concrete cloud endpoint, an adapter for serialization, and an auth method:
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
Deployment(
|
|
125
|
+
id="claude-vertex-euw1",
|
|
126
|
+
url="https://europe-west1-aiplatform.googleapis.com/v1/...",
|
|
127
|
+
model_id="claude-sonnet-4",
|
|
128
|
+
adapter=AdapterType.ANTHROPIC_VERTEX_V1,
|
|
129
|
+
auth_id="google_adc",
|
|
130
|
+
)
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### AuthPrinciple
|
|
134
|
+
|
|
135
|
+
Data-driven auth configuration. Declares which resolver to use, which env vars are required, and resolver-specific config:
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
AuthPrinciple(
|
|
139
|
+
id="azure_openai_key",
|
|
140
|
+
name="Azure OpenAI API Key",
|
|
141
|
+
resolver_id="api_key",
|
|
142
|
+
required_env_vars=["AZURE_OPENAI_KEY"],
|
|
143
|
+
config={"header_name": "api-key"},
|
|
144
|
+
)
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Built-in principles (`google_adc`, `api_key`) are pre-seeded in the default SQLite store.
|
|
148
|
+
|
|
149
|
+
## IO Models
|
|
150
|
+
|
|
151
|
+
### Input
|
|
152
|
+
|
|
153
|
+
`GenerationInput` is the unified request object. It carries:
|
|
154
|
+
|
|
155
|
+
- **model** — model ID string (resolved to a `Deployment` by `DeploymentService`)
|
|
156
|
+
- **conversation** — list of `Message` with typed content blocks (text, media, tool calls, tool responses, thinking, errors). Each message has a `source` field (`user | model | system | tool`)
|
|
157
|
+
- **llm_config** — generation parameters (temperature, top_p, top_k, max_tokens, stop sequences, thinking level)
|
|
158
|
+
- **tool_config** — function calling tools, mode (auto/any/none), parallel calling
|
|
159
|
+
- **image_config** — image generation settings (ratio, resolution, person generation, mime type)
|
|
160
|
+
- **output_type** — text, image, hybrid, or a Pydantic `BaseModel` for structured output
|
|
161
|
+
- **system_prompt** — extracted from provider-specific locations into a dedicated field
|
|
162
|
+
- **stream** — whether to stream the response
|
|
163
|
+
|
|
164
|
+
### Output
|
|
165
|
+
|
|
166
|
+
`GenerationOutput` is the unified response object. It carries:
|
|
167
|
+
|
|
168
|
+
- **message** — the model's response as a `Message` (directly appendable to conversation history)
|
|
169
|
+
- **finish_reason** — why generation stopped (stop, max_tokens, tool_use, content_filter, error)
|
|
170
|
+
- **usage** — token breakdown (input, output, thinking, cache read/creation)
|
|
171
|
+
- **citations** — grounding annotations (URL, document, search) with text spans and confidence
|
|
172
|
+
- **safety** — content filtering results with per-category ratings and refusal details
|
|
173
|
+
|
|
174
|
+
### Streaming
|
|
175
|
+
|
|
176
|
+
During streaming, the library yields unified `StreamDelta` events (`TextDelta` or `ThinkingDelta`) on the fly, then returns the aggregated `GenerationOutput` at the end.
|
|
177
|
+
|
|
178
|
+
### Media Resolution
|
|
179
|
+
|
|
180
|
+
`MediaContent` blocks can carry three source types: `Base64Source`, `UrlSource`, or `ReferenceSource`. References are opaque user-domain IDs (e.g. a document-management key) that providers cannot consume directly.
|
|
181
|
+
|
|
182
|
+
The `MediaResolver` protocol bridges this gap:
|
|
183
|
+
|
|
184
|
+
```python
|
|
185
|
+
class MediaResolver(Protocol):
|
|
186
|
+
async def resolve(self, references: list[ReferenceSource]) -> list[Base64Source | UrlSource]: ...
|
|
187
|
+
async def store(self, media: list[Base64Source | UrlSource]) -> list[ReferenceSource]: ...
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
- **`resolve`** — outbound: converts user-domain IDs into provider-sendable sources before serialization
|
|
191
|
+
- **`store`** — inbound: uploads AI-generated media and returns user-domain IDs after deserialization
|
|
192
|
+
|
|
193
|
+
Pass an implementation to `generate()` via the `media_resolver` keyword argument. Resolution and storage are batched and applied transparently. Resolver failures are wrapped in `MediaResolutionError`.
|
|
194
|
+
|
|
195
|
+
## Deployment & Storage
|
|
196
|
+
|
|
197
|
+
### Repository Protocols
|
|
198
|
+
|
|
199
|
+
Three separate async protocols define data access — users can swap the storage backend by implementing them:
|
|
200
|
+
|
|
201
|
+
- `ModelRepository` — CRUD for `LLMModel`
|
|
202
|
+
- `DeploymentRepository` — CRUD for `Deployment`, plus `get_for_model()`
|
|
203
|
+
- `AuthRepository` — CRUD for `AuthPrinciple`
|
|
204
|
+
|
|
205
|
+
### SQLiteStore (default)
|
|
206
|
+
|
|
207
|
+
`SQLiteStore` is the built-in implementation using `aiosqlite`. It manages three tables with foreign keys (deployments cascade-delete when a model is removed) and pre-seeds built-in auth principles.
|
|
208
|
+
|
|
209
|
+
```python
|
|
210
|
+
store = await SQLiteStore.create("deployments.db") # or ":memory:"
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### DeploymentService
|
|
214
|
+
|
|
215
|
+
Composes the three repositories and auth resolution into a high-level API:
|
|
216
|
+
|
|
217
|
+
```python
|
|
218
|
+
service = await DeploymentService.from_sqlite("deployments.db")
|
|
219
|
+
|
|
220
|
+
await service.add_model(model)
|
|
221
|
+
await service.add_deployment(deployment) # validates model_id + auth_id exist
|
|
222
|
+
deployment = await service.resolve_deployment("claude-sonnet-4")
|
|
223
|
+
headers = await service.get_auth_headers(deployment)
|
|
224
|
+
issues = await service.check_deployment_ready(deployment) # missing env vars, etc.
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## Auth System
|
|
228
|
+
|
|
229
|
+
Auth is data-driven and extensible. Each `AuthPrinciple` references a `resolver_id` that maps to a registered `AuthResolver`:
|
|
230
|
+
|
|
231
|
+
```python
|
|
232
|
+
class AuthResolver(Protocol):
|
|
233
|
+
async def get_headers(self, principle: AuthPrinciple) -> dict[str, str]: ...
|
|
234
|
+
def check_env(self, principle: AuthPrinciple) -> list[str]: ...
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Built-in resolvers:
|
|
238
|
+
|
|
239
|
+
| Resolver ID | Resolver | Description |
|
|
240
|
+
|---|---|---|
|
|
241
|
+
| `google_adc` | `GoogleADCResolver` | Google Application Default Credentials via `google.auth` |
|
|
242
|
+
| `api_key` | `ApiKeyResolver` | Reads an env var, returns it as a header |
|
|
243
|
+
|
|
244
|
+
Register custom resolvers:
|
|
245
|
+
|
|
246
|
+
```python
|
|
247
|
+
fllme.register_resolver("my_oauth", MyOAuthResolver())
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## Adapters
|
|
251
|
+
|
|
252
|
+
Each provider/cloud/version combination has its own adapter implementing the `Adapter` protocol (`serialize`, `parse_stream`).
|
|
253
|
+
|
|
254
|
+
| AdapterType | Provider | Cloud |
|
|
255
|
+
|---|---|---|
|
|
256
|
+
| `anthropic_vertex_v1` | Anthropic | Vertex AI |
|
|
257
|
+
| `gemini_vertex_v1` | Gemini | Vertex AI |
|
|
258
|
+
| `mistral_vertex_v1` | Mistral | Vertex AI |
|
|
259
|
+
| `openai_azure_v1` | OpenAI | Azure |
|
|
260
|
+
|
|
261
|
+
New API versions get new enum values (e.g., `anthropic_vertex_v2`). Adapters are looked up via `get_adapter(adapter_type)`.
|
|
262
|
+
|
|
263
|
+
## Requirements
|
|
264
|
+
|
|
265
|
+
- Python >= 3.13
|
|
266
|
+
- pydantic >= 2.13
|
|
267
|
+
- httpx >= 0.28
|
|
268
|
+
- aiosqlite >= 0.20
|
|
269
|
+
- google-auth >= 2.55
|
|
270
|
+
- requests >= 2.32
|
fllme-0.2.0/README.md
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
# Func-LLM
|
|
2
|
+
|
|
3
|
+
A functional-oriented Python library for LLM calling with multi-provider and multi-model support.
|
|
4
|
+
|
|
5
|
+
Provides a unified, provider-agnostic interface over **Anthropic**, **Gemini**, **Mistral** (Vertex AI), and **OpenAI** (Azure) through a single data-driven configuration layer.
|
|
6
|
+
|
|
7
|
+
## Quick Start
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
import asyncio
|
|
11
|
+
import fllme
|
|
12
|
+
|
|
13
|
+
async def main():
|
|
14
|
+
# 1. Create the service (default SQLite backend)
|
|
15
|
+
service = await fllme.DeploymentService.from_sqlite("deployments.db")
|
|
16
|
+
fllme.configure(service)
|
|
17
|
+
|
|
18
|
+
# 2. Register a model and its deployment
|
|
19
|
+
await service.add_model(fllme.LLMModel(
|
|
20
|
+
id="claude-sonnet-4",
|
|
21
|
+
name="Claude Sonnet 4",
|
|
22
|
+
provider=fllme.Provider.ANTHROPIC,
|
|
23
|
+
))
|
|
24
|
+
await service.add_deployment(fllme.Deployment(
|
|
25
|
+
id="claude-vertex-euw1",
|
|
26
|
+
url="https://europe-west1-aiplatform.googleapis.com/v1/projects/my-project/...",
|
|
27
|
+
model_id="claude-sonnet-4",
|
|
28
|
+
adapter=fllme.AdapterType.ANTHROPIC_VERTEX_V1,
|
|
29
|
+
auth_id="google_adc",
|
|
30
|
+
))
|
|
31
|
+
|
|
32
|
+
# 3. Generate
|
|
33
|
+
gen_input = fllme.GenerationInput(
|
|
34
|
+
model="claude-sonnet-4",
|
|
35
|
+
conversation=[fllme.Message(source="user", contents=[...])],
|
|
36
|
+
)
|
|
37
|
+
output = await fllme.generate(gen_input)
|
|
38
|
+
|
|
39
|
+
asyncio.run(main())
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Architecture
|
|
43
|
+
|
|
44
|
+
The design is **data-driven**: models, deployments, and auth are stored as data (not coded as classes). Users register entries in a database; the library resolves them at generation time.
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
LLMModel ──┐
|
|
48
|
+
├── DeploymentService ── generate()
|
|
49
|
+
Deployment ─┤ │
|
|
50
|
+
│ ├── resolves model → deployment → adapter
|
|
51
|
+
AuthPrinciple ┘ └── resolves auth → headers
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The flow: `GenerationInput` → `DeploymentService` resolves the deployment → `Adapter` serializes → HTTP call → `Adapter` deserializes → `GenerationOutput`.
|
|
55
|
+
|
|
56
|
+
## Project Structure
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
fllme/
|
|
60
|
+
models/
|
|
61
|
+
model.py # LLMModel (id, name)
|
|
62
|
+
deployment.py # AdapterType enum, Deployment (url, model_id, adapter, auth_id)
|
|
63
|
+
auth.py # AuthPrinciple, built-in principles (google_adc, api_key)
|
|
64
|
+
message.py # Message, Content blocks (text, media, tool calls, thinking, errors)
|
|
65
|
+
input/
|
|
66
|
+
main.py # ThinkingLevel, LLMConfig, BasicOutputType, GenerationInput
|
|
67
|
+
tools.py # ToolsCallingMode, Tool, ToolsConfig
|
|
68
|
+
image.py # Ratio, Resolution, PersonGeneration, MimeType, ImageConfig
|
|
69
|
+
output/
|
|
70
|
+
main.py # FinishReason, GenerationOutput
|
|
71
|
+
usage.py # CacheUsage, Usage
|
|
72
|
+
citation.py # CitationType, TextSpan, Citation
|
|
73
|
+
safety.py # SafetyCategory, SafetySeverity, SafetyRating, SafetyResult
|
|
74
|
+
stream.py # StreamEventType, TextDelta, ThinkingDelta, StreamDelta
|
|
75
|
+
auth/
|
|
76
|
+
protocol.py # AuthResolver protocol
|
|
77
|
+
google_adc.py # Google ADC resolver (asyncio.to_thread wrapping google.auth)
|
|
78
|
+
api_key.py # API key resolver (reads env var, returns header)
|
|
79
|
+
store/
|
|
80
|
+
deployments/
|
|
81
|
+
protocol.py # ModelRepository, DeploymentRepository, AuthRepository protocols
|
|
82
|
+
sqlite.py # SQLiteStore — default async SQLite implementation
|
|
83
|
+
providers/
|
|
84
|
+
base.py # Adapter protocol (serialize, parse_stream)
|
|
85
|
+
anthropic/vertex_v1.py
|
|
86
|
+
gemini/vertex_v1.py
|
|
87
|
+
mistral/vertex_v1.py
|
|
88
|
+
openai/azure_v1.py
|
|
89
|
+
service.py # DeploymentService — composes repos + auth resolution
|
|
90
|
+
generate.py # configure(), generate() — main entry point
|
|
91
|
+
media.py # MediaResolver protocol, resolve_references(), store_media()
|
|
92
|
+
http.py # Shared httpx async client
|
|
93
|
+
errors.py # FuncLLMError hierarchy
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Domain Models
|
|
97
|
+
|
|
98
|
+
### LLMModel
|
|
99
|
+
|
|
100
|
+
Simple identity — an ID, a display name, and the provider:
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
LLMModel(id="claude-sonnet-4", name="Claude Sonnet 4", provider=Provider.ANTHROPIC)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Deployment
|
|
107
|
+
|
|
108
|
+
Links a model to a concrete cloud endpoint, an adapter for serialization, and an auth method:
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
Deployment(
|
|
112
|
+
id="claude-vertex-euw1",
|
|
113
|
+
url="https://europe-west1-aiplatform.googleapis.com/v1/...",
|
|
114
|
+
model_id="claude-sonnet-4",
|
|
115
|
+
adapter=AdapterType.ANTHROPIC_VERTEX_V1,
|
|
116
|
+
auth_id="google_adc",
|
|
117
|
+
)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### AuthPrinciple
|
|
121
|
+
|
|
122
|
+
Data-driven auth configuration. Declares which resolver to use, which env vars are required, and resolver-specific config:
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
AuthPrinciple(
|
|
126
|
+
id="azure_openai_key",
|
|
127
|
+
name="Azure OpenAI API Key",
|
|
128
|
+
resolver_id="api_key",
|
|
129
|
+
required_env_vars=["AZURE_OPENAI_KEY"],
|
|
130
|
+
config={"header_name": "api-key"},
|
|
131
|
+
)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Built-in principles (`google_adc`, `api_key`) are pre-seeded in the default SQLite store.
|
|
135
|
+
|
|
136
|
+
## IO Models
|
|
137
|
+
|
|
138
|
+
### Input
|
|
139
|
+
|
|
140
|
+
`GenerationInput` is the unified request object. It carries:
|
|
141
|
+
|
|
142
|
+
- **model** — model ID string (resolved to a `Deployment` by `DeploymentService`)
|
|
143
|
+
- **conversation** — list of `Message` with typed content blocks (text, media, tool calls, tool responses, thinking, errors). Each message has a `source` field (`user | model | system | tool`)
|
|
144
|
+
- **llm_config** — generation parameters (temperature, top_p, top_k, max_tokens, stop sequences, thinking level)
|
|
145
|
+
- **tool_config** — function calling tools, mode (auto/any/none), parallel calling
|
|
146
|
+
- **image_config** — image generation settings (ratio, resolution, person generation, mime type)
|
|
147
|
+
- **output_type** — text, image, hybrid, or a Pydantic `BaseModel` for structured output
|
|
148
|
+
- **system_prompt** — extracted from provider-specific locations into a dedicated field
|
|
149
|
+
- **stream** — whether to stream the response
|
|
150
|
+
|
|
151
|
+
### Output
|
|
152
|
+
|
|
153
|
+
`GenerationOutput` is the unified response object. It carries:
|
|
154
|
+
|
|
155
|
+
- **message** — the model's response as a `Message` (directly appendable to conversation history)
|
|
156
|
+
- **finish_reason** — why generation stopped (stop, max_tokens, tool_use, content_filter, error)
|
|
157
|
+
- **usage** — token breakdown (input, output, thinking, cache read/creation)
|
|
158
|
+
- **citations** — grounding annotations (URL, document, search) with text spans and confidence
|
|
159
|
+
- **safety** — content filtering results with per-category ratings and refusal details
|
|
160
|
+
|
|
161
|
+
### Streaming
|
|
162
|
+
|
|
163
|
+
During streaming, the library yields unified `StreamDelta` events (`TextDelta` or `ThinkingDelta`) on the fly, then returns the aggregated `GenerationOutput` at the end.
|
|
164
|
+
|
|
165
|
+
### Media Resolution
|
|
166
|
+
|
|
167
|
+
`MediaContent` blocks can carry three source types: `Base64Source`, `UrlSource`, or `ReferenceSource`. References are opaque user-domain IDs (e.g. a document-management key) that providers cannot consume directly.
|
|
168
|
+
|
|
169
|
+
The `MediaResolver` protocol bridges this gap:
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
class MediaResolver(Protocol):
|
|
173
|
+
async def resolve(self, references: list[ReferenceSource]) -> list[Base64Source | UrlSource]: ...
|
|
174
|
+
async def store(self, media: list[Base64Source | UrlSource]) -> list[ReferenceSource]: ...
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
- **`resolve`** — outbound: converts user-domain IDs into provider-sendable sources before serialization
|
|
178
|
+
- **`store`** — inbound: uploads AI-generated media and returns user-domain IDs after deserialization
|
|
179
|
+
|
|
180
|
+
Pass an implementation to `generate()` via the `media_resolver` keyword argument. Resolution and storage are batched and applied transparently. Resolver failures are wrapped in `MediaResolutionError`.
|
|
181
|
+
|
|
182
|
+
## Deployment & Storage
|
|
183
|
+
|
|
184
|
+
### Repository Protocols
|
|
185
|
+
|
|
186
|
+
Three separate async protocols define data access — users can swap the storage backend by implementing them:
|
|
187
|
+
|
|
188
|
+
- `ModelRepository` — CRUD for `LLMModel`
|
|
189
|
+
- `DeploymentRepository` — CRUD for `Deployment`, plus `get_for_model()`
|
|
190
|
+
- `AuthRepository` — CRUD for `AuthPrinciple`
|
|
191
|
+
|
|
192
|
+
### SQLiteStore (default)
|
|
193
|
+
|
|
194
|
+
`SQLiteStore` is the built-in implementation using `aiosqlite`. It manages three tables with foreign keys (deployments cascade-delete when a model is removed) and pre-seeds built-in auth principles.
|
|
195
|
+
|
|
196
|
+
```python
|
|
197
|
+
store = await SQLiteStore.create("deployments.db") # or ":memory:"
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### DeploymentService
|
|
201
|
+
|
|
202
|
+
Composes the three repositories and auth resolution into a high-level API:
|
|
203
|
+
|
|
204
|
+
```python
|
|
205
|
+
service = await DeploymentService.from_sqlite("deployments.db")
|
|
206
|
+
|
|
207
|
+
await service.add_model(model)
|
|
208
|
+
await service.add_deployment(deployment) # validates model_id + auth_id exist
|
|
209
|
+
deployment = await service.resolve_deployment("claude-sonnet-4")
|
|
210
|
+
headers = await service.get_auth_headers(deployment)
|
|
211
|
+
issues = await service.check_deployment_ready(deployment) # missing env vars, etc.
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## Auth System
|
|
215
|
+
|
|
216
|
+
Auth is data-driven and extensible. Each `AuthPrinciple` references a `resolver_id` that maps to a registered `AuthResolver`:
|
|
217
|
+
|
|
218
|
+
```python
|
|
219
|
+
class AuthResolver(Protocol):
|
|
220
|
+
async def get_headers(self, principle: AuthPrinciple) -> dict[str, str]: ...
|
|
221
|
+
def check_env(self, principle: AuthPrinciple) -> list[str]: ...
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Built-in resolvers:
|
|
225
|
+
|
|
226
|
+
| Resolver ID | Resolver | Description |
|
|
227
|
+
|---|---|---|
|
|
228
|
+
| `google_adc` | `GoogleADCResolver` | Google Application Default Credentials via `google.auth` |
|
|
229
|
+
| `api_key` | `ApiKeyResolver` | Reads an env var, returns it as a header |
|
|
230
|
+
|
|
231
|
+
Register custom resolvers:
|
|
232
|
+
|
|
233
|
+
```python
|
|
234
|
+
fllme.register_resolver("my_oauth", MyOAuthResolver())
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## Adapters
|
|
238
|
+
|
|
239
|
+
Each provider/cloud/version combination has its own adapter implementing the `Adapter` protocol (`serialize`, `parse_stream`).
|
|
240
|
+
|
|
241
|
+
| AdapterType | Provider | Cloud |
|
|
242
|
+
|---|---|---|
|
|
243
|
+
| `anthropic_vertex_v1` | Anthropic | Vertex AI |
|
|
244
|
+
| `gemini_vertex_v1` | Gemini | Vertex AI |
|
|
245
|
+
| `mistral_vertex_v1` | Mistral | Vertex AI |
|
|
246
|
+
| `openai_azure_v1` | OpenAI | Azure |
|
|
247
|
+
|
|
248
|
+
New API versions get new enum values (e.g., `anthropic_vertex_v2`). Adapters are looked up via `get_adapter(adapter_type)`.
|
|
249
|
+
|
|
250
|
+
## Requirements
|
|
251
|
+
|
|
252
|
+
- Python >= 3.13
|
|
253
|
+
- pydantic >= 2.13
|
|
254
|
+
- httpx >= 0.28
|
|
255
|
+
- aiosqlite >= 0.20
|
|
256
|
+
- google-auth >= 2.55
|
|
257
|
+
- requests >= 2.32
|