cocoindex 0.3.4__cp311-abi3-manylinux_2_28_x86_64.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 (42) hide show
  1. cocoindex/__init__.py +114 -0
  2. cocoindex/_engine.abi3.so +0 -0
  3. cocoindex/auth_registry.py +44 -0
  4. cocoindex/cli.py +830 -0
  5. cocoindex/engine_object.py +214 -0
  6. cocoindex/engine_value.py +550 -0
  7. cocoindex/flow.py +1281 -0
  8. cocoindex/functions/__init__.py +40 -0
  9. cocoindex/functions/_engine_builtin_specs.py +66 -0
  10. cocoindex/functions/colpali.py +247 -0
  11. cocoindex/functions/sbert.py +77 -0
  12. cocoindex/index.py +50 -0
  13. cocoindex/lib.py +75 -0
  14. cocoindex/llm.py +47 -0
  15. cocoindex/op.py +1047 -0
  16. cocoindex/py.typed +0 -0
  17. cocoindex/query_handler.py +57 -0
  18. cocoindex/runtime.py +78 -0
  19. cocoindex/setting.py +171 -0
  20. cocoindex/setup.py +92 -0
  21. cocoindex/sources/__init__.py +5 -0
  22. cocoindex/sources/_engine_builtin_specs.py +120 -0
  23. cocoindex/subprocess_exec.py +277 -0
  24. cocoindex/targets/__init__.py +5 -0
  25. cocoindex/targets/_engine_builtin_specs.py +153 -0
  26. cocoindex/targets/lancedb.py +466 -0
  27. cocoindex/tests/__init__.py +0 -0
  28. cocoindex/tests/test_engine_object.py +331 -0
  29. cocoindex/tests/test_engine_value.py +1724 -0
  30. cocoindex/tests/test_optional_database.py +249 -0
  31. cocoindex/tests/test_transform_flow.py +300 -0
  32. cocoindex/tests/test_typing.py +553 -0
  33. cocoindex/tests/test_validation.py +134 -0
  34. cocoindex/typing.py +834 -0
  35. cocoindex/user_app_loader.py +53 -0
  36. cocoindex/utils.py +20 -0
  37. cocoindex/validation.py +104 -0
  38. cocoindex-0.3.4.dist-info/METADATA +288 -0
  39. cocoindex-0.3.4.dist-info/RECORD +42 -0
  40. cocoindex-0.3.4.dist-info/WHEEL +4 -0
  41. cocoindex-0.3.4.dist-info/entry_points.txt +2 -0
  42. cocoindex-0.3.4.dist-info/licenses/THIRD_PARTY_NOTICES.html +13249 -0
