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.
Files changed (46) hide show
  1. fllme/__init__.py +87 -0
  2. fllme/__main__.py +0 -0
  3. fllme/auth/__init__.py +42 -0
  4. fllme/auth/api_key.py +32 -0
  5. fllme/auth/google_adc.py +52 -0
  6. fllme/auth/protocol.py +11 -0
  7. fllme/errors.py +35 -0
  8. fllme/generate.py +86 -0
  9. fllme/http.py +19 -0
  10. fllme/media.py +99 -0
  11. fllme/models/__init__.py +103 -0
  12. fllme/models/auth.py +28 -0
  13. fllme/models/deployment.py +21 -0
  14. fllme/models/input/__init__.py +31 -0
  15. fllme/models/input/image.py +37 -0
  16. fllme/models/input/main.py +45 -0
  17. fllme/models/input/tools.py +21 -0
  18. fllme/models/message.py +95 -0
  19. fllme/models/model.py +16 -0
  20. fllme/models/output/__init__.py +28 -0
  21. fllme/models/output/citation.py +24 -0
  22. fllme/models/output/main.py +26 -0
  23. fllme/models/output/safety.py +33 -0
  24. fllme/models/output/stream.py +20 -0
  25. fllme/models/output/usage.py +14 -0
  26. fllme/providers/__init__.py +35 -0
  27. fllme/providers/anthropic/__init__.py +4 -0
  28. fllme/providers/anthropic/vertex_v1.py +346 -0
  29. fllme/providers/anthropic/vertex_v2.py +29 -0
  30. fllme/providers/base.py +38 -0
  31. fllme/providers/gemini/__init__.py +3 -0
  32. fllme/providers/gemini/vertex_v1.py +281 -0
  33. fllme/providers/mistral/__init__.py +3 -0
  34. fllme/providers/mistral/vertex_v1.py +284 -0
  35. fllme/providers/openai/__init__.py +5 -0
  36. fllme/providers/openai/azure_v1.py +303 -0
  37. fllme/providers/openai/azure_v2.py +19 -0
  38. fllme/providers/openai/azure_v3.py +20 -0
  39. fllme/service.py +111 -0
  40. fllme/store/__init__.py +9 -0
  41. fllme/store/deployments/__init__.py +9 -0
  42. fllme/store/deployments/protocol.py +47 -0
  43. fllme/store/deployments/sqlite.py +268 -0
  44. fllme-0.2.0.dist-info/METADATA +270 -0
  45. fllme-0.2.0.dist-info/RECORD +46 -0
  46. 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
@@ -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})
@@ -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