model-library 0.1.0__py3-none-any.whl → 0.1.2__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 (42) hide show
  1. model_library/__init__.py +7 -3
  2. model_library/base/__init__.py +7 -0
  3. model_library/{base.py → base/base.py} +47 -423
  4. model_library/base/batch.py +121 -0
  5. model_library/base/delegate_only.py +94 -0
  6. model_library/base/input.py +100 -0
  7. model_library/base/output.py +175 -0
  8. model_library/base/utils.py +42 -0
  9. model_library/config/all_models.json +164 -2
  10. model_library/config/anthropic_models.yaml +4 -0
  11. model_library/config/deepseek_models.yaml +3 -1
  12. model_library/config/openai_models.yaml +48 -0
  13. model_library/exceptions.py +2 -0
  14. model_library/logging.py +30 -0
  15. model_library/providers/__init__.py +0 -0
  16. model_library/providers/ai21labs.py +2 -0
  17. model_library/providers/alibaba.py +16 -78
  18. model_library/providers/amazon.py +3 -0
  19. model_library/providers/anthropic.py +213 -2
  20. model_library/providers/azure.py +2 -0
  21. model_library/providers/cohere.py +14 -80
  22. model_library/providers/deepseek.py +14 -90
  23. model_library/providers/fireworks.py +17 -81
  24. model_library/providers/google/google.py +22 -20
  25. model_library/providers/inception.py +15 -83
  26. model_library/providers/kimi.py +15 -83
  27. model_library/providers/mistral.py +2 -0
  28. model_library/providers/openai.py +2 -0
  29. model_library/providers/perplexity.py +12 -79
  30. model_library/providers/together.py +2 -0
  31. model_library/providers/vals.py +2 -0
  32. model_library/providers/xai.py +2 -0
  33. model_library/providers/zai.py +15 -83
  34. model_library/register_models.py +75 -55
  35. model_library/registry_utils.py +5 -5
  36. model_library/utils.py +3 -28
  37. {model_library-0.1.0.dist-info → model_library-0.1.2.dist-info}/METADATA +36 -7
  38. model_library-0.1.2.dist-info/RECORD +61 -0
  39. model_library-0.1.0.dist-info/RECORD +0 -53
  40. {model_library-0.1.0.dist-info → model_library-0.1.2.dist-info}/WHEEL +0 -0
  41. {model_library-0.1.0.dist-info → model_library-0.1.2.dist-info}/licenses/LICENSE +0 -0
  42. {model_library-0.1.0.dist-info → model_library-0.1.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,94 @@
1
+ import io
2
+ from typing import Any, Literal, Sequence
3
+
4
+ from typing_extensions import override
5
+
6
+ from model_library.base import (
7
+ LLM,
8
+ FileInput,
9
+ FileWithId,
10
+ InputItem,
11
+ LLMConfig,
12
+ QueryResult,
13
+ ToolDefinition,
14
+ )
15
+
16
+
17
+ class DelegateOnlyException(Exception):
18
+ """
19
+ Raised when native model functionality is performed on a
20
+ delegate-only model.
21
+ """
22
+
23
+ DEFAULT_MESSAGE: str = "This model supports only delegate-only functionality. Only the query() method should be used."
24
+
25
+ def __init__(self, message: str | None = None):
26
+ super().__init__(message or DelegateOnlyException.DEFAULT_MESSAGE)
27
+
28
+
29
+ class DelegateOnly(LLM):
30
+ @override
31
+ def get_client(self) -> None:
32
+ raise DelegateOnlyException()
33
+
34
+ def __init__(
35
+ self,
36
+ model_name: str,
37
+ provider: str,
38
+ *,
39
+ config: LLMConfig | None = None,
40
+ ):
41
+ config = config or LLMConfig()
42
+ config.native = False
43
+ super().__init__(model_name, provider, config=config)
44
+
45
+ @override
46
+ async def _query_impl(
47
+ self,
48
+ input: Sequence[InputItem],
49
+ *,
50
+ tools: list[ToolDefinition],
51
+ **kwargs: object,
52
+ ) -> QueryResult:
53
+ assert self.delegate
54
+
55
+ return await self.delegate_query(input, tools=tools, **kwargs)
56
+
57
+ @override
58
+ async def parse_input(
59
+ self,
60
+ input: Sequence[InputItem],
61
+ **kwargs: Any,
62
+ ) -> Any:
63
+ raise DelegateOnlyException()
64
+
65
+ @override
66
+ async def parse_image(
67
+ self,
68
+ image: FileInput,
69
+ ) -> Any:
70
+ raise DelegateOnlyException()
71
+
72
+ @override
73
+ async def parse_file(
74
+ self,
75
+ file: FileInput,
76
+ ) -> Any:
77
+ raise DelegateOnlyException()
78
+
79
+ @override
80
+ async def parse_tools(
81
+ self,
82
+ tools: list[ToolDefinition],
83
+ ) -> Any:
84
+ raise DelegateOnlyException()
85
+
86
+ @override
87
+ async def upload_file(
88
+ self,
89
+ name: str,
90
+ mime: str,
91
+ bytes: io.BytesIO,
92
+ type: Literal["image", "file"] = "file",
93
+ ) -> FileWithId:
94
+ raise DelegateOnlyException()
@@ -0,0 +1,100 @@
1
+ from pprint import pformat
2
+ from typing import Annotated, Any, Literal
3
+
4
+ from pydantic import BaseModel, Field
5
+ from typing_extensions import override
6
+
7
+ from model_library.utils import truncate_str
8
+
9
+ """
10
+ --- FILES ---
11
+ """
12
+
13
+
14
+ class FileBase(BaseModel):
15
+ type: Literal["image", "file"]
16
+ name: str
17
+ mime: str
18
+
19
+ @override
20
+ def __repr__(self):
21
+ attrs = vars(self).copy()
22
+ if "base64" in attrs:
23
+ attrs["base64"] = truncate_str(attrs["base64"])
24
+ return f"{self.__class__.__name__}(\n{pformat(attrs, indent=2)}\n)"
25
+
26
+
27
+ class FileWithBase64(FileBase):
28
+ append_type: Literal["base64"] = "base64"
29
+ base64: str
30
+
31
+
32
+ class FileWithUrl(FileBase):
33
+ append_type: Literal["url"] = "url"
34
+ url: str
35
+
36
+
37
+ class FileWithId(FileBase):
38
+ append_type: Literal["file_id"] = "file_id"
39
+ file_id: str
40
+
41
+
42
+ FileInput = Annotated[
43
+ FileWithBase64 | FileWithUrl | FileWithId,
44
+ Field(discriminator="append_type"),
45
+ ]
46
+
47
+
48
+ """
49
+ --- TOOLS ---
50
+ """
51
+
52
+
53
+ class ToolBody(BaseModel):
54
+ name: str
55
+ description: str
56
+ properties: dict[str, Any]
57
+ required: list[str]
58
+ kwargs: dict[str, Any] = {}
59
+
60
+
61
+ class ToolDefinition(BaseModel):
62
+ name: str # acts as a key
63
+ body: ToolBody | Any
64
+
65
+
66
+ class ToolCall(BaseModel):
67
+ id: str
68
+ call_id: str | None = None
69
+ name: str
70
+ args: dict[str, Any] | str
71
+
72
+
73
+ """
74
+ --- INPUT ---
75
+ """
76
+
77
+ RawResponse = Any
78
+
79
+
80
+ class ToolInput(BaseModel):
81
+ tools: list[ToolDefinition] = []
82
+
83
+
84
+ class ToolResult(BaseModel):
85
+ tool_call: ToolCall
86
+ result: Any
87
+
88
+
89
+ class TextInput(BaseModel):
90
+ text: str
91
+
92
+
93
+ RawInputItem = dict[
94
+ str, Any
95
+ ] # to pass in, for example, a mock convertsation with {"role": "user", "content": "Hello"}
96
+
97
+
98
+ InputItem = (
99
+ TextInput | FileInput | ToolResult | RawInputItem | RawResponse
100
+ ) # input item can either be a prompt, a file (image or file), a tool call result, raw input, or a previous response
@@ -0,0 +1,175 @@
1
+ """
2
+ --- OUTPUT ---
3
+ """
4
+
5
+ from pprint import pformat
6
+ from typing import Any, Mapping, Sequence, cast
7
+
8
+ from pydantic import BaseModel, Field, computed_field, field_validator
9
+ from typing_extensions import override
10
+
11
+ from model_library.base.input import InputItem, ToolCall
12
+ from model_library.base.utils import (
13
+ sum_optional,
14
+ )
15
+ from model_library.utils import truncate_str
16
+
17
+
18
+ class Citation(BaseModel):
19
+ type: str | None = None
20
+ title: str | None = None
21
+ url: str | None = None
22
+ start_index: int | None = None
23
+ end_index: int | None = None
24
+ file_id: str | None = None
25
+ filename: str | None = None
26
+ index: int | None = None
27
+ container_id: str | None = None
28
+
29
+
30
+ class QueryResultExtras(BaseModel):
31
+ citations: list[Citation] = Field(default_factory=list)
32
+
33
+
34
+ class QueryResultCost(BaseModel):
35
+ """
36
+ Cost information for a query
37
+ Includes total cost and a structured breakdown.
38
+ """
39
+
40
+ input: float
41
+ output: float
42
+ reasoning: float | None = None
43
+ cache_read: float | None = None
44
+ cache_write: float | None = None
45
+
46
+ @computed_field
47
+ @property
48
+ def total(self) -> float:
49
+ return sum(
50
+ filter(
51
+ None,
52
+ [
53
+ self.input,
54
+ self.output,
55
+ self.reasoning,
56
+ self.cache_read,
57
+ self.cache_write,
58
+ ],
59
+ )
60
+ )
61
+
62
+ @override
63
+ def __repr__(self):
64
+ use_cents = self.total < 1
65
+
66
+ def format_cost(value: float | None):
67
+ if value is None:
68
+ return None
69
+ return f"{value * 100:.3f} cents" if use_cents else f"${value:.2f}"
70
+
71
+ return (
72
+ f"{format_cost(self.total)} "
73
+ + 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)})"
74
+ )
75
+
76
+
77
+ class QueryResultMetadata(BaseModel):
78
+ """
79
+ Metadata for a query: token usage and timing.
80
+
81
+ """
82
+
83
+ cost: QueryResultCost | None = None # set post query
84
+ duration_seconds: float | None = None # set post query
85
+ in_tokens: int = 0
86
+ out_tokens: int = 0
87
+ reasoning_tokens: int | None = None
88
+ cache_read_tokens: int | None = None
89
+ cache_write_tokens: int | None = None
90
+
91
+ @property
92
+ def default_duration_seconds(self) -> float:
93
+ return self.duration_seconds or 0
94
+
95
+ def __add__(self, other: "QueryResultMetadata") -> "QueryResultMetadata":
96
+ return QueryResultMetadata(
97
+ in_tokens=self.in_tokens + other.in_tokens,
98
+ out_tokens=self.out_tokens + other.out_tokens,
99
+ reasoning_tokens=sum_optional(
100
+ self.reasoning_tokens, other.reasoning_tokens
101
+ ),
102
+ cache_read_tokens=sum_optional(
103
+ self.cache_read_tokens, other.cache_read_tokens
104
+ ),
105
+ cache_write_tokens=sum_optional(
106
+ self.cache_write_tokens, other.cache_write_tokens
107
+ ),
108
+ duration_seconds=self.default_duration_seconds
109
+ + other.default_duration_seconds,
110
+ )
111
+
112
+ @override
113
+ def __repr__(self):
114
+ attrs = vars(self).copy()
115
+ return f"{self.__class__.__name__}(\n{pformat(attrs, indent=2, sort_dicts=False)}\n)"
116
+
117
+
118
+ class QueryResult(BaseModel):
119
+ """
120
+ Result of a query
121
+ Contains the text, reasoning, metadata, tool calls, and history
122
+ """
123
+
124
+ output_text: str | None = None
125
+ reasoning: str | None = None
126
+ metadata: QueryResultMetadata = Field(default_factory=QueryResultMetadata)
127
+ tool_calls: list[ToolCall] = Field(default_factory=list)
128
+ history: list[InputItem] = Field(default_factory=list)
129
+ extras: QueryResultExtras = Field(default_factory=QueryResultExtras)
130
+ raw: dict[str, Any] = Field(default_factory=dict)
131
+
132
+ @property
133
+ def output_text_str(self) -> str:
134
+ return self.output_text or ""
135
+
136
+ @field_validator("reasoning", mode="before")
137
+ def default_reasoning(cls, v: str | None):
138
+ return None if not v else v # make reasoning None if empty
139
+
140
+ @property
141
+ def search_results(self) -> Any | None:
142
+ """Expose provider-supplied search metadata without additional processing."""
143
+ raw_dict = cast(dict[str, Any], getattr(self, "raw", {}))
144
+ raw_candidate = raw_dict.get("search_results")
145
+ if raw_candidate is not None:
146
+ return raw_candidate
147
+
148
+ return _get_from_history(self.history, "search_results")
149
+
150
+ @override
151
+ def __repr__(self):
152
+ attrs = vars(self).copy()
153
+ ordered_attrs = {
154
+ "output_text": truncate_str(attrs.pop("output_text", None), 400),
155
+ "reasoning": truncate_str(attrs.pop("reasoning", None), 400),
156
+ "metadata": attrs.pop("metadata", None),
157
+ }
158
+ if self.tool_calls:
159
+ ordered_attrs["tool_calls"] = self.tool_calls
160
+ return f"{self.__class__.__name__}(\n{pformat(ordered_attrs, indent=2, sort_dicts=False)}\n)"
161
+
162
+
163
+ def _get_from_history(history: Sequence[InputItem], key: str) -> Any | None:
164
+ for item in reversed(history):
165
+ value = getattr(item, key, None)
166
+ if value is not None:
167
+ return value
168
+
169
+ extra = getattr(item, "model_extra", None)
170
+ if isinstance(extra, Mapping):
171
+ value = cast(Mapping[str, Any], extra).get(key)
172
+ if value is not None:
173
+ return value
174
+
175
+ return None
@@ -0,0 +1,42 @@
1
+ from typing import Sequence, cast
2
+
3
+ from model_library.base.input import (
4
+ FileBase,
5
+ InputItem,
6
+ RawInputItem,
7
+ TextInput,
8
+ ToolResult,
9
+ )
10
+ from model_library.utils import truncate_str
11
+
12
+
13
+ def sum_optional(a: int | None, b: int | None) -> int | None:
14
+ """Sum two optional integers, returning None if both are None.
15
+
16
+ Preserves None to indicate "unknown/not provided" when both inputs are None,
17
+ otherwise treats None as 0 for summation.
18
+ """
19
+ if a is None and b is None:
20
+ return None
21
+ return (a or 0) + (b or 0)
22
+
23
+
24
+ def get_pretty_input_types(input: Sequence["InputItem"]) -> str:
25
+ # for logging
26
+ def process_item(item: "InputItem"):
27
+ match item:
28
+ case TextInput():
29
+ return truncate_str(repr(item))
30
+ case FileBase(): # FileInput
31
+ return repr(item)
32
+ case ToolResult():
33
+ return repr(item)
34
+ case dict():
35
+ item = cast(RawInputItem, item)
36
+ return repr(item)
37
+ case _:
38
+ # RawResponse
39
+ return repr(item)
40
+
41
+ processed_items = [f" {process_item(item)}" for item in input]
42
+ return "\n" + "\n".join(processed_items) if processed_items else ""
@@ -1,4 +1,148 @@
1
1
  {
2
+ "openai/gpt-5.1-codex-mini": {
3
+ "company": "OpenAI",
4
+ "label": "GPT 5.1 Codex Mini",
5
+ "description": "OpenAI's miniature coding model",
6
+ "release_date": "2025-11-13",
7
+ "open_source": false,
8
+ "documentation_url": "https://platform.openai.com/docs/models/gpt-5.1-codex-mini",
9
+ "properties": {
10
+ "context_window": 400000,
11
+ "max_token_output": 128000,
12
+ "training_cutoff": null,
13
+ "reasoning_model": true
14
+ },
15
+ "class_properties": {
16
+ "supports_images": true,
17
+ "supports_files": true,
18
+ "supports_batch_requests": true,
19
+ "supports_temperature": false,
20
+ "supports_tools": true,
21
+ "deprecated": false,
22
+ "available_for_everyone": false,
23
+ "available_as_evaluator": false,
24
+ "ignored_for_cost": false
25
+ },
26
+ "provider_properties": {},
27
+ "costs_per_million_token": {
28
+ "input": 0.25,
29
+ "output": 2.0,
30
+ "cache": {
31
+ "read": 0.025,
32
+ "write_markup": 1.0
33
+ },
34
+ "batch": {
35
+ "input_discount": 0.5,
36
+ "output_discount": 0.5
37
+ }
38
+ },
39
+ "alternative_keys": [],
40
+ "default_parameters": {
41
+ "max_output_tokens": 128000,
42
+ "temperature": 1.0,
43
+ "reasoning_effort": "high"
44
+ },
45
+ "provider_name": "openai",
46
+ "provider_endpoint": "gpt-5.1-codex-mini",
47
+ "full_key": "openai/gpt-5.1-codex-mini",
48
+ "slug": "openai_gpt-5.1-codex-mini"
49
+ },
50
+ "openai/gpt-5.1-codex": {
51
+ "company": "OpenAI",
52
+ "label": "GPT 5.1 Codex",
53
+ "description": "OpenAI's latest coding model",
54
+ "release_date": "2025-11-13",
55
+ "open_source": false,
56
+ "documentation_url": "https://platform.openai.com/docs/models/gpt-5.1-codex",
57
+ "properties": {
58
+ "context_window": 400000,
59
+ "max_token_output": 128000,
60
+ "training_cutoff": null,
61
+ "reasoning_model": true
62
+ },
63
+ "class_properties": {
64
+ "supports_images": true,
65
+ "supports_files": true,
66
+ "supports_batch_requests": true,
67
+ "supports_temperature": false,
68
+ "supports_tools": true,
69
+ "deprecated": false,
70
+ "available_for_everyone": false,
71
+ "available_as_evaluator": false,
72
+ "ignored_for_cost": false
73
+ },
74
+ "provider_properties": {},
75
+ "costs_per_million_token": {
76
+ "input": 1.25,
77
+ "output": 10.0,
78
+ "cache": {
79
+ "read": 0.125,
80
+ "write_markup": 1.0
81
+ },
82
+ "batch": {
83
+ "input_discount": 0.5,
84
+ "output_discount": 0.5
85
+ }
86
+ },
87
+ "alternative_keys": [],
88
+ "default_parameters": {
89
+ "max_output_tokens": 128000,
90
+ "temperature": 1.0,
91
+ "reasoning_effort": "high"
92
+ },
93
+ "provider_name": "openai",
94
+ "provider_endpoint": "gpt-5.1-codex",
95
+ "full_key": "openai/gpt-5.1-codex",
96
+ "slug": "openai_gpt-5.1-codex"
97
+ },
98
+ "openai/gpt-5.1-2025-11-13": {
99
+ "company": "OpenAI",
100
+ "label": "GPT 5.1",
101
+ "description": "GPT-5.1 is OpenAI's flagship model for coding and agentic tasks with configurable reasoning and non-reasoning effort.",
102
+ "release_date": "2025-11-13",
103
+ "open_source": false,
104
+ "documentation_url": "https://platform.openai.com/docs/models/gpt-5.1",
105
+ "properties": {
106
+ "context_window": 400000,
107
+ "max_token_output": 128000,
108
+ "training_cutoff": "2024-09",
109
+ "reasoning_model": true
110
+ },
111
+ "class_properties": {
112
+ "supports_images": true,
113
+ "supports_files": true,
114
+ "supports_batch_requests": true,
115
+ "supports_temperature": false,
116
+ "supports_tools": true,
117
+ "deprecated": false,
118
+ "available_for_everyone": false,
119
+ "available_as_evaluator": true,
120
+ "ignored_for_cost": false
121
+ },
122
+ "provider_properties": {},
123
+ "costs_per_million_token": {
124
+ "input": 1.25,
125
+ "output": 10.0,
126
+ "cache": {
127
+ "read": 0.125,
128
+ "write_markup": 1.0
129
+ },
130
+ "batch": {
131
+ "input_discount": 0.5,
132
+ "output_discount": 0.5
133
+ }
134
+ },
135
+ "alternative_keys": [],
136
+ "default_parameters": {
137
+ "max_output_tokens": 128000,
138
+ "temperature": 1.0,
139
+ "reasoning_effort": "high"
140
+ },
141
+ "provider_name": "openai",
142
+ "provider_endpoint": "gpt-5.1-2025-11-13",
143
+ "full_key": "openai/gpt-5.1-2025-11-13",
144
+ "slug": "openai_gpt-5.1-2025-11-13"
145
+ },
2
146
  "kimi/kimi-k2-thinking": {
3
147
  "company": "Kimi",
4
148
  "label": "Kimi K2 Thinking",
@@ -187,6 +331,7 @@
187
331
  "class_properties": {
188
332
  "supports_images": true,
189
333
  "supports_files": true,
334
+ "supports_batch_requests": true,
190
335
  "supports_temperature": true,
191
336
  "supports_tools": true,
192
337
  "deprecated": false,
@@ -234,6 +379,7 @@
234
379
  "class_properties": {
235
380
  "supports_images": true,
236
381
  "supports_files": true,
382
+ "supports_batch_requests": true,
237
383
  "supports_temperature": true,
238
384
  "supports_tools": true,
239
385
  "deprecated": false,
@@ -386,7 +532,11 @@
386
532
  "provider_properties": {},
387
533
  "costs_per_million_token": {
388
534
  "input": 0.28,
389
- "output": 0.42
535
+ "output": 0.42,
536
+ "cache": {
537
+ "read_discount": 0.1,
538
+ "write_markup": 1.0
539
+ }
390
540
  },
391
541
  "alternative_keys": [],
392
542
  "default_parameters": {
@@ -424,7 +574,11 @@
424
574
  "provider_properties": {},
425
575
  "costs_per_million_token": {
426
576
  "input": 0.28,
427
- "output": 0.42
577
+ "output": 0.42,
578
+ "cache": {
579
+ "read_discount": 0.1,
580
+ "write_markup": 1.0
581
+ }
428
582
  },
429
583
  "alternative_keys": [],
430
584
  "default_parameters": {
@@ -451,6 +605,7 @@
451
605
  "class_properties": {
452
606
  "supports_images": true,
453
607
  "supports_files": true,
608
+ "supports_batch_requests": true,
454
609
  "supports_temperature": true,
455
610
  "supports_tools": true,
456
611
  "deprecated": false,
@@ -508,6 +663,7 @@
508
663
  "class_properties": {
509
664
  "supports_images": true,
510
665
  "supports_files": true,
666
+ "supports_batch_requests": true,
511
667
  "supports_temperature": true,
512
668
  "supports_tools": true,
513
669
  "deprecated": false,
@@ -1979,6 +2135,7 @@
1979
2135
  "class_properties": {
1980
2136
  "supports_images": true,
1981
2137
  "supports_files": true,
2138
+ "supports_batch_requests": true,
1982
2139
  "supports_temperature": true,
1983
2140
  "supports_tools": true,
1984
2141
  "deprecated": false,
@@ -2026,6 +2183,7 @@
2026
2183
  "class_properties": {
2027
2184
  "supports_images": true,
2028
2185
  "supports_files": true,
2186
+ "supports_batch_requests": true,
2029
2187
  "supports_temperature": true,
2030
2188
  "supports_tools": true,
2031
2189
  "deprecated": false,
@@ -3350,6 +3508,7 @@
3350
3508
  "class_properties": {
3351
3509
  "supports_images": true,
3352
3510
  "supports_files": true,
3511
+ "supports_batch_requests": true,
3353
3512
  "supports_temperature": false,
3354
3513
  "supports_tools": true,
3355
3514
  "deprecated": false,
@@ -3407,6 +3566,7 @@
3407
3566
  "class_properties": {
3408
3567
  "supports_images": true,
3409
3568
  "supports_files": true,
3569
+ "supports_batch_requests": true,
3410
3570
  "supports_temperature": true,
3411
3571
  "supports_tools": true,
3412
3572
  "deprecated": false,
@@ -3475,6 +3635,7 @@
3475
3635
  "class_properties": {
3476
3636
  "supports_images": true,
3477
3637
  "supports_files": true,
3638
+ "supports_batch_requests": true,
3478
3639
  "supports_temperature": true,
3479
3640
  "supports_tools": true,
3480
3641
  "deprecated": false,
@@ -3522,6 +3683,7 @@
3522
3683
  "class_properties": {
3523
3684
  "supports_images": true,
3524
3685
  "supports_files": true,
3686
+ "supports_batch_requests": true,
3525
3687
  "supports_temperature": true,
3526
3688
  "supports_tools": true,
3527
3689
  "deprecated": false,
@@ -23,6 +23,10 @@ base-config:
23
23
  temperature: 1
24
24
 
25
25
  claude-4-models:
26
+ base-config:
27
+ class_properties:
28
+ supports_batch_requests: true
29
+
26
30
  anthropic/claude-opus-4-1-20250805:
27
31
  label: Claude Opus 4.1 (Nonthinking)
28
32
  description: Advanced model for specialized complex
@@ -10,7 +10,7 @@ base-config:
10
10
  ignored_for_cost: false
11
11
  properties:
12
12
  reasoning_model: false
13
-
13
+
14
14
  deepseek-v3p2-exp-models:
15
15
  base-config:
16
16
  class_properties:
@@ -23,6 +23,8 @@ deepseek-v3p2-exp-models:
23
23
  costs_per_million_token:
24
24
  input: 0.28
25
25
  output: 0.42
26
+ cache:
27
+ read_discount: 0.1
26
28
 
27
29
  deepseek/deepseek-chat:
28
30
  label: DeepSeek V3.2-Exp (Nonthinking)