@@ -0,0 +1,40 @@
1
+ """Functions module for cocoindex.
2
+
3
+ This module provides various function specifications and executors for data processing,
4
+ including embedding functions, text processing, and multimodal operations.
5
+ """
6
+
7
+ # Import all engine builtin function specs
8
+ from ._engine_builtin_specs import *
9
+
10
+ # Import SentenceTransformer embedding functionality
11
+ from .sbert import (
12
+ SentenceTransformerEmbed,
13
+ SentenceTransformerEmbedExecutor,
14
+ )
15
+
16
+ # Import ColPali multimodal embedding functionality
17
+ from .colpali import (
18
+ ColPaliEmbedImage,
19
+ ColPaliEmbedImageExecutor,
20
+ ColPaliEmbedQuery,
21
+ ColPaliEmbedQueryExecutor,
22
+ )
23
+
24
+ __all__ = [
25
+ # Engine builtin specs
26
+ "DetectProgrammingLanguage",
27
+ "EmbedText",
28
+ "ExtractByLlm",
29
+ "ParseJson",
30
+ "SplitBySeparators",
31
+ "SplitRecursively",
32
+ # SentenceTransformer
33
+ "SentenceTransformerEmbed",
34
+ "SentenceTransformerEmbedExecutor",
35
+ # ColPali
36
+ "ColPaliEmbedImage",
37
+ "ColPaliEmbedImageExecutor",
38
+ "ColPaliEmbedQuery",
39
+ "ColPaliEmbedQueryExecutor",
40
+ ]
@@ -0,0 +1,66 @@
1
+ """All builtin function specs."""
2
+
3
+ import dataclasses
4
+ from typing import Literal
5
+
6
+ from .. import llm, op
7
+
8
+
9
+ class ParseJson(op.FunctionSpec):
10
+ """Parse a text into a JSON object."""
11
+
12
+
13
+ @dataclasses.dataclass
14
+ class CustomLanguageSpec:
15
+ """Custom language specification."""
16
+
17
+ language_name: str
18
+ separators_regex: list[str]
19
+ aliases: list[str] = dataclasses.field(default_factory=list)
20
+
21
+
22
+ class DetectProgrammingLanguage(op.FunctionSpec):
23
+ """Detect the programming language of a file."""
24
+
25
+
26
+ class SplitRecursively(op.FunctionSpec):
27
+ """Split a document (in string) recursively."""
28
+
29
+ custom_languages: list[CustomLanguageSpec] = dataclasses.field(default_factory=list)
30
+
31
+
32
+ class SplitBySeparators(op.FunctionSpec):
33
+ """
34
+ Split text by specified regex separators only.
35
+ Output schema matches SplitRecursively for drop-in compatibility:
36
+ KTable rows with fields: location (Range), text (Str), start, end.
37
+ Args:
38
+ separators_regex: list[str] # e.g., [r"\\n\\n+"]
39
+ keep_separator: Literal["NONE", "LEFT", "RIGHT"] = "NONE"
40
+ include_empty: bool = False
41
+ trim: bool = True
42
+ """
43
+
44
+ separators_regex: list[str] = dataclasses.field(default_factory=list)
45
+ keep_separator: Literal["NONE", "LEFT", "RIGHT"] = "NONE"
46
+ include_empty: bool = False
47
+ trim: bool = True
48
+
49
+
50
+ class EmbedText(op.FunctionSpec):
51
+ """Embed a text into a vector space."""
52
+
53
+ api_type: llm.LlmApiType
54
+ model: str
55
+ address: str | None = None
56
+ output_dimension: int | None = None
57
+ task_type: str | None = None
58
+ api_config: llm.VertexAiConfig | None = None
59
+
60
+
61
+ class ExtractByLlm(op.FunctionSpec):
62
+ """Extract information from a text using a LLM."""
63
+
64
+ llm_spec: llm.LlmSpec
65
+ output_type: type
66
+ instruction: str | None = None
@@ -0,0 +1,247 @@
1
+ """ColPali image and query embedding functions for multimodal document retrieval."""
2
+
3
+ import functools
4
+ from dataclasses import dataclass
5
+ from typing import Any, TYPE_CHECKING, Literal
6
+ import numpy as np
7
+
8
+ from .. import op
9
+ from ..typing import Vector
10
+
11
+ if TYPE_CHECKING:
12
+ import torch
13
+
14
+
15
+ @dataclass
16
+ class ColPaliModelInfo:
17
+ """Shared model information for ColPali embedding functions."""
18
+
19
+ model: Any
20
+ processor: Any
21
+ device: Any
22
+ dimension: int
23
+
24
+
25
+ @functools.cache
26
+ def _get_colpali_model_and_processor(model_name: str) -> ColPaliModelInfo:
27
+ """Load and cache ColPali model and processor with shared device setup."""
28
+ try:
29
+ import colpali_engine as ce # type: ignore[import-untyped]
30
+ import torch
31
+ except ImportError as e:
32
+ raise ImportError(
33
+ "ColPali support requires the optional 'colpali' dependency. "
34
+ "Install it with: pip install 'cocoindex[colpali]'"
35
+ ) from e
36
+
37
+ device = "cuda" if torch.cuda.is_available() else "cpu"
38
+ lower_model_name = model_name.lower()
39
+
40
+ # Determine model type from name
41
+ if lower_model_name.startswith("colpali"):
42
+ model = ce.ColPali.from_pretrained(
43
+ model_name, torch_dtype=torch.bfloat16, device_map=device
44
+ )
45
+ processor = ce.ColPaliProcessor.from_pretrained(model_name)
46
+ elif lower_model_name.startswith("colqwen2.5"):
47
+ model = ce.ColQwen2_5.from_pretrained(
48
+ model_name, torch_dtype=torch.bfloat16, device_map=device
49
+ )
50
+ processor = ce.ColQwen2_5_Processor.from_pretrained(model_name)
51
+ elif lower_model_name.startswith("colqwen"):
52
+ model = ce.ColQwen2.from_pretrained(
53
+ model_name, torch_dtype=torch.bfloat16, device_map=device
54
+ )
55
+ processor = ce.ColQwen2Processor.from_pretrained(model_name)
56
+ else:
57
+ # Fallback to ColPali for backwards compatibility
58
+ model = ce.ColPali.from_pretrained(
59
+ model_name, torch_dtype=torch.bfloat16, device_map=device
60
+ )
61
+ processor = ce.ColPaliProcessor.from_pretrained(model_name)
62
+
63
+ # Detect dimension
64
+ dimension = _detect_colpali_dimension(model, processor, device)
65
+
66
+ return ColPaliModelInfo(
67
+ model=model,
68
+ processor=processor,
69
+ dimension=dimension,
70
+ device=device,
71
+ )
72
+
73
+
74
+ def _detect_colpali_dimension(model: Any, processor: Any, device: Any) -> int:
75
+ """Detect ColPali embedding dimension from the actual model config."""
76
+ # Try to access embedding dimension
77
+ if hasattr(model.config, "embedding_dim"):
78
+ dim = model.config.embedding_dim
79
+ else:
80
+ # Fallback: infer from output shape with dummy data
81
+ from PIL import Image
82
+ import numpy as np
83
+ import torch
84
+
85
+ dummy_img = Image.fromarray(np.zeros((224, 224, 3), np.uint8))
86
+ # Use the processor to process the dummy image
87
+ processed = processor.process_images([dummy_img]).to(device)
88
+ with torch.no_grad():
89
+ output = model(**processed)
90
+ dim = int(output.shape[-1])
91
+ if isinstance(dim, int):
92
+ return dim
93
+ else:
94
+ raise ValueError(f"Expected integer dimension, got {type(dim)}: {dim}")
95
+ return dim
96
+
97
+
98
+ class ColPaliEmbedImage(op.FunctionSpec):
99
+ """
100
+ `ColPaliEmbedImage` embeds images using ColVision multimodal models.
101
+
102
+ Supports ALL models available in the colpali-engine library, including:
103
+ - ColPali models (colpali-*): PaliGemma-based, best for general document retrieval
104
+ - ColQwen2 models (colqwen-*): Qwen2-VL-based, excellent for multilingual text (29+ languages) and general vision
105
+ - ColSmol models (colsmol-*): Lightweight, good for resource-constrained environments
106
+ - Any future ColVision models supported by colpali-engine
107
+
108
+ These models use late interaction between image patch embeddings and text token
109
+ embeddings for retrieval.
110
+
111
+ Args:
112
+ model: Any ColVision model name supported by colpali-engine
113
+ (e.g., "vidore/colpali-v1.2", "vidore/colqwen2.5-v0.2", "vidore/colsmol-v1.0")
114
+ See https://github.com/illuin-tech/colpali for the complete list of supported models.
115
+
116
+ Note:
117
+ This function requires the optional colpali-engine dependency.
118
+ Install it with: pip install 'cocoindex[colpali]'
119
+ """
120
+
121
+ model: str
122
+
123
+
124
+ @op.executor_class(
125
+ gpu=True,
126
+ cache=True,
127
+ batching=True,
128
+ max_batch_size=32,
129
+ behavior_version=1,
130
+ )
131
+ class ColPaliEmbedImageExecutor:
132
+ """Executor for ColVision image embedding (ColPali, ColQwen2, ColSmol, etc.)."""
133
+
134
+ spec: ColPaliEmbedImage
135
+ _model_info: ColPaliModelInfo
136
+
137
+ def analyze(self) -> type:
138
+ # Get shared model and dimension
139
+ self._model_info = _get_colpali_model_and_processor(self.spec.model)
140
+
141
+ # Return multi-vector type: Variable patches x Fixed hidden dimension
142
+ dimension = self._model_info.dimension
143
+ return Vector[Vector[np.float32, Literal[dimension]]] # type: ignore
144
+
145
+ def __call__(self, img_bytes_list: list[bytes]) -> Any:
146
+ try:
147
+ from PIL import Image
148
+ import torch
149
+ import io
150
+ except ImportError as e:
151
+ raise ImportError(
152
+ "Required dependencies (PIL, torch) are missing for ColVision image embedding."
153
+ ) from e
154
+
155
+ model = self._model_info.model
156
+ processor = self._model_info.processor
157
+ device = self._model_info.device
158
+
159
+ pil_images = [
160
+ Image.open(io.BytesIO(img_bytes)).convert("RGB")
161
+ for img_bytes in img_bytes_list
162
+ ]
163
+ inputs = processor.process_images(pil_images).to(device)
164
+ with torch.no_grad():
165
+ embeddings = model(**inputs)
166
+
167
+ # Return multi-vector format: [patches, hidden_dim]
168
+ if len(embeddings.shape) != 3:
169
+ raise ValueError(
170
+ f"Expected 3D tensor [batch, patches, hidden_dim], got shape {embeddings.shape}"
171
+ )
172
+
173
+ # [patches, hidden_dim]
174
+ return embeddings.cpu().to(torch.float32).numpy()
175
+
176
+
177
+ class ColPaliEmbedQuery(op.FunctionSpec):
178
+ """
179
+ `ColPaliEmbedQuery` embeds text queries using ColVision multimodal models.
180
+
181
+ Supports ALL models available in the colpali-engine library, including:
182
+ - ColPali models (colpali-*): PaliGemma-based, best for general document retrieval
183
+ - ColQwen2 models (colqwen-*): Qwen2-VL-based, excellent for multilingual text (29+ languages) and general vision
184
+ - ColSmol models (colsmol-*): Lightweight, good for resource-constrained environments
185
+ - Any future ColVision models supported by colpali-engine
186
+
187
+ This produces query embeddings compatible with ColVision image embeddings
188
+ for late interaction scoring (MaxSim).
189
+
190
+ Args:
191
+ model: Any ColVision model name supported by colpali-engine
192
+ (e.g., "vidore/colpali-v1.2", "vidore/colqwen2.5-v0.2", "vidore/colsmol-v1.0")
193
+ See https://github.com/illuin-tech/colpali for the complete list of supported models.
194
+
195
+ Note:
196
+ This function requires the optional colpali-engine dependency.
197
+ Install it with: pip install 'cocoindex[colpali]'
198
+ """
199
+
200
+ model: str
201
+
202
+
203
+ @op.executor_class(
204
+ gpu=True,
205
+ cache=True,
206
+ behavior_version=1,
207
+ batching=True,
208
+ max_batch_size=32,
209
+ )
210
+ class ColPaliEmbedQueryExecutor:
211
+ """Executor for ColVision query embedding (ColPali, ColQwen2, ColSmol, etc.)."""
212
+
213
+ spec: ColPaliEmbedQuery
214
+ _model_info: ColPaliModelInfo
215
+
216
+ def analyze(self) -> type:
217
+ # Get shared model and dimension
218
+ self._model_info = _get_colpali_model_and_processor(self.spec.model)
219
+
220
+ # Return multi-vector type: Variable tokens x Fixed hidden dimension
221
+ dimension = self._model_info.dimension
222
+ return Vector[Vector[np.float32, Literal[dimension]]] # type: ignore
223
+
224
+ def __call__(self, queries: list[str]) -> Any:
225
+ try:
226
+ import torch
227
+ except ImportError as e:
228
+ raise ImportError(
229
+ "Required dependencies (torch) are missing for ColVision query embedding."
230
+ ) from e
231
+
232
+ model = self._model_info.model
233
+ processor = self._model_info.processor
234
+ device = self._model_info.device
235
+
236
+ inputs = processor.process_queries(queries).to(device)
237
+ with torch.no_grad():
238
+ embeddings = model(**inputs)
239
+
240
+ # Return multi-vector format: [tokens, hidden_dim]
241
+ if len(embeddings.shape) != 3:
242
+ raise ValueError(
243
+ f"Expected 3D tensor [batch, tokens, hidden_dim], got shape {embeddings.shape}"
244
+ )
245
+
246
+ # [tokens, hidden_dim]
247
+ return embeddings.cpu().to(torch.float32).numpy()
@@ -0,0 +1,77 @@
1
+ """SentenceTransformer embedding functionality."""
2
+
3
+ from typing import Any, Literal, cast
4
+
5
+ import numpy as np
6
+ from numpy.typing import NDArray
7
+
8
+ from .. import op
9
+ from ..typing import Vector
10
+
11
+
12
+ class SentenceTransformerEmbed(op.FunctionSpec):
13
+ """
14
+ `SentenceTransformerEmbed` embeds a text into a vector space using the [SentenceTransformer](https://huggingface.co/sentence-transformers) library.
15
+
16
+ Args:
17
+
18
+ model: The name of the SentenceTransformer model to use.
19
+ args: Additional arguments to pass to the SentenceTransformer constructor. e.g. {"trust_remote_code": True}
20
+
21
+ Note:
22
+ This function requires the optional sentence-transformers dependency.
23
+ Install it with: pip install 'cocoindex[embeddings]'
24
+ """
25
+
26
+ model: str
27
+ args: dict[str, Any] | None = None
28
+
29
+
30
+ @op.executor_class(
31
+ gpu=True,
32
+ cache=True,
33
+ batching=True,
34
+ max_batch_size=512,
35
+ behavior_version=1,
36
+ arg_relationship=(op.ArgRelationship.EMBEDDING_ORIGIN_TEXT, "text"),
37
+ )
38
+ class SentenceTransformerEmbedExecutor:
39
+ """Executor for SentenceTransformerEmbed."""
40
+
41
+ spec: SentenceTransformerEmbed
42
+ _model: Any | None = None
43
+
44
+ def analyze(self) -> type:
45
+ try:
46
+ # Only import sentence_transformers locally when it's needed, as its import is very slow.
47
+ import sentence_transformers # pylint: disable=import-outside-toplevel
48
+ except ImportError as e:
49
+ raise ImportError(
50
+ "sentence_transformers is required for SentenceTransformerEmbed function. "
51
+ "Install it with one of these commands:\n"
52
+ " pip install 'cocoindex[embeddings]'\n"
53
+ " pip install sentence-transformers"
54
+ ) from e
55
+
56
+ args = self.spec.args or {}
57
+ self._model = sentence_transformers.SentenceTransformer(self.spec.model, **args)
58
+ dim = self._model.get_sentence_embedding_dimension()
59
+ return Vector[np.float32, Literal[dim]] # type: ignore
60
+
61
+ def __call__(self, text: list[str]) -> list[NDArray[np.float32]]:
62
+ assert self._model is not None
63
+
64
+ # Sort the text by length to minimize the number of padding tokens.
65
+ text_with_idx = [(idx, t) for idx, t in enumerate(text)]
66
+ text_with_idx.sort(key=lambda x: len(x[1]))
67
+
68
+ results: list[NDArray[np.float32]] = self._model.encode(
69
+ [t for _, t in text_with_idx], convert_to_numpy=True
70
+ )
71
+ final_results: list[NDArray[np.float32] | None] = [
72
+ None for _ in range(len(text))
73
+ ]
74
+ for (idx, _), result in zip(text_with_idx, results):
75
+ final_results[idx] = result
76
+
77
+ return cast(list[NDArray[np.float32]], final_results)
cocoindex/index.py ADDED
@@ -0,0 +1,50 @@
1
+ from enum import Enum
2
+ from dataclasses import dataclass
3
+ from typing import Sequence, Union
4
+
5
+
6
+ class VectorSimilarityMetric(Enum):
7
+ COSINE_SIMILARITY = "CosineSimilarity"
8
+ L2_DISTANCE = "L2Distance"
9
+ INNER_PRODUCT = "InnerProduct"
10
+
11
+
12
+ @dataclass
13
+ class HnswVectorIndexMethod:
14
+ """HNSW vector index parameters."""
15
+
16
+ kind: str = "Hnsw"
17
+ m: int | None = None
18
+ ef_construction: int | None = None
19
+
20
+
21
+ @dataclass
22
+ class IvfFlatVectorIndexMethod:
23
+ """IVFFlat vector index parameters."""
24
+
25
+ kind: str = "IvfFlat"
26
+ lists: int | None = None
27
+
28
+
29
+ VectorIndexMethod = Union[HnswVectorIndexMethod, IvfFlatVectorIndexMethod]
30
+
31
+
32
+ @dataclass
33
+ class VectorIndexDef:
34
+ """
35
+ Define a vector index on a field.
36
+ """
37
+
38
+ field_name: str
39
+ metric: VectorSimilarityMetric
40
+ method: VectorIndexMethod | None = None
41
+
42
+
43
+ @dataclass
44
+ class IndexOptions:
45
+ """
46
+ Options for an index.
47
+ """
48
+
49
+ primary_key_fields: Sequence[str]
50
+ vector_indexes: Sequence[VectorIndexDef] = ()
cocoindex/lib.py ADDED
@@ -0,0 +1,75 @@
1
+ """
2
+ Library level functions and states.
3
+ """
4
+
5
+ import threading
6
+ import warnings
7
+
8
+ from . import _engine # type: ignore
9
+ from . import flow, setting
10
+ from .engine_object import dump_engine_object
11
+ from .validation import validate_app_namespace_name
12
+ from typing import Any, Callable, overload
13
+
14
+
15
+ def prepare_settings(settings: setting.Settings) -> Any:
16
+ """Prepare the settings for the engine."""
17
+ if settings.app_namespace:
18
+ validate_app_namespace_name(settings.app_namespace)
19
+ return dump_engine_object(settings)
20
+
21
+
22
+ _engine.set_settings_fn(lambda: prepare_settings(setting.Settings.from_env()))
23
+
24
+
25
+ _prev_settings_fn: Callable[[], setting.Settings] | None = None
26
+ _prev_settings_fn_lock: threading.Lock = threading.Lock()
27
+
28
+
29
+ @overload
30
+ def settings(fn: Callable[[], setting.Settings]) -> Callable[[], setting.Settings]: ...
31
+ @overload
32
+ def settings(
33
+ fn: None,
34
+ ) -> Callable[[Callable[[], setting.Settings]], Callable[[], setting.Settings]]: ...
35
+ def settings(fn: Callable[[], setting.Settings] | None = None) -> Any:
36
+ """
37
+ Decorate a function that returns a settings.Settings object.
38
+ It registers the function as a settings provider.
39
+ """
40
+
41
+ def _inner(fn: Callable[[], setting.Settings]) -> Callable[[], setting.Settings]:
42
+ global _prev_settings_fn # pylint: disable=global-statement
43
+ with _prev_settings_fn_lock:
44
+ if _prev_settings_fn is not None:
45
+ warnings.warn(
46
+ f"Setting a new settings function will override the previous one {_prev_settings_fn}."
47
+ )
48
+ _prev_settings_fn = fn
49
+ _engine.set_settings_fn(lambda: prepare_settings(fn()))
50
+ return fn
51
+
52
+ if fn is not None:
53
+ return _inner(fn)
54
+ else:
55
+ return _inner
56
+
57
+
58
+ def init(settings: setting.Settings | None = None) -> None:
59
+ """
60
+ Initialize the cocoindex library.
61
+
62
+ If the settings are not provided, they are loaded from the environment variables.
63
+ """
64
+ _engine.init(prepare_settings(settings) if settings is not None else None)
65
+
66
+
67
+ def start_server(settings: setting.ServerSettings) -> None:
68
+ """Start the cocoindex server."""
69
+ flow.ensure_all_flows_built()
70
+ _engine.start_server(settings.__dict__)
71
+
72
+
73
+ def stop() -> None:
74
+ """Stop the cocoindex library."""
75
+ _engine.stop()
cocoindex/llm.py ADDED
@@ -0,0 +1,47 @@
1
+ from dataclasses import dataclass
2
+ from enum import Enum
3
+
4
+
5
+ class LlmApiType(Enum):
6
+ """The type of LLM API to use."""
7
+
8
+ OPENAI = "OpenAi"
9
+ OLLAMA = "Ollama"
10
+ GEMINI = "Gemini"
11
+ VERTEX_AI = "VertexAi"
12
+ ANTHROPIC = "Anthropic"
13
+ LITE_LLM = "LiteLlm"
14
+ OPEN_ROUTER = "OpenRouter"
15
+ VOYAGE = "Voyage"
16
+ VLLM = "Vllm"
17
+ BEDROCK = "Bedrock"
18
+
19
+
20
+ @dataclass
21
+ class VertexAiConfig:
22
+ """A specification for a Vertex AI LLM."""
23
+
24
+ kind = "VertexAi"
25
+
26
+ project: str
27
+ region: str | None = None
28
+
29
+
30
+ @dataclass
31
+ class OpenAiConfig:
32
+ """A specification for a OpenAI LLM."""
33
+
34
+ kind = "OpenAi"
35
+
36
+ org_id: str | None = None
37
+ project_id: str | None = None
38
+
39
+
40
+ @dataclass
41
+ class LlmSpec:
42
+ """A specification for a LLM."""
43
+
44
+ api_type: LlmApiType
45
+ model: str
46
+ address: str | None = None
47
+ api_config: VertexAiConfig | OpenAiConfig | None = None