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.
Files changed (66) hide show
  1. fllme-0.2.0/.github/workflows/pypi.yml +20 -0
  2. fllme-0.2.0/.github/workflows/release.yml +33 -0
  3. fllme-0.2.0/.github/workflows/tests.yml +24 -0
  4. fllme-0.2.0/.gitignore +11 -0
  5. fllme-0.2.0/.python-version +1 -0
  6. fllme-0.2.0/Makefile +22 -0
  7. fllme-0.2.0/PKG-INFO +270 -0
  8. fllme-0.2.0/README.md +257 -0
  9. fllme-0.2.0/fllme/__init__.py +87 -0
  10. fllme-0.2.0/fllme/__main__.py +0 -0
  11. fllme-0.2.0/fllme/auth/__init__.py +42 -0
  12. fllme-0.2.0/fllme/auth/api_key.py +32 -0
  13. fllme-0.2.0/fllme/auth/google_adc.py +52 -0
  14. fllme-0.2.0/fllme/auth/protocol.py +11 -0
  15. fllme-0.2.0/fllme/errors.py +35 -0
  16. fllme-0.2.0/fllme/generate.py +86 -0
  17. fllme-0.2.0/fllme/http.py +19 -0
  18. fllme-0.2.0/fllme/media.py +99 -0
  19. fllme-0.2.0/fllme/models/__init__.py +103 -0
  20. fllme-0.2.0/fllme/models/auth.py +28 -0
  21. fllme-0.2.0/fllme/models/deployment.py +21 -0
  22. fllme-0.2.0/fllme/models/input/__init__.py +31 -0
  23. fllme-0.2.0/fllme/models/input/image.py +37 -0
  24. fllme-0.2.0/fllme/models/input/main.py +45 -0
  25. fllme-0.2.0/fllme/models/input/tools.py +21 -0
  26. fllme-0.2.0/fllme/models/message.py +95 -0
  27. fllme-0.2.0/fllme/models/model.py +16 -0
  28. fllme-0.2.0/fllme/models/output/__init__.py +28 -0
  29. fllme-0.2.0/fllme/models/output/citation.py +24 -0
  30. fllme-0.2.0/fllme/models/output/main.py +26 -0
  31. fllme-0.2.0/fllme/models/output/safety.py +33 -0
  32. fllme-0.2.0/fllme/models/output/stream.py +20 -0
  33. fllme-0.2.0/fllme/models/output/usage.py +14 -0
  34. fllme-0.2.0/fllme/providers/__init__.py +35 -0
  35. fllme-0.2.0/fllme/providers/anthropic/__init__.py +4 -0
  36. fllme-0.2.0/fllme/providers/anthropic/vertex_v1.py +346 -0
  37. fllme-0.2.0/fllme/providers/anthropic/vertex_v2.py +29 -0
  38. fllme-0.2.0/fllme/providers/base.py +38 -0
  39. fllme-0.2.0/fllme/providers/gemini/__init__.py +3 -0
  40. fllme-0.2.0/fllme/providers/gemini/vertex_v1.py +281 -0
  41. fllme-0.2.0/fllme/providers/mistral/__init__.py +3 -0
  42. fllme-0.2.0/fllme/providers/mistral/vertex_v1.py +284 -0
  43. fllme-0.2.0/fllme/providers/openai/__init__.py +5 -0
  44. fllme-0.2.0/fllme/providers/openai/azure_v1.py +303 -0
  45. fllme-0.2.0/fllme/providers/openai/azure_v2.py +19 -0
  46. fllme-0.2.0/fllme/providers/openai/azure_v3.py +20 -0
  47. fllme-0.2.0/fllme/service.py +111 -0
  48. fllme-0.2.0/fllme/store/__init__.py +9 -0
  49. fllme-0.2.0/fllme/store/deployments/__init__.py +9 -0
  50. fllme-0.2.0/fllme/store/deployments/protocol.py +47 -0
  51. fllme-0.2.0/fllme/store/deployments/sqlite.py +268 -0
  52. fllme-0.2.0/func_llm.egg-info/PKG-INFO +257 -0
  53. fllme-0.2.0/func_llm.egg-info/SOURCES.txt +53 -0
  54. fllme-0.2.0/func_llm.egg-info/dependency_links.txt +1 -0
  55. fllme-0.2.0/func_llm.egg-info/requires.txt +5 -0
  56. fllme-0.2.0/func_llm.egg-info/top_level.txt +1 -0
  57. fllme-0.2.0/pyproject.toml +43 -0
  58. fllme-0.2.0/tests/__init__.py +0 -0
  59. fllme-0.2.0/tests/test_auth.py +201 -0
  60. fllme-0.2.0/tests/test_gemini_adapter.py +505 -0
  61. fllme-0.2.0/tests/test_generate.py +34 -0
  62. fllme-0.2.0/tests/test_models.py +95 -0
  63. fllme-0.2.0/tests/test_openai_adapter.py +581 -0
  64. fllme-0.2.0/tests/test_service.py +177 -0
  65. fllme-0.2.0/tests/test_store_sqlite.py +214 -0
  66. 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,11 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[codz]
4
+ *$py.class
5
+
6
+ .claude
7
+ .serena
8
+ .venv
9
+ CLAUDE.md
10
+ fllme.egg-info/
11
+ wip/
@@ -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