model-library 0.1.1__tar.gz → 0.1.3__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.
- {model_library-0.1.1 → model_library-0.1.3}/Makefile +1 -1
- {model_library-0.1.1 → model_library-0.1.3}/PKG-INFO +2 -3
- model_library-0.1.3/model_library/base/__init__.py +7 -0
- {model_library-0.1.1/model_library → model_library-0.1.3/model_library/base}/base.py +58 -429
- model_library-0.1.3/model_library/base/batch.py +121 -0
- model_library-0.1.1/model_library/providers/cohere.py → model_library-0.1.3/model_library/base/delegate_only.py +34 -40
- model_library-0.1.3/model_library/base/input.py +100 -0
- model_library-0.1.3/model_library/base/output.py +229 -0
- model_library-0.1.3/model_library/base/utils.py +43 -0
- {model_library-0.1.1 → model_library-0.1.3}/model_library/config/ai21labs_models.yaml +1 -0
- {model_library-0.1.1 → model_library-0.1.3}/model_library/config/all_models.json +461 -36
- {model_library-0.1.1 → model_library-0.1.3}/model_library/config/anthropic_models.yaml +30 -3
- {model_library-0.1.1 → model_library-0.1.3}/model_library/config/deepseek_models.yaml +3 -1
- {model_library-0.1.1 → model_library-0.1.3}/model_library/config/google_models.yaml +49 -0
- {model_library-0.1.1 → model_library-0.1.3}/model_library/config/openai_models.yaml +43 -4
- {model_library-0.1.1 → model_library-0.1.3}/model_library/config/together_models.yaml +1 -0
- {model_library-0.1.1 → model_library-0.1.3}/model_library/config/xai_models.yaml +63 -3
- {model_library-0.1.1 → model_library-0.1.3}/model_library/exceptions.py +8 -2
- {model_library-0.1.1 → model_library-0.1.3}/model_library/file_utils.py +1 -1
- {model_library-0.1.1 → model_library-0.1.3}/model_library/providers/ai21labs.py +2 -0
- {model_library-0.1.1 → model_library-0.1.3}/model_library/providers/alibaba.py +16 -78
- {model_library-0.1.1 → model_library-0.1.3}/model_library/providers/amazon.py +3 -0
- {model_library-0.1.1 → model_library-0.1.3}/model_library/providers/anthropic.py +215 -8
- {model_library-0.1.1 → model_library-0.1.3}/model_library/providers/azure.py +2 -0
- model_library-0.1.3/model_library/providers/cohere.py +34 -0
- model_library-0.1.3/model_library/providers/deepseek.py +39 -0
- model_library-0.1.3/model_library/providers/fireworks.py +69 -0
- {model_library-0.1.1 → model_library-0.1.3}/model_library/providers/google/google.py +55 -47
- model_library-0.1.3/model_library/providers/inception.py +34 -0
- model_library-0.1.3/model_library/providers/kimi.py +34 -0
- {model_library-0.1.1 → model_library-0.1.3}/model_library/providers/mistral.py +2 -0
- {model_library-0.1.1 → model_library-0.1.3}/model_library/providers/openai.py +10 -2
- model_library-0.1.3/model_library/providers/perplexity.py +34 -0
- model_library-0.1.3/model_library/providers/together.py +58 -0
- {model_library-0.1.1 → model_library-0.1.3}/model_library/providers/vals.py +2 -0
- {model_library-0.1.1 → model_library-0.1.3}/model_library/providers/xai.py +2 -0
- model_library-0.1.3/model_library/providers/zai.py +34 -0
- {model_library-0.1.1 → model_library-0.1.3}/model_library/register_models.py +75 -57
- {model_library-0.1.1 → model_library-0.1.3}/model_library/registry_utils.py +5 -5
- {model_library-0.1.1 → model_library-0.1.3}/model_library/utils.py +3 -28
- {model_library-0.1.1 → model_library-0.1.3}/model_library.egg-info/PKG-INFO +2 -3
- {model_library-0.1.1 → model_library-0.1.3}/model_library.egg-info/SOURCES.txt +10 -3
- {model_library-0.1.1 → model_library-0.1.3}/model_library.egg-info/requires.txt +1 -2
- {model_library-0.1.1 → model_library-0.1.3}/pyproject.toml +1 -2
- {model_library-0.1.1 → model_library-0.1.3}/scripts/run_models.py +5 -1
- {model_library-0.1.1 → model_library-0.1.3}/tests/conftest.py +4 -0
- {model_library-0.1.1 → model_library-0.1.3}/tests/integration/test_batch.py +64 -1
- {model_library-0.1.1 → model_library-0.1.3}/tests/unit/conftest.py +25 -1
- model_library-0.1.3/tests/unit/providers/__init__.py +0 -0
- model_library-0.1.1/tests/unit/test_model_registry.py → model_library-0.1.3/tests/unit/test_registry.py +54 -1
- model_library-0.1.3/uv.lock +2128 -0
- model_library-0.1.1/model_library/providers/deepseek.py +0 -115
- model_library-0.1.1/model_library/providers/fireworks.py +0 -133
- model_library-0.1.1/model_library/providers/inception.py +0 -102
- model_library-0.1.1/model_library/providers/kimi.py +0 -102
- model_library-0.1.1/model_library/providers/perplexity.py +0 -101
- model_library-0.1.1/model_library/providers/together.py +0 -249
- model_library-0.1.1/model_library/providers/zai.py +0 -102
- model_library-0.1.1/uv.lock +0 -1895
- {model_library-0.1.1 → model_library-0.1.3}/.gitattributes +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/.github/workflows/publish.yml +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/.github/workflows/style.yaml +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/.github/workflows/test.yaml +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/.github/workflows/typecheck.yml +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/.gitignore +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/LICENSE +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/README.md +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/examples/README.md +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/examples/advanced/batch.py +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/examples/advanced/custom_retrier.py +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/examples/advanced/deep_research.py +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/examples/advanced/stress.py +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/examples/advanced/structured_output.py +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/examples/advanced/web_search.py +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/examples/basics.py +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/examples/data/files.py +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/examples/data/images.py +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/examples/embeddings.py +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/examples/files.py +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/examples/images.py +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/examples/prompt_caching.py +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/examples/setup.py +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/examples/tool_calls.py +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/model_library/__init__.py +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/model_library/config/alibaba_models.yaml +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/model_library/config/amazon_models.yaml +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/model_library/config/cohere_models.yaml +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/model_library/config/dummy_model.yaml +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/model_library/config/fireworks_models.yaml +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/model_library/config/inception_models.yaml +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/model_library/config/kimi_models.yaml +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/model_library/config/mistral_models.yaml +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/model_library/config/perplexity_models.yaml +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/model_library/config/zai_models.yaml +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/model_library/logging.py +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/model_library/model_utils.py +0 -0
- {model_library-0.1.1/tests → model_library-0.1.3/model_library/providers}/__init__.py +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/model_library/providers/google/__init__.py +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/model_library/providers/google/batch.py +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/model_library/py.typed +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/model_library/settings.py +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/model_library.egg-info/dependency_links.txt +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/model_library.egg-info/top_level.txt +0 -0
- /model_library-0.1.1/scripts/explore_models.py → /model_library-0.1.3/scripts/browse_models.py +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/scripts/config.py +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/scripts/publish.py +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/setup.cfg +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/tests/README.md +0 -0
- {model_library-0.1.1/tests/integration → model_library-0.1.3/tests}/__init__.py +0 -0
- {model_library-0.1.1/tests/unit → model_library-0.1.3/tests/integration}/__init__.py +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/tests/integration/conftest.py +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/tests/integration/test_completion.py +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/tests/integration/test_files.py +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/tests/integration/test_reasoning.py +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/tests/integration/test_retry.py +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/tests/integration/test_streaming.py +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/tests/integration/test_structured_output.py +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/tests/integration/test_tools.py +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/tests/test_helpers.py +0 -0
- {model_library-0.1.1/tests/unit/providers → model_library-0.1.3/tests/unit}/__init__.py +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/tests/unit/providers/test_fireworks_provider.py +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/tests/unit/providers/test_google_provider.py +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/tests/unit/test_batch.py +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/tests/unit/test_context_window.py +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/tests/unit/test_deep_research.py +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/tests/unit/test_perplexity_provider.py +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/tests/unit/test_prompt_caching.py +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/tests/unit/test_retry.py +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/tests/unit/test_streaming.py +0 -0
- {model_library-0.1.1 → model_library-0.1.3}/tests/unit/test_tools.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: model-library
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: Model Library for vals.ai
|
|
5
5
|
Author-email: "Vals AI, Inc." <contact@vals.ai>
|
|
6
6
|
License: MIT
|
|
@@ -17,12 +17,11 @@ Requires-Dist: tiktoken==0.11.0
|
|
|
17
17
|
Requires-Dist: pillow
|
|
18
18
|
Requires-Dist: openai<2.0,>=1.97.1
|
|
19
19
|
Requires-Dist: anthropic<1.0,>=0.57.1
|
|
20
|
-
Requires-Dist: together<2.0,>=1.5.25
|
|
21
20
|
Requires-Dist: mistralai<2.0,>=1.9.10
|
|
22
21
|
Requires-Dist: xai-sdk<2.0,>=1.0.0
|
|
23
22
|
Requires-Dist: ai21<5.0,>=4.0.3
|
|
24
23
|
Requires-Dist: boto3<2.0,>=1.38.27
|
|
25
|
-
Requires-Dist: google-genai[aiohttp]
|
|
24
|
+
Requires-Dist: google-genai[aiohttp]>=1.51.0
|
|
26
25
|
Requires-Dist: google-cloud-storage>=1.26.0
|
|
27
26
|
Dynamic: license-file
|
|
28
27
|
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# ruff: noqa: F403,F401
|
|
2
|
+
from model_library.base.base import *
|
|
3
|
+
from model_library.base.batch import *
|
|
4
|
+
from model_library.base.delegate_only import *
|
|
5
|
+
from model_library.base.input import *
|
|
6
|
+
from model_library.base.output import *
|
|
7
|
+
from model_library.base.utils import *
|
|
@@ -7,293 +7,46 @@ from collections.abc import Awaitable
|
|
|
7
7
|
from pprint import pformat
|
|
8
8
|
from typing import (
|
|
9
9
|
TYPE_CHECKING,
|
|
10
|
-
Annotated,
|
|
11
10
|
Any,
|
|
12
11
|
Callable,
|
|
13
12
|
Literal,
|
|
14
|
-
Mapping,
|
|
15
13
|
Sequence,
|
|
16
14
|
TypeVar,
|
|
17
|
-
cast,
|
|
18
15
|
)
|
|
19
16
|
|
|
20
|
-
from pydantic import
|
|
21
|
-
from pydantic.fields import Field
|
|
17
|
+
from pydantic import model_serializer
|
|
22
18
|
from pydantic.main import BaseModel
|
|
23
19
|
from typing_extensions import override
|
|
24
20
|
|
|
21
|
+
from model_library.base.batch import (
|
|
22
|
+
LLMBatchMixin,
|
|
23
|
+
)
|
|
24
|
+
from model_library.base.input import (
|
|
25
|
+
FileInput,
|
|
26
|
+
FileWithId,
|
|
27
|
+
InputItem,
|
|
28
|
+
TextInput,
|
|
29
|
+
ToolDefinition,
|
|
30
|
+
ToolResult,
|
|
31
|
+
)
|
|
32
|
+
from model_library.base.output import (
|
|
33
|
+
QueryResult,
|
|
34
|
+
QueryResultCost,
|
|
35
|
+
QueryResultMetadata,
|
|
36
|
+
)
|
|
37
|
+
from model_library.base.utils import (
|
|
38
|
+
get_pretty_input_types,
|
|
39
|
+
)
|
|
25
40
|
from model_library.exceptions import (
|
|
26
41
|
ImmediateRetryException,
|
|
27
42
|
retry_llm_call,
|
|
28
43
|
)
|
|
29
|
-
from model_library.utils import
|
|
30
|
-
|
|
31
|
-
PydanticT = TypeVar("PydanticT", bound=BaseModel)
|
|
32
|
-
|
|
33
|
-
DEFAULT_MAX_TOKENS = 2048
|
|
34
|
-
DEFAULT_TEMPERATURE = 0.7
|
|
35
|
-
DEFAULT_TOP_P = 1
|
|
44
|
+
from model_library.utils import truncate_str
|
|
36
45
|
|
|
37
46
|
if TYPE_CHECKING:
|
|
38
47
|
from model_library.providers.openai import OpenAIModel
|
|
39
48
|
|
|
40
|
-
""
|
|
41
|
-
--- FILES ---
|
|
42
|
-
"""
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
class FileBase(BaseModel):
|
|
46
|
-
type: Literal["image", "file"]
|
|
47
|
-
name: str
|
|
48
|
-
mime: str
|
|
49
|
-
|
|
50
|
-
@override
|
|
51
|
-
def __repr__(self):
|
|
52
|
-
attrs = vars(self).copy()
|
|
53
|
-
if "base64" in attrs:
|
|
54
|
-
attrs["base64"] = truncate_str(attrs["base64"])
|
|
55
|
-
return f"{self.__class__.__name__}(\n{pformat(attrs, indent=2)}\n)"
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
class FileWithBase64(FileBase):
|
|
59
|
-
append_type: Literal["base64"] = "base64"
|
|
60
|
-
base64: str
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
class FileWithUrl(FileBase):
|
|
64
|
-
append_type: Literal["url"] = "url"
|
|
65
|
-
url: str
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
class FileWithId(FileBase):
|
|
69
|
-
append_type: Literal["file_id"] = "file_id"
|
|
70
|
-
file_id: str
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
FileInput = Annotated[
|
|
74
|
-
FileWithBase64 | FileWithUrl | FileWithId,
|
|
75
|
-
Field(discriminator="append_type"),
|
|
76
|
-
]
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
"""
|
|
80
|
-
--- TOOLS ---
|
|
81
|
-
"""
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
class ToolBody(BaseModel):
|
|
85
|
-
name: str
|
|
86
|
-
description: str
|
|
87
|
-
properties: dict[str, Any]
|
|
88
|
-
required: list[str]
|
|
89
|
-
kwargs: dict[str, Any] = {}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
class ToolDefinition(BaseModel):
|
|
93
|
-
name: str # acts as a key
|
|
94
|
-
body: ToolBody | Any
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
class ToolCall(BaseModel):
|
|
98
|
-
id: str
|
|
99
|
-
call_id: str | None = None
|
|
100
|
-
name: str
|
|
101
|
-
args: dict[str, Any] | str
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
"""
|
|
105
|
-
--- INPUT ---
|
|
106
|
-
"""
|
|
107
|
-
|
|
108
|
-
RawResponse = Any
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
class ToolInput(BaseModel):
|
|
112
|
-
tools: list[ToolDefinition] = []
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
class ToolResult(BaseModel):
|
|
116
|
-
tool_call: ToolCall
|
|
117
|
-
result: Any
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
class TextInput(BaseModel):
|
|
121
|
-
text: str
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
RawInputItem = dict[
|
|
125
|
-
str, Any
|
|
126
|
-
] # to pass in, for example, a mock convertsation with {"role": "user", "content": "Hello"}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
InputItem = (
|
|
130
|
-
TextInput | FileInput | ToolResult | RawInputItem | RawResponse
|
|
131
|
-
) # input item can either be a prompt, a file (image or file), a tool call result, raw input, or a previous response
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
"""
|
|
135
|
-
--- OUTPUT ---
|
|
136
|
-
"""
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
class Citation(BaseModel):
|
|
140
|
-
type: str | None = None
|
|
141
|
-
title: str | None = None
|
|
142
|
-
url: str | None = None
|
|
143
|
-
start_index: int | None = None
|
|
144
|
-
end_index: int | None = None
|
|
145
|
-
file_id: str | None = None
|
|
146
|
-
filename: str | None = None
|
|
147
|
-
index: int | None = None
|
|
148
|
-
container_id: str | None = None
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
class QueryResultExtras(BaseModel):
|
|
152
|
-
citations: list[Citation] = Field(default_factory=list)
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
class QueryResultCost(BaseModel):
|
|
156
|
-
"""
|
|
157
|
-
Cost information for a query
|
|
158
|
-
Includes total cost and a structured breakdown.
|
|
159
|
-
"""
|
|
160
|
-
|
|
161
|
-
input: float
|
|
162
|
-
output: float
|
|
163
|
-
reasoning: float | None = None
|
|
164
|
-
cache_read: float | None = None
|
|
165
|
-
cache_write: float | None = None
|
|
166
|
-
|
|
167
|
-
@computed_field
|
|
168
|
-
@property
|
|
169
|
-
def total(self) -> float:
|
|
170
|
-
return sum(
|
|
171
|
-
filter(
|
|
172
|
-
None,
|
|
173
|
-
[
|
|
174
|
-
self.input,
|
|
175
|
-
self.output,
|
|
176
|
-
self.reasoning,
|
|
177
|
-
self.cache_read,
|
|
178
|
-
self.cache_write,
|
|
179
|
-
],
|
|
180
|
-
)
|
|
181
|
-
)
|
|
182
|
-
|
|
183
|
-
@override
|
|
184
|
-
def __repr__(self):
|
|
185
|
-
use_cents = self.total < 1
|
|
186
|
-
|
|
187
|
-
def format_cost(value: float | None):
|
|
188
|
-
if value is None:
|
|
189
|
-
return None
|
|
190
|
-
return f"{value * 100:.3f} cents" if use_cents else f"${value:.2f}"
|
|
191
|
-
|
|
192
|
-
return (
|
|
193
|
-
f"{format_cost(self.total)} "
|
|
194
|
-
+ f"(uncached input: {format_cost(self.input)} | output: {format_cost(self.output)} | reasoning: {format_cost(self.reasoning)} | cache_read: {format_cost(self.cache_read)} | cache_write: {format_cost(self.cache_write)})"
|
|
195
|
-
)
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
class QueryResultMetadata(BaseModel):
|
|
199
|
-
"""
|
|
200
|
-
Metadata for a query: token usage and timing.
|
|
201
|
-
|
|
202
|
-
"""
|
|
203
|
-
|
|
204
|
-
cost: QueryResultCost | None = None # set post query
|
|
205
|
-
duration_seconds: float | None = None # set post query
|
|
206
|
-
in_tokens: int = 0
|
|
207
|
-
out_tokens: int = 0
|
|
208
|
-
reasoning_tokens: int | None = None
|
|
209
|
-
cache_read_tokens: int | None = None
|
|
210
|
-
cache_write_tokens: int | None = None
|
|
211
|
-
|
|
212
|
-
@property
|
|
213
|
-
def default_duration_seconds(self) -> float:
|
|
214
|
-
return self.duration_seconds or 0
|
|
215
|
-
|
|
216
|
-
def __add__(self, other: "QueryResultMetadata") -> "QueryResultMetadata":
|
|
217
|
-
return QueryResultMetadata(
|
|
218
|
-
in_tokens=self.in_tokens + other.in_tokens,
|
|
219
|
-
out_tokens=self.out_tokens + other.out_tokens,
|
|
220
|
-
reasoning_tokens=sum_optional(
|
|
221
|
-
self.reasoning_tokens, other.reasoning_tokens
|
|
222
|
-
),
|
|
223
|
-
cache_read_tokens=sum_optional(
|
|
224
|
-
self.cache_read_tokens, other.cache_read_tokens
|
|
225
|
-
),
|
|
226
|
-
cache_write_tokens=sum_optional(
|
|
227
|
-
self.cache_write_tokens, other.cache_write_tokens
|
|
228
|
-
),
|
|
229
|
-
duration_seconds=self.default_duration_seconds
|
|
230
|
-
+ other.default_duration_seconds,
|
|
231
|
-
)
|
|
232
|
-
|
|
233
|
-
@override
|
|
234
|
-
def __repr__(self):
|
|
235
|
-
attrs = vars(self).copy()
|
|
236
|
-
return f"{self.__class__.__name__}(\n{pformat(attrs, indent=2, sort_dicts=False)}\n)"
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
class QueryResult(BaseModel):
|
|
240
|
-
"""
|
|
241
|
-
Result of a query
|
|
242
|
-
Contains the text, reasoning, metadata, tool calls, and history
|
|
243
|
-
"""
|
|
244
|
-
|
|
245
|
-
output_text: str | None = None
|
|
246
|
-
reasoning: str | None = None
|
|
247
|
-
metadata: QueryResultMetadata = Field(default_factory=QueryResultMetadata)
|
|
248
|
-
tool_calls: list[ToolCall] = Field(default_factory=list)
|
|
249
|
-
history: list[InputItem] = Field(default_factory=list)
|
|
250
|
-
extras: QueryResultExtras = Field(default_factory=QueryResultExtras)
|
|
251
|
-
raw: dict[str, Any] = Field(default_factory=dict)
|
|
252
|
-
|
|
253
|
-
@property
|
|
254
|
-
def output_text_str(self) -> str:
|
|
255
|
-
return self.output_text or ""
|
|
256
|
-
|
|
257
|
-
@field_validator("reasoning", mode="before")
|
|
258
|
-
def default_reasoning(cls, v: str | None):
|
|
259
|
-
return None if not v else v # make reasoning None if empty
|
|
260
|
-
|
|
261
|
-
@property
|
|
262
|
-
def search_results(self) -> Any | None:
|
|
263
|
-
"""Expose provider-supplied search metadata without additional processing."""
|
|
264
|
-
raw_dict = cast(dict[str, Any], getattr(self, "raw", {}))
|
|
265
|
-
raw_candidate = raw_dict.get("search_results")
|
|
266
|
-
if raw_candidate is not None:
|
|
267
|
-
return raw_candidate
|
|
268
|
-
|
|
269
|
-
return _get_from_history(self.history, "search_results")
|
|
270
|
-
|
|
271
|
-
@override
|
|
272
|
-
def __repr__(self):
|
|
273
|
-
attrs = vars(self).copy()
|
|
274
|
-
ordered_attrs = {
|
|
275
|
-
"output_text": truncate_str(attrs.pop("output_text", None), 400),
|
|
276
|
-
"reasoning": truncate_str(attrs.pop("reasoning", None), 400),
|
|
277
|
-
"metadata": attrs.pop("metadata", None),
|
|
278
|
-
}
|
|
279
|
-
if self.tool_calls:
|
|
280
|
-
ordered_attrs["tool_calls"] = self.tool_calls
|
|
281
|
-
return f"{self.__class__.__name__}(\n{pformat(ordered_attrs, indent=2, sort_dicts=False)}\n)"
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
def _get_from_history(history: Sequence[InputItem], key: str) -> Any | None:
|
|
285
|
-
for item in reversed(history):
|
|
286
|
-
value = getattr(item, key, None)
|
|
287
|
-
if value is not None:
|
|
288
|
-
return value
|
|
289
|
-
|
|
290
|
-
extra = getattr(item, "model_extra", None)
|
|
291
|
-
if isinstance(extra, Mapping):
|
|
292
|
-
value = cast(Mapping[str, Any], extra).get(key)
|
|
293
|
-
if value is not None:
|
|
294
|
-
return value
|
|
295
|
-
|
|
296
|
-
return None
|
|
49
|
+
PydanticT = TypeVar("PydanticT", bound=BaseModel)
|
|
297
50
|
|
|
298
51
|
|
|
299
52
|
class ProviderConfig(BaseModel):
|
|
@@ -304,6 +57,9 @@ class ProviderConfig(BaseModel):
|
|
|
304
57
|
return self.__dict__
|
|
305
58
|
|
|
306
59
|
|
|
60
|
+
DEFAULT_MAX_TOKENS = 2048
|
|
61
|
+
|
|
62
|
+
|
|
307
63
|
class LLMConfig(BaseModel):
|
|
308
64
|
max_tokens: int = DEFAULT_MAX_TOKENS
|
|
309
65
|
temperature: float | None = None
|
|
@@ -347,9 +103,6 @@ class LLM(ABC):
|
|
|
347
103
|
config = config or LLMConfig()
|
|
348
104
|
self._registry_key = config.registry_key
|
|
349
105
|
|
|
350
|
-
if config.provider_config:
|
|
351
|
-
self.provider_config = config.provider_config
|
|
352
|
-
|
|
353
106
|
self.max_tokens: int = config.max_tokens
|
|
354
107
|
self.temperature: float | None = config.temperature
|
|
355
108
|
self.top_p: float | None = config.top_p
|
|
@@ -368,6 +121,12 @@ class LLM(ABC):
|
|
|
368
121
|
self.delegate: "OpenAIModel | None" = None
|
|
369
122
|
self.batch: LLMBatchMixin | None = None
|
|
370
123
|
|
|
124
|
+
if config.provider_config:
|
|
125
|
+
if isinstance(
|
|
126
|
+
config.provider_config, type(getattr(self, "provider_config"))
|
|
127
|
+
):
|
|
128
|
+
self.provider_config = config.provider_config
|
|
129
|
+
|
|
371
130
|
self.logger: logging.Logger = logging.getLogger(
|
|
372
131
|
f"llm.{provider}.{model_name}<instance={self.instance_id}>"
|
|
373
132
|
)
|
|
@@ -459,6 +218,10 @@ class LLM(ABC):
|
|
|
459
218
|
Join input with history
|
|
460
219
|
Log, Time, and Retry
|
|
461
220
|
"""
|
|
221
|
+
|
|
222
|
+
# verbose on debug
|
|
223
|
+
verbose = self.logger.isEnabledFor(logging.DEBUG)
|
|
224
|
+
|
|
462
225
|
# format str input
|
|
463
226
|
if isinstance(input, str):
|
|
464
227
|
input = [TextInput(text=input)]
|
|
@@ -467,11 +230,11 @@ class LLM(ABC):
|
|
|
467
230
|
input = [*files, *images, *input]
|
|
468
231
|
|
|
469
232
|
# format input info
|
|
470
|
-
item_info =
|
|
233
|
+
item_info = (
|
|
234
|
+
f"--- input ({len(input)}): {get_pretty_input_types(input, verbose)}\n"
|
|
235
|
+
)
|
|
471
236
|
if history:
|
|
472
|
-
item_info += (
|
|
473
|
-
f"--- history({len(history)}): {get_pretty_input_types(history)}\n"
|
|
474
|
-
)
|
|
237
|
+
item_info += f"--- history({len(history)}): {get_pretty_input_types(history, verbose)}\n"
|
|
475
238
|
|
|
476
239
|
# format tool info
|
|
477
240
|
tool_results = [t for t in input if isinstance(t, ToolResult)]
|
|
@@ -492,7 +255,7 @@ class LLM(ABC):
|
|
|
492
255
|
|
|
493
256
|
# unique logger for the query
|
|
494
257
|
query_id = uuid.uuid4().hex[:14]
|
|
495
|
-
query_logger =
|
|
258
|
+
query_logger = self.logger.getChild(f"query={query_id}")
|
|
496
259
|
|
|
497
260
|
query_logger.info(
|
|
498
261
|
"Query started:\n" + item_info + tool_info + f"--- kwargs: {short_kwargs}\n"
|
|
@@ -518,36 +281,10 @@ class LLM(ABC):
|
|
|
518
281
|
output.metadata.cost = await self._calculate_cost(output.metadata)
|
|
519
282
|
|
|
520
283
|
query_logger.info(f"Query completed: {repr(output)}")
|
|
284
|
+
query_logger.debug(output.model_dump(exclude={"history", "raw"}))
|
|
521
285
|
|
|
522
286
|
return output
|
|
523
287
|
|
|
524
|
-
async def query_json(
|
|
525
|
-
self,
|
|
526
|
-
input: Sequence[InputItem],
|
|
527
|
-
pydantic_model: type[PydanticT],
|
|
528
|
-
**kwargs: object,
|
|
529
|
-
) -> PydanticT:
|
|
530
|
-
"""Query the model with JSON response format using Pydantic model.
|
|
531
|
-
|
|
532
|
-
This is a convenience method that is not implemented for all providers.
|
|
533
|
-
Only OpenAI and Google providers currently support this method.
|
|
534
|
-
|
|
535
|
-
Args:
|
|
536
|
-
input: Input items (text, files, etc.)
|
|
537
|
-
pydantic_model: Pydantic model class defining the expected response structure
|
|
538
|
-
**kwargs: Additional arguments passed to the query method
|
|
539
|
-
|
|
540
|
-
Returns:
|
|
541
|
-
Instance of the pydantic_model with the model's response
|
|
542
|
-
|
|
543
|
-
Raises:
|
|
544
|
-
NotImplementedError: If the provider does not support structured JSON output
|
|
545
|
-
"""
|
|
546
|
-
raise NotImplementedError(
|
|
547
|
-
f"query_json is not implemented for {self.__class__.__name__}. "
|
|
548
|
-
f"Only OpenAI and Google providers currently support this method."
|
|
549
|
-
)
|
|
550
|
-
|
|
551
288
|
async def _calculate_cost(
|
|
552
289
|
self,
|
|
553
290
|
metadata: QueryResultMetadata,
|
|
@@ -584,7 +321,7 @@ class LLM(ABC):
|
|
|
584
321
|
)
|
|
585
322
|
|
|
586
323
|
# costs for long context
|
|
587
|
-
total_in = metadata.
|
|
324
|
+
total_in = metadata.total_input_tokens
|
|
588
325
|
if costs.context and total_in > costs.context.threshold:
|
|
589
326
|
input_cost, output_cost = costs.context.get_costs(
|
|
590
327
|
input_cost,
|
|
@@ -678,137 +415,29 @@ class LLM(ABC):
|
|
|
678
415
|
"""Upload a file to the model provider"""
|
|
679
416
|
...
|
|
680
417
|
|
|
681
|
-
|
|
682
|
-
class BatchResult(BaseModel):
|
|
683
|
-
custom_id: str
|
|
684
|
-
output: QueryResult
|
|
685
|
-
error_message: str | None = None
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
class LLMBatchMixin(ABC):
|
|
689
|
-
@abstractmethod
|
|
690
|
-
async def create_batch_query_request(
|
|
418
|
+
async def query_json(
|
|
691
419
|
self,
|
|
692
|
-
custom_id: str,
|
|
693
420
|
input: Sequence[InputItem],
|
|
421
|
+
pydantic_model: type[PydanticT],
|
|
694
422
|
**kwargs: object,
|
|
695
|
-
) ->
|
|
696
|
-
"""
|
|
697
|
-
|
|
698
|
-
The batch api sends out a batch of query requests to various endpoints.
|
|
423
|
+
) -> PydanticT:
|
|
424
|
+
"""Query the model with JSON response format using Pydantic model.
|
|
699
425
|
|
|
700
|
-
|
|
426
|
+
This is a convenience method that is not implemented for all providers.
|
|
427
|
+
Only OpenAI and Google providers currently support this method.
|
|
701
428
|
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
429
|
+
Args:
|
|
430
|
+
input: Input items (text, files, etc.)
|
|
431
|
+
pydantic_model: Pydantic model class defining the expected response structure
|
|
432
|
+
**kwargs: Additional arguments passed to the query method
|
|
705
433
|
|
|
706
|
-
@abstractmethod
|
|
707
|
-
async def batch_query(
|
|
708
|
-
self,
|
|
709
|
-
batch_name: str,
|
|
710
|
-
requests: list[dict[str, Any]],
|
|
711
|
-
) -> str:
|
|
712
|
-
"""
|
|
713
|
-
Batch query the model
|
|
714
434
|
Returns:
|
|
715
|
-
|
|
716
|
-
Raises:
|
|
717
|
-
Exception: If failed to batch query
|
|
718
|
-
"""
|
|
719
|
-
...
|
|
720
|
-
|
|
721
|
-
@abstractmethod
|
|
722
|
-
async def get_batch_results(self, batch_id: str) -> list[BatchResult]:
|
|
723
|
-
"""
|
|
724
|
-
Returns results for batch
|
|
725
|
-
Raises:
|
|
726
|
-
Exception: If failed to get results
|
|
727
|
-
"""
|
|
728
|
-
...
|
|
729
|
-
|
|
730
|
-
@abstractmethod
|
|
731
|
-
async def get_batch_progress(self, batch_id: str) -> int:
|
|
732
|
-
"""
|
|
733
|
-
Returns number of completed requests for batch
|
|
734
|
-
Raises:
|
|
735
|
-
Exception: If failed to get progress
|
|
736
|
-
"""
|
|
737
|
-
...
|
|
435
|
+
Instance of the pydantic_model with the model's response
|
|
738
436
|
|
|
739
|
-
@abstractmethod
|
|
740
|
-
async def cancel_batch_request(self, batch_id: str) -> None:
|
|
741
|
-
"""
|
|
742
|
-
Cancels batch
|
|
743
437
|
Raises:
|
|
744
|
-
|
|
745
|
-
"""
|
|
746
|
-
...
|
|
747
|
-
|
|
748
|
-
@abstractmethod
|
|
749
|
-
async def get_batch_status(
|
|
750
|
-
self,
|
|
751
|
-
batch_id: str,
|
|
752
|
-
) -> str:
|
|
753
|
-
"""
|
|
754
|
-
Returns batch status
|
|
755
|
-
Raises:
|
|
756
|
-
Exception: If failed to get status
|
|
757
|
-
"""
|
|
758
|
-
...
|
|
759
|
-
|
|
760
|
-
@classmethod
|
|
761
|
-
@abstractmethod
|
|
762
|
-
def is_batch_status_completed(
|
|
763
|
-
cls,
|
|
764
|
-
batch_status: str,
|
|
765
|
-
) -> bool:
|
|
766
|
-
"""
|
|
767
|
-
Returns if batch status is completed
|
|
768
|
-
|
|
769
|
-
A completed state is any state that is final and not in-progress
|
|
770
|
-
Example: failed | cancelled | expired | completed
|
|
771
|
-
|
|
772
|
-
An incompleted state is any state that is not completed
|
|
773
|
-
Example: in_progress | pending | running
|
|
438
|
+
NotImplementedError: If the provider does not support structured JSON output
|
|
774
439
|
"""
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
def is_batch_status_failed(
|
|
780
|
-
cls,
|
|
781
|
-
batch_status: str,
|
|
782
|
-
) -> bool:
|
|
783
|
-
"""Returns if batch status is failed"""
|
|
784
|
-
...
|
|
785
|
-
|
|
786
|
-
@classmethod
|
|
787
|
-
@abstractmethod
|
|
788
|
-
def is_batch_status_cancelled(
|
|
789
|
-
cls,
|
|
790
|
-
batch_status: str,
|
|
791
|
-
) -> bool:
|
|
792
|
-
"""Returns if batch status is cancelled"""
|
|
793
|
-
...
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
def get_pretty_input_types(input: Sequence["InputItem"]) -> str:
|
|
797
|
-
# for logging
|
|
798
|
-
def process_item(item: "InputItem"):
|
|
799
|
-
match item:
|
|
800
|
-
case TextInput():
|
|
801
|
-
return truncate_str(repr(item))
|
|
802
|
-
case FileBase(): # FileInput
|
|
803
|
-
return repr(item)
|
|
804
|
-
case ToolResult():
|
|
805
|
-
return repr(item)
|
|
806
|
-
case dict():
|
|
807
|
-
item = cast(RawInputItem, item)
|
|
808
|
-
return repr(item)
|
|
809
|
-
case _:
|
|
810
|
-
# RawResponse
|
|
811
|
-
return repr(item)
|
|
812
|
-
|
|
813
|
-
processed_items = [f" {process_item(item)}" for item in input]
|
|
814
|
-
return "\n" + "\n".join(processed_items) if processed_items else ""
|
|
440
|
+
raise NotImplementedError(
|
|
441
|
+
f"query_json is not implemented for {self.__class__.__name__}. "
|
|
442
|
+
f"Only OpenAI and Google providers currently support this method."
|
|
443
|
+
)
|