fllme 0.2.0__py3-none-any.whl
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/__init__.py +87 -0
- fllme/__main__.py +0 -0
- fllme/auth/__init__.py +42 -0
- fllme/auth/api_key.py +32 -0
- fllme/auth/google_adc.py +52 -0
- fllme/auth/protocol.py +11 -0
- fllme/errors.py +35 -0
- fllme/generate.py +86 -0
- fllme/http.py +19 -0
- fllme/media.py +99 -0
- fllme/models/__init__.py +103 -0
- fllme/models/auth.py +28 -0
- fllme/models/deployment.py +21 -0
- fllme/models/input/__init__.py +31 -0
- fllme/models/input/image.py +37 -0
- fllme/models/input/main.py +45 -0
- fllme/models/input/tools.py +21 -0
- fllme/models/message.py +95 -0
- fllme/models/model.py +16 -0
- fllme/models/output/__init__.py +28 -0
- fllme/models/output/citation.py +24 -0
- fllme/models/output/main.py +26 -0
- fllme/models/output/safety.py +33 -0
- fllme/models/output/stream.py +20 -0
- fllme/models/output/usage.py +14 -0
- fllme/providers/__init__.py +35 -0
- fllme/providers/anthropic/__init__.py +4 -0
- fllme/providers/anthropic/vertex_v1.py +346 -0
- fllme/providers/anthropic/vertex_v2.py +29 -0
- fllme/providers/base.py +38 -0
- fllme/providers/gemini/__init__.py +3 -0
- fllme/providers/gemini/vertex_v1.py +281 -0
- fllme/providers/mistral/__init__.py +3 -0
- fllme/providers/mistral/vertex_v1.py +284 -0
- fllme/providers/openai/__init__.py +5 -0
- fllme/providers/openai/azure_v1.py +303 -0
- fllme/providers/openai/azure_v2.py +19 -0
- fllme/providers/openai/azure_v3.py +20 -0
- fllme/service.py +111 -0
- fllme/store/__init__.py +9 -0
- fllme/store/deployments/__init__.py +9 -0
- fllme/store/deployments/protocol.py +47 -0
- fllme/store/deployments/sqlite.py +268 -0
- fllme-0.2.0.dist-info/METADATA +270 -0
- fllme-0.2.0.dist-info/RECORD +46 -0
- fllme-0.2.0.dist-info/WHEEL +4 -0
fllme/__init__.py
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
from .models import (
|
|
2
|
+
AdapterType,
|
|
3
|
+
AuthPrinciple,
|
|
4
|
+
BasicOutputType,
|
|
5
|
+
CacheUsage,
|
|
6
|
+
Citation,
|
|
7
|
+
Content,
|
|
8
|
+
Deployment,
|
|
9
|
+
FinishReason,
|
|
10
|
+
GenerationInput,
|
|
11
|
+
GenerationOutput,
|
|
12
|
+
ImageConfig,
|
|
13
|
+
LLMConfig,
|
|
14
|
+
LLMModel,
|
|
15
|
+
Provider,
|
|
16
|
+
Message,
|
|
17
|
+
SafetyResult,
|
|
18
|
+
StreamDelta,
|
|
19
|
+
TextDelta,
|
|
20
|
+
ThinkingDelta,
|
|
21
|
+
ThinkingLevel,
|
|
22
|
+
Tool,
|
|
23
|
+
ToolsConfig,
|
|
24
|
+
Usage,
|
|
25
|
+
)
|
|
26
|
+
from .auth import AuthResolver, get_resolver, register_resolver
|
|
27
|
+
from .store import AuthRepository, DeploymentRepository, ModelRepository, SQLiteStore
|
|
28
|
+
from .service import DeploymentService
|
|
29
|
+
from .providers import Adapter, get_adapter
|
|
30
|
+
from .generate import configure, generate, get_service
|
|
31
|
+
from .media import MediaResolver
|
|
32
|
+
from .errors import (
|
|
33
|
+
AuthError,
|
|
34
|
+
DeploymentNotFoundError,
|
|
35
|
+
FuncLLMError,
|
|
36
|
+
MediaResolutionError,
|
|
37
|
+
ModelNotFoundError,
|
|
38
|
+
ProviderError,
|
|
39
|
+
SerializationError,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
__all__ = [
|
|
43
|
+
"Adapter",
|
|
44
|
+
"AdapterType",
|
|
45
|
+
"AuthError",
|
|
46
|
+
"AuthPrinciple",
|
|
47
|
+
"AuthRepository",
|
|
48
|
+
"AuthResolver",
|
|
49
|
+
"BasicOutputType",
|
|
50
|
+
"CacheUsage",
|
|
51
|
+
"Citation",
|
|
52
|
+
"Content",
|
|
53
|
+
"Deployment",
|
|
54
|
+
"DeploymentNotFoundError",
|
|
55
|
+
"DeploymentRepository",
|
|
56
|
+
"DeploymentService",
|
|
57
|
+
"FinishReason",
|
|
58
|
+
"FuncLLMError",
|
|
59
|
+
"GenerationInput",
|
|
60
|
+
"GenerationOutput",
|
|
61
|
+
"ImageConfig",
|
|
62
|
+
"LLMConfig",
|
|
63
|
+
"LLMModel",
|
|
64
|
+
"MediaResolutionError",
|
|
65
|
+
"MediaResolver",
|
|
66
|
+
"Message",
|
|
67
|
+
"ModelNotFoundError",
|
|
68
|
+
"ModelRepository",
|
|
69
|
+
"Provider",
|
|
70
|
+
"ProviderError",
|
|
71
|
+
"SQLiteStore",
|
|
72
|
+
"SafetyResult",
|
|
73
|
+
"SerializationError",
|
|
74
|
+
"StreamDelta",
|
|
75
|
+
"TextDelta",
|
|
76
|
+
"ThinkingDelta",
|
|
77
|
+
"ThinkingLevel",
|
|
78
|
+
"Tool",
|
|
79
|
+
"ToolsConfig",
|
|
80
|
+
"Usage",
|
|
81
|
+
"configure",
|
|
82
|
+
"generate",
|
|
83
|
+
"get_adapter",
|
|
84
|
+
"get_resolver",
|
|
85
|
+
"get_service",
|
|
86
|
+
"register_resolver",
|
|
87
|
+
]
|
fllme/__main__.py
ADDED
|
File without changes
|
fllme/auth/__init__.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from ..errors import AuthError
|
|
6
|
+
from .api_key import ApiKeyResolver
|
|
7
|
+
from .google_adc import GoogleADCResolver
|
|
8
|
+
from .protocol import AuthResolver
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
RESOLVERS: dict[str, AuthResolver] = {}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _register_builtins() -> None:
|
|
17
|
+
register_resolver("google_adc", GoogleADCResolver())
|
|
18
|
+
register_resolver("api_key", ApiKeyResolver())
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def register_resolver(resolver_id: str, resolver: AuthResolver) -> None:
|
|
22
|
+
RESOLVERS[resolver_id] = resolver
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def get_resolver(resolver_id: str) -> AuthResolver:
|
|
26
|
+
resolver = RESOLVERS.get(resolver_id)
|
|
27
|
+
if resolver is None:
|
|
28
|
+
msg = f"Unknown auth resolver: {resolver_id!r}"
|
|
29
|
+
raise AuthError(msg)
|
|
30
|
+
return resolver
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
_register_builtins()
|
|
34
|
+
|
|
35
|
+
__all__ = [
|
|
36
|
+
"RESOLVERS",
|
|
37
|
+
"ApiKeyResolver",
|
|
38
|
+
"AuthResolver",
|
|
39
|
+
"GoogleADCResolver",
|
|
40
|
+
"get_resolver",
|
|
41
|
+
"register_resolver",
|
|
42
|
+
]
|
fllme/auth/api_key.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from ..errors import AuthError
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from ..models.auth import AuthPrinciple
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ApiKeyResolver:
|
|
13
|
+
async def get_headers(self, principle: AuthPrinciple) -> dict[str, str]:
|
|
14
|
+
if not principle.required_env_vars:
|
|
15
|
+
msg = "API key auth requires at least one env var in required_env_vars"
|
|
16
|
+
raise AuthError(msg)
|
|
17
|
+
|
|
18
|
+
env_var = principle.required_env_vars[0]
|
|
19
|
+
value = os.environ.get(env_var)
|
|
20
|
+
if value is None:
|
|
21
|
+
msg = f"Missing required environment variable: {env_var}"
|
|
22
|
+
raise AuthError(msg)
|
|
23
|
+
|
|
24
|
+
header_name = principle.config.get("header_name", "api-key")
|
|
25
|
+
return {header_name: value}
|
|
26
|
+
|
|
27
|
+
def check_env(self, principle: AuthPrinciple) -> list[str]:
|
|
28
|
+
missing: list[str] = []
|
|
29
|
+
for var in principle.required_env_vars:
|
|
30
|
+
if var not in os.environ:
|
|
31
|
+
missing.append(var)
|
|
32
|
+
return missing
|
fllme/auth/google_adc.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from typing import TYPE_CHECKING, Any
|
|
5
|
+
|
|
6
|
+
from ..errors import AuthError
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from ..models.auth import AuthPrinciple
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class GoogleADCResolver:
|
|
13
|
+
def __init__(self) -> None:
|
|
14
|
+
self._credentials: Any | None = None
|
|
15
|
+
self._lock = asyncio.Lock()
|
|
16
|
+
|
|
17
|
+
async def get_headers(self, principle: AuthPrinciple) -> dict[str, str]:
|
|
18
|
+
try:
|
|
19
|
+
import google.auth
|
|
20
|
+
import google.auth.transport.requests
|
|
21
|
+
except ImportError as exc:
|
|
22
|
+
msg = (
|
|
23
|
+
"google-auth is required for Google ADC authentication. "
|
|
24
|
+
"Install it with: pip install google-auth"
|
|
25
|
+
)
|
|
26
|
+
raise AuthError(msg) from exc
|
|
27
|
+
|
|
28
|
+
async with self._lock:
|
|
29
|
+
if self._credentials is None:
|
|
30
|
+
try:
|
|
31
|
+
self._credentials, _ = await asyncio.to_thread(
|
|
32
|
+
google.auth.default,
|
|
33
|
+
)
|
|
34
|
+
except Exception as exc:
|
|
35
|
+
msg = f"Failed to obtain Google ADC credentials: {exc}"
|
|
36
|
+
raise AuthError(msg) from exc
|
|
37
|
+
|
|
38
|
+
creds: Any = self._credentials
|
|
39
|
+
if not creds.valid:
|
|
40
|
+
try:
|
|
41
|
+
auth_req = google.auth.transport.requests.Request()
|
|
42
|
+
await asyncio.to_thread(creds.refresh, auth_req)
|
|
43
|
+
except Exception as exc:
|
|
44
|
+
msg = f"Failed to refresh Google ADC credentials: {exc}"
|
|
45
|
+
raise AuthError(msg) from exc
|
|
46
|
+
|
|
47
|
+
token: str = creds.token
|
|
48
|
+
return {"Authorization": f"Bearer {token}"}
|
|
49
|
+
return {"Authorization": f"Bearer {token}"}
|
|
50
|
+
|
|
51
|
+
def check_env(self, principle: AuthPrinciple) -> list[str]:
|
|
52
|
+
return []
|
fllme/auth/protocol.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Protocol
|
|
4
|
+
|
|
5
|
+
from ..models.auth import AuthPrinciple
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class AuthResolver(Protocol):
|
|
9
|
+
async def get_headers(self, principle: AuthPrinciple) -> dict[str, str]: ...
|
|
10
|
+
|
|
11
|
+
def check_env(self, principle: AuthPrinciple) -> list[str]: ...
|
fllme/errors.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
class FuncLLMError(Exception):
|
|
2
|
+
pass
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class ModelNotFoundError(FuncLLMError):
|
|
6
|
+
pass
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class DeploymentNotFoundError(FuncLLMError):
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class AuthError(FuncLLMError):
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ProviderError(FuncLLMError):
|
|
18
|
+
def __init__(self, status_code: int, body: str) -> None:
|
|
19
|
+
self.status_code = status_code
|
|
20
|
+
self.body = body
|
|
21
|
+
super().__init__(f"Provider returned {status_code}: {body}")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class SerializationError(FuncLLMError):
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class MediaResolutionError(FuncLLMError):
|
|
29
|
+
def __init__(self, failed_ids: list[str] | None = None, msg: str = "") -> None:
|
|
30
|
+
self.failed_ids = failed_ids
|
|
31
|
+
if not msg:
|
|
32
|
+
msg = "Media resolution failed"
|
|
33
|
+
if failed_ids:
|
|
34
|
+
msg += f" for IDs: {', '.join(failed_ids)}"
|
|
35
|
+
super().__init__(msg)
|
fllme/generate.py
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import AsyncIterator
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from .errors import ProviderError
|
|
7
|
+
from .http import get_client
|
|
8
|
+
from .media import MediaResolver, resolve_references, store_output_media
|
|
9
|
+
from .models.input import GenerationInput
|
|
10
|
+
from .models.output import GenerationOutput, StreamDelta
|
|
11
|
+
from .providers import get_adapter
|
|
12
|
+
from .providers.base import Adapter
|
|
13
|
+
from .service import DeploymentService
|
|
14
|
+
|
|
15
|
+
DEFAULT_SERVICE: DeploymentService | None = None
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def configure(service: DeploymentService) -> None:
|
|
19
|
+
global DEFAULT_SERVICE # noqa: PLW0603
|
|
20
|
+
DEFAULT_SERVICE = service
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def get_service() -> DeploymentService:
|
|
24
|
+
if DEFAULT_SERVICE is None:
|
|
25
|
+
msg = "No service configured. Call fllme.configure(service) first."
|
|
26
|
+
raise RuntimeError(msg)
|
|
27
|
+
return DEFAULT_SERVICE
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
async def generate(
|
|
31
|
+
gen_input: GenerationInput,
|
|
32
|
+
*,
|
|
33
|
+
deployment_id: str | None = None,
|
|
34
|
+
service: DeploymentService | None = None,
|
|
35
|
+
media_resolver: MediaResolver | None = None,
|
|
36
|
+
) -> GenerationOutput | AsyncIterator[StreamDelta | GenerationOutput]:
|
|
37
|
+
svc = service or get_service()
|
|
38
|
+
|
|
39
|
+
if media_resolver is not None:
|
|
40
|
+
resolved = await resolve_references(gen_input.conversation, media_resolver)
|
|
41
|
+
gen_input = gen_input.model_copy(update={"conversation": resolved})
|
|
42
|
+
|
|
43
|
+
deployment = await svc.resolve_deployment(gen_input.model, deployment_id)
|
|
44
|
+
adapter = get_adapter(deployment.adapter)
|
|
45
|
+
payload: dict[str, Any] = adapter.serialize(gen_input)
|
|
46
|
+
headers = await svc.get_auth_headers(deployment)
|
|
47
|
+
|
|
48
|
+
if gen_input.stream:
|
|
49
|
+
return _stream(deployment.url, headers, payload, adapter, media_resolver)
|
|
50
|
+
|
|
51
|
+
return await _collect(deployment.url, headers, payload, adapter, media_resolver)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
async def _collect(
|
|
55
|
+
url: str,
|
|
56
|
+
headers: dict[str, str],
|
|
57
|
+
payload: dict[str, Any],
|
|
58
|
+
adapter: Adapter,
|
|
59
|
+
media_resolver: MediaResolver | None = None,
|
|
60
|
+
) -> GenerationOutput:
|
|
61
|
+
result: GenerationOutput | None = None
|
|
62
|
+
async for item in _stream(url, headers, payload, adapter, media_resolver):
|
|
63
|
+
if isinstance(item, GenerationOutput):
|
|
64
|
+
result = item
|
|
65
|
+
if result is None:
|
|
66
|
+
msg = "Stream ended without a final GenerationOutput"
|
|
67
|
+
raise ProviderError(0, msg)
|
|
68
|
+
return result
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
async def _stream(
|
|
72
|
+
url: str,
|
|
73
|
+
headers: dict[str, str],
|
|
74
|
+
payload: dict[str, Any],
|
|
75
|
+
adapter: Adapter,
|
|
76
|
+
media_resolver: MediaResolver | None = None,
|
|
77
|
+
) -> AsyncIterator[StreamDelta | GenerationOutput]:
|
|
78
|
+
client = get_client()
|
|
79
|
+
async with client.stream("POST", url, headers=headers, json=payload) as response:
|
|
80
|
+
if response.status_code != 200:
|
|
81
|
+
body = await response.aread()
|
|
82
|
+
raise ProviderError(response.status_code, body.decode())
|
|
83
|
+
async for item in adapter.parse_stream(response.aiter_lines()):
|
|
84
|
+
if isinstance(item, GenerationOutput) and media_resolver is not None:
|
|
85
|
+
item = await store_output_media(item, media_resolver)
|
|
86
|
+
yield item
|
fllme/http.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import httpx
|
|
4
|
+
|
|
5
|
+
_client: httpx.AsyncClient | None = None
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def get_client() -> httpx.AsyncClient:
|
|
9
|
+
global _client # noqa: PLW0603
|
|
10
|
+
if _client is None:
|
|
11
|
+
_client = httpx.AsyncClient(timeout=httpx.Timeout(120.0))
|
|
12
|
+
return _client
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
async def close() -> None:
|
|
16
|
+
global _client # noqa: PLW0603
|
|
17
|
+
if _client is not None:
|
|
18
|
+
await _client.aclose()
|
|
19
|
+
_client = None
|
fllme/media.py
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import copy
|
|
4
|
+
from typing import Protocol, runtime_checkable
|
|
5
|
+
|
|
6
|
+
from .errors import MediaResolutionError
|
|
7
|
+
from .models.message import (
|
|
8
|
+
Base64Source,
|
|
9
|
+
MediaContent,
|
|
10
|
+
Message,
|
|
11
|
+
ReferenceSource,
|
|
12
|
+
UrlSource,
|
|
13
|
+
)
|
|
14
|
+
from .models.output.main import GenerationOutput
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@runtime_checkable
|
|
18
|
+
class MediaResolver(Protocol):
|
|
19
|
+
async def resolve(
|
|
20
|
+
self, references: list[ReferenceSource]
|
|
21
|
+
) -> list[Base64Source | UrlSource]:
|
|
22
|
+
"""Outbound: convert user-domain IDs to provider-sendable sources."""
|
|
23
|
+
...
|
|
24
|
+
|
|
25
|
+
async def store(
|
|
26
|
+
self, media: list[Base64Source | UrlSource]
|
|
27
|
+
) -> list[ReferenceSource]:
|
|
28
|
+
"""Inbound: upload AI-generated media, return user-domain IDs."""
|
|
29
|
+
...
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
async def resolve_references(
|
|
33
|
+
messages: list[Message], resolver: MediaResolver
|
|
34
|
+
) -> list[Message]:
|
|
35
|
+
refs: list[ReferenceSource] = []
|
|
36
|
+
positions: list[tuple[int, int]] = []
|
|
37
|
+
|
|
38
|
+
for msg_idx, msg in enumerate(messages):
|
|
39
|
+
for blk_idx, block in enumerate(msg.contents):
|
|
40
|
+
if isinstance(block, MediaContent) and isinstance(
|
|
41
|
+
block.source, ReferenceSource
|
|
42
|
+
):
|
|
43
|
+
refs.append(block.source)
|
|
44
|
+
positions.append((msg_idx, blk_idx))
|
|
45
|
+
|
|
46
|
+
if not refs:
|
|
47
|
+
return messages
|
|
48
|
+
|
|
49
|
+
try:
|
|
50
|
+
resolved = await resolver.resolve(refs)
|
|
51
|
+
except Exception as exc:
|
|
52
|
+
raise MediaResolutionError(
|
|
53
|
+
failed_ids=[r.id for r in refs],
|
|
54
|
+
) from exc
|
|
55
|
+
|
|
56
|
+
out = copy.deepcopy(messages)
|
|
57
|
+
for (msg_idx, blk_idx), new_source in zip(positions, resolved):
|
|
58
|
+
block = out[msg_idx].contents[blk_idx]
|
|
59
|
+
assert isinstance(block, MediaContent)
|
|
60
|
+
block.source = new_source
|
|
61
|
+
|
|
62
|
+
return out
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
async def store_media(message: Message, resolver: MediaResolver) -> Message:
|
|
66
|
+
sources: list[Base64Source | UrlSource] = []
|
|
67
|
+
positions: list[int] = []
|
|
68
|
+
|
|
69
|
+
for blk_idx, block in enumerate(message.contents):
|
|
70
|
+
if isinstance(block, MediaContent) and isinstance(
|
|
71
|
+
block.source, Base64Source | UrlSource
|
|
72
|
+
):
|
|
73
|
+
sources.append(block.source)
|
|
74
|
+
positions.append(blk_idx)
|
|
75
|
+
|
|
76
|
+
if not sources:
|
|
77
|
+
return message
|
|
78
|
+
|
|
79
|
+
try:
|
|
80
|
+
stored = await resolver.store(sources)
|
|
81
|
+
except Exception as exc:
|
|
82
|
+
raise MediaResolutionError() from exc
|
|
83
|
+
|
|
84
|
+
out = message.model_copy(deep=True)
|
|
85
|
+
for blk_idx, new_ref in zip(positions, stored):
|
|
86
|
+
block = out.contents[blk_idx]
|
|
87
|
+
assert isinstance(block, MediaContent)
|
|
88
|
+
block.source = new_ref
|
|
89
|
+
|
|
90
|
+
return out
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
async def store_output_media(
|
|
94
|
+
output: GenerationOutput, resolver: MediaResolver
|
|
95
|
+
) -> GenerationOutput:
|
|
96
|
+
new_message = await store_media(output.message, resolver)
|
|
97
|
+
if new_message is output.message:
|
|
98
|
+
return output
|
|
99
|
+
return output.model_copy(update={"message": new_message})
|
fllme/models/__init__.py
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
from .message import (
|
|
2
|
+
Base64Source,
|
|
3
|
+
Content,
|
|
4
|
+
ContentType,
|
|
5
|
+
ErrorContent,
|
|
6
|
+
MediaContent,
|
|
7
|
+
MediaSource,
|
|
8
|
+
Message,
|
|
9
|
+
MessageSource,
|
|
10
|
+
ReferenceSource,
|
|
11
|
+
SourceType,
|
|
12
|
+
TextContent,
|
|
13
|
+
ThinkingContent,
|
|
14
|
+
ToolCallContent,
|
|
15
|
+
ToolResponseContent,
|
|
16
|
+
UrlSource,
|
|
17
|
+
)
|
|
18
|
+
from .input import (
|
|
19
|
+
BasicOutputType,
|
|
20
|
+
GenerationInput,
|
|
21
|
+
ImageConfig,
|
|
22
|
+
LLMConfig,
|
|
23
|
+
MimeType,
|
|
24
|
+
OutputType,
|
|
25
|
+
PersonGeneration,
|
|
26
|
+
Ratio,
|
|
27
|
+
Resolution,
|
|
28
|
+
ThinkingLevel,
|
|
29
|
+
Tool,
|
|
30
|
+
ToolsCallingMode,
|
|
31
|
+
ToolsConfig,
|
|
32
|
+
)
|
|
33
|
+
from .auth import AuthPrinciple
|
|
34
|
+
from .deployment import AdapterType, Deployment
|
|
35
|
+
from .model import LLMModel, Provider
|
|
36
|
+
from .output import (
|
|
37
|
+
CacheUsage,
|
|
38
|
+
Citation,
|
|
39
|
+
CitationType,
|
|
40
|
+
FinishReason,
|
|
41
|
+
GenerationOutput,
|
|
42
|
+
SafetyCategory,
|
|
43
|
+
SafetyRating,
|
|
44
|
+
SafetyResult,
|
|
45
|
+
SafetySeverity,
|
|
46
|
+
StreamDelta,
|
|
47
|
+
StreamEventType,
|
|
48
|
+
TextDelta,
|
|
49
|
+
TextSpan,
|
|
50
|
+
ThinkingDelta,
|
|
51
|
+
Usage,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
__all__ = [
|
|
55
|
+
"AdapterType",
|
|
56
|
+
"AuthPrinciple",
|
|
57
|
+
"Base64Source",
|
|
58
|
+
"BasicOutputType",
|
|
59
|
+
"CacheUsage",
|
|
60
|
+
"Citation",
|
|
61
|
+
"CitationType",
|
|
62
|
+
"Content",
|
|
63
|
+
"ContentType",
|
|
64
|
+
"Deployment",
|
|
65
|
+
"ErrorContent",
|
|
66
|
+
"FinishReason",
|
|
67
|
+
"GenerationInput",
|
|
68
|
+
"GenerationOutput",
|
|
69
|
+
"ImageConfig",
|
|
70
|
+
"LLMConfig",
|
|
71
|
+
"LLMModel",
|
|
72
|
+
"MediaContent",
|
|
73
|
+
"MediaSource",
|
|
74
|
+
"Message",
|
|
75
|
+
"MessageSource",
|
|
76
|
+
"MimeType",
|
|
77
|
+
"OutputType",
|
|
78
|
+
"PersonGeneration",
|
|
79
|
+
"Provider",
|
|
80
|
+
"Ratio",
|
|
81
|
+
"ReferenceSource",
|
|
82
|
+
"Resolution",
|
|
83
|
+
"SafetyCategory",
|
|
84
|
+
"SafetyRating",
|
|
85
|
+
"SafetyResult",
|
|
86
|
+
"SafetySeverity",
|
|
87
|
+
"SourceType",
|
|
88
|
+
"StreamDelta",
|
|
89
|
+
"StreamEventType",
|
|
90
|
+
"TextContent",
|
|
91
|
+
"TextDelta",
|
|
92
|
+
"TextSpan",
|
|
93
|
+
"ThinkingContent",
|
|
94
|
+
"ThinkingDelta",
|
|
95
|
+
"ThinkingLevel",
|
|
96
|
+
"Tool",
|
|
97
|
+
"ToolCallContent",
|
|
98
|
+
"ToolResponseContent",
|
|
99
|
+
"ToolsCallingMode",
|
|
100
|
+
"ToolsConfig",
|
|
101
|
+
"UrlSource",
|
|
102
|
+
"Usage",
|
|
103
|
+
]
|
fllme/models/auth.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from pydantic import BaseModel
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class AuthPrinciple(BaseModel):
|
|
5
|
+
id: str
|
|
6
|
+
name: str
|
|
7
|
+
resolver_id: str
|
|
8
|
+
required_env_vars: list[str] = []
|
|
9
|
+
config: dict[str, str] = {}
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
GOOGLE_ADC_PRINCIPLE = AuthPrinciple(
|
|
13
|
+
id="google_adc",
|
|
14
|
+
name="Google ADC",
|
|
15
|
+
resolver_id="google_adc",
|
|
16
|
+
required_env_vars=[],
|
|
17
|
+
config={},
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
API_KEY_PRINCIPLE = AuthPrinciple(
|
|
21
|
+
id="api_key",
|
|
22
|
+
name="API Key",
|
|
23
|
+
resolver_id="api_key",
|
|
24
|
+
required_env_vars=[],
|
|
25
|
+
config={"header_name": "api-key"},
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
BUILTIN_PRINCIPLES: list[AuthPrinciple] = [GOOGLE_ADC_PRINCIPLE, API_KEY_PRINCIPLE]
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from enum import StrEnum
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class AdapterType(StrEnum):
|
|
7
|
+
ANTHROPIC_VERTEX_V1 = "anthropic_vertex_v1"
|
|
8
|
+
ANTHROPIC_VERTEX_V2 = "anthropic_vertex_v2"
|
|
9
|
+
GEMINI_VERTEX_V1 = "gemini_vertex_v1"
|
|
10
|
+
MISTRAL_VERTEX_V1 = "mistral_vertex_v1"
|
|
11
|
+
OPENAI_AZURE_V1 = "openai_azure_v1"
|
|
12
|
+
OPENAI_AZURE_V2 = "openai_azure_v2"
|
|
13
|
+
OPENAI_AZURE_V3 = "openai_azure_v3"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Deployment(BaseModel):
|
|
17
|
+
id: str
|
|
18
|
+
url: str
|
|
19
|
+
model_id: str
|
|
20
|
+
adapter: AdapterType
|
|
21
|
+
auth_id: str
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from .main import (
|
|
2
|
+
BasicOutputType,
|
|
3
|
+
GenerationInput,
|
|
4
|
+
LLMConfig,
|
|
5
|
+
OutputType,
|
|
6
|
+
ThinkingLevel,
|
|
7
|
+
)
|
|
8
|
+
from .tools import Tool, ToolsCallingMode, ToolsConfig
|
|
9
|
+
from .image import (
|
|
10
|
+
ImageConfig,
|
|
11
|
+
MimeType,
|
|
12
|
+
PersonGeneration,
|
|
13
|
+
Ratio,
|
|
14
|
+
Resolution,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"BasicOutputType",
|
|
19
|
+
"GenerationInput",
|
|
20
|
+
"ImageConfig",
|
|
21
|
+
"LLMConfig",
|
|
22
|
+
"MimeType",
|
|
23
|
+
"OutputType",
|
|
24
|
+
"PersonGeneration",
|
|
25
|
+
"Ratio",
|
|
26
|
+
"Resolution",
|
|
27
|
+
"ThinkingLevel",
|
|
28
|
+
"Tool",
|
|
29
|
+
"ToolsCallingMode",
|
|
30
|
+
"ToolsConfig",
|
|
31
|
+
]
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from pydantic import BaseModel
|
|
2
|
+
from enum import StrEnum
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Ratio(StrEnum):
|
|
6
|
+
_1_1 = "1:1"
|
|
7
|
+
_2_3 = "2:3"
|
|
8
|
+
_3_2 = "3:2"
|
|
9
|
+
_3_4 = "3:4"
|
|
10
|
+
_4_3 = "4:3"
|
|
11
|
+
_9_16 = "9:16"
|
|
12
|
+
_16_9 = "16:9"
|
|
13
|
+
_21_9 = "21:9"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Resolution(StrEnum):
|
|
17
|
+
_1K = "1K"
|
|
18
|
+
_2K = "2K"
|
|
19
|
+
_4K = "4K"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class PersonGeneration(StrEnum):
|
|
23
|
+
ALL = "ALLOW_ALL"
|
|
24
|
+
ADULT = "ALLOW_ADULT"
|
|
25
|
+
NONE = "ALLOW_NONE"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class MimeType(StrEnum):
|
|
29
|
+
PNG = "image/png"
|
|
30
|
+
JPEG = "image/jpeg"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class ImageConfig(BaseModel):
|
|
34
|
+
ratio: Ratio = Ratio._1_1
|
|
35
|
+
resolution: Resolution = Resolution._1K
|
|
36
|
+
person_generation: PersonGeneration = PersonGeneration.ALL
|
|
37
|
+
mime_type: MimeType = MimeType.PNG
|