usegradient 1.3.1__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.
- gradient_sdk/__init__.py +122 -0
- gradient_sdk/agent.py +368 -0
- gradient_sdk/client.py +1059 -0
- gradient_sdk/context.py +91 -0
- gradient_sdk/credentials.py +92 -0
- gradient_sdk/decorators.py +202 -0
- gradient_sdk/errors.py +30 -0
- gradient_sdk/graph.py +190 -0
- gradient_sdk/integrations/__init__.py +7 -0
- gradient_sdk/integrations/agent.py +263 -0
- gradient_sdk/integrations/anthropic.py +51 -0
- gradient_sdk/integrations/openai.py +105 -0
- gradient_sdk/model_catalog.py +75 -0
- gradient_sdk/models.py +370 -0
- gradient_sdk/py.typed +1 -0
- gradient_sdk/runtime.py +1474 -0
- gradient_sdk/session.py +132 -0
- gradient_sdk/tools.py +156 -0
- gradient_sdk/tracing.py +361 -0
- usegradient-1.3.1.dist-info/METADATA +235 -0
- usegradient-1.3.1.dist-info/RECORD +22 -0
- usegradient-1.3.1.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Any, Literal
|
|
5
|
+
|
|
6
|
+
from ..tools import Tool, execute_tool_calls
|
|
7
|
+
from ..tracing import get_tracer
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def generate(
|
|
11
|
+
*,
|
|
12
|
+
provider: Literal["openai", "anthropic"],
|
|
13
|
+
model: str,
|
|
14
|
+
interface: Literal["responses", "chat", "messages"] | str | None = None,
|
|
15
|
+
messages: list[dict[str, Any]] | str,
|
|
16
|
+
tools: list[Tool] | None = None,
|
|
17
|
+
client: Any | None = None,
|
|
18
|
+
**kwargs: Any,
|
|
19
|
+
) -> dict[str, Any]:
|
|
20
|
+
"""Run a traced provider call and return a normalized response."""
|
|
21
|
+
|
|
22
|
+
tracer = get_tracer()
|
|
23
|
+
with tracer.start_span(f"llm.{provider}.{model}", kind="LLM") as span:
|
|
24
|
+
span.set_attribute("llm.provider", provider)
|
|
25
|
+
span.set_attribute("llm.model_name", model)
|
|
26
|
+
span.set_attribute(
|
|
27
|
+
"input.value",
|
|
28
|
+
messages if isinstance(messages, str) else json.dumps(messages, default=str),
|
|
29
|
+
)
|
|
30
|
+
if provider == "openai":
|
|
31
|
+
result = _generate_openai(
|
|
32
|
+
model=model,
|
|
33
|
+
interface=interface or "responses",
|
|
34
|
+
messages=messages,
|
|
35
|
+
tools=tools,
|
|
36
|
+
client=client,
|
|
37
|
+
**kwargs,
|
|
38
|
+
)
|
|
39
|
+
else:
|
|
40
|
+
result = _generate_anthropic(model=model, messages=messages, tools=tools, client=client, **kwargs)
|
|
41
|
+
usage = result.get("usage", {})
|
|
42
|
+
span.set_attribute("llm.token_count.prompt", int(usage.get("prompt_tokens", 0)))
|
|
43
|
+
span.set_attribute("llm.token_count.completion", int(usage.get("completion_tokens", 0)))
|
|
44
|
+
span.set_attribute("llm.token_count.total", int(usage.get("total_tokens", 0)))
|
|
45
|
+
span.set_output(result.get("text", ""))
|
|
46
|
+
return result
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def run_agent_loop(
|
|
50
|
+
*,
|
|
51
|
+
provider: Literal["openai", "anthropic"],
|
|
52
|
+
model: str,
|
|
53
|
+
messages: list[dict[str, Any]],
|
|
54
|
+
tools: list[Tool],
|
|
55
|
+
client: Any | None = None,
|
|
56
|
+
max_turns: int = 8,
|
|
57
|
+
**kwargs: Any,
|
|
58
|
+
) -> dict[str, Any]:
|
|
59
|
+
"""Run a traced tool-calling loop similar to LangChain AgentExecutor."""
|
|
60
|
+
|
|
61
|
+
tracer = get_tracer()
|
|
62
|
+
with tracer.start_span("agent.loop", kind="AGENT") as span:
|
|
63
|
+
span.set_attribute("llm.provider", provider)
|
|
64
|
+
span.set_attribute("llm.model_name", model)
|
|
65
|
+
span.set_attribute("input.value", json.dumps(messages, default=str))
|
|
66
|
+
history = list(messages)
|
|
67
|
+
final_text = ""
|
|
68
|
+
usage = {"prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0}
|
|
69
|
+
tool_results: list[dict[str, Any]] = []
|
|
70
|
+
for _ in range(max_turns):
|
|
71
|
+
result = generate(
|
|
72
|
+
provider=provider,
|
|
73
|
+
model=model,
|
|
74
|
+
messages=history,
|
|
75
|
+
tools=tools,
|
|
76
|
+
client=client,
|
|
77
|
+
**kwargs,
|
|
78
|
+
)
|
|
79
|
+
_merge_usage(usage, result.get("usage", {}))
|
|
80
|
+
tool_calls = result.get("tool_calls") or []
|
|
81
|
+
final_text = result.get("text", "")
|
|
82
|
+
if not tool_calls:
|
|
83
|
+
break
|
|
84
|
+
history.append({"role": "assistant", "content": final_text, "tool_calls": tool_calls})
|
|
85
|
+
executed = execute_tool_calls(tools, tool_calls)
|
|
86
|
+
tool_results.extend(executed)
|
|
87
|
+
for item in executed:
|
|
88
|
+
history.append(
|
|
89
|
+
{
|
|
90
|
+
"role": "tool",
|
|
91
|
+
"tool_call_id": item.get("tool_call_id"),
|
|
92
|
+
"name": item.get("name"),
|
|
93
|
+
"content": json.dumps(item.get("output", item.get("error")), default=str),
|
|
94
|
+
}
|
|
95
|
+
)
|
|
96
|
+
payload = {"text": final_text, "messages": history, "usage": usage, "tool_results": tool_results}
|
|
97
|
+
span.set_output(payload)
|
|
98
|
+
return payload
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _merge_usage(target: dict[str, int], incoming: dict[str, int]) -> None:
|
|
102
|
+
for key in ("prompt_tokens", "completion_tokens", "total_tokens"):
|
|
103
|
+
target[key] = int(target.get(key, 0)) + int(incoming.get(key, 0))
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _generate_openai(
|
|
107
|
+
*,
|
|
108
|
+
model: str,
|
|
109
|
+
interface: str,
|
|
110
|
+
messages: list[dict[str, Any]] | str,
|
|
111
|
+
tools: list[Tool] | None,
|
|
112
|
+
client: Any | None,
|
|
113
|
+
**kwargs: Any,
|
|
114
|
+
) -> dict[str, Any]:
|
|
115
|
+
if client is None:
|
|
116
|
+
from openai import OpenAI
|
|
117
|
+
|
|
118
|
+
client = OpenAI()
|
|
119
|
+
if interface == "responses" and hasattr(client, "responses"):
|
|
120
|
+
payload: dict[str, Any] = {"model": model, "input": messages, **kwargs}
|
|
121
|
+
if tools:
|
|
122
|
+
payload["tools"] = [_openai_responses_tool(tool) for tool in tools]
|
|
123
|
+
response = client.responses.create(**payload)
|
|
124
|
+
return _parse_openai_responses_response(response)
|
|
125
|
+
|
|
126
|
+
payload = {"model": model, "messages": messages, **kwargs}
|
|
127
|
+
if tools:
|
|
128
|
+
payload["tools"] = [tool.to_openai() for tool in tools]
|
|
129
|
+
response = client.chat.completions.create(**payload)
|
|
130
|
+
text = ""
|
|
131
|
+
tool_calls: list[dict[str, Any]] = []
|
|
132
|
+
if response.choices:
|
|
133
|
+
message = response.choices[0].message
|
|
134
|
+
text = message.content or ""
|
|
135
|
+
for call in getattr(message, "tool_calls", None) or []:
|
|
136
|
+
tool_calls.append(
|
|
137
|
+
{
|
|
138
|
+
"id": call.id,
|
|
139
|
+
"name": call.function.name,
|
|
140
|
+
"arguments": call.function.arguments,
|
|
141
|
+
}
|
|
142
|
+
)
|
|
143
|
+
usage = getattr(response, "usage", None)
|
|
144
|
+
prompt_tokens = int(getattr(usage, "prompt_tokens", 0) or 0) if usage else 0
|
|
145
|
+
completion_tokens = int(getattr(usage, "completion_tokens", 0) or 0) if usage else 0
|
|
146
|
+
return {
|
|
147
|
+
"text": text,
|
|
148
|
+
"tool_calls": tool_calls,
|
|
149
|
+
"usage": {
|
|
150
|
+
"prompt_tokens": prompt_tokens,
|
|
151
|
+
"completion_tokens": completion_tokens,
|
|
152
|
+
"total_tokens": prompt_tokens + completion_tokens,
|
|
153
|
+
},
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _parse_openai_responses_response(response: Any) -> dict[str, Any]:
|
|
158
|
+
text = str(_get(response, "output_text", "") or "")
|
|
159
|
+
tool_calls: list[dict[str, Any]] = []
|
|
160
|
+
for item in _get(response, "output", []) or []:
|
|
161
|
+
item_type = _get(item, "type", "")
|
|
162
|
+
if item_type == "message" and not text:
|
|
163
|
+
text = _extract_response_message_text(item)
|
|
164
|
+
if item_type in {"function_call", "tool_call"}:
|
|
165
|
+
tool_calls.append(
|
|
166
|
+
{
|
|
167
|
+
"id": _get(item, "id", _get(item, "call_id", None)),
|
|
168
|
+
"name": _get(item, "name", ""),
|
|
169
|
+
"arguments": _get(item, "arguments", _get(item, "input", {})),
|
|
170
|
+
}
|
|
171
|
+
)
|
|
172
|
+
usage = _get(response, "usage", {}) or {}
|
|
173
|
+
prompt_tokens = int(_get(usage, "input_tokens", _get(usage, "prompt_tokens", 0)) or 0)
|
|
174
|
+
completion_tokens = int(_get(usage, "output_tokens", _get(usage, "completion_tokens", 0)) or 0)
|
|
175
|
+
total_tokens = int(_get(usage, "total_tokens", prompt_tokens + completion_tokens) or 0)
|
|
176
|
+
return {
|
|
177
|
+
"text": text,
|
|
178
|
+
"tool_calls": tool_calls,
|
|
179
|
+
"usage": {
|
|
180
|
+
"prompt_tokens": prompt_tokens,
|
|
181
|
+
"completion_tokens": completion_tokens,
|
|
182
|
+
"total_tokens": total_tokens,
|
|
183
|
+
},
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _openai_responses_tool(tool: Tool) -> dict[str, Any]:
|
|
188
|
+
raw = tool.to_openai()
|
|
189
|
+
fn = raw.get("function", {})
|
|
190
|
+
return {
|
|
191
|
+
"type": "function",
|
|
192
|
+
"name": fn.get("name", tool.name),
|
|
193
|
+
"description": fn.get("description", tool.description),
|
|
194
|
+
"parameters": fn.get("parameters", tool.parameters),
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def _extract_response_message_text(item: Any) -> str:
|
|
199
|
+
parts: list[str] = []
|
|
200
|
+
for content in _get(item, "content", []) or []:
|
|
201
|
+
value = _get(content, "text", None)
|
|
202
|
+
if value is None:
|
|
203
|
+
value = _get(content, "output_text", "")
|
|
204
|
+
if value:
|
|
205
|
+
parts.append(str(value))
|
|
206
|
+
return "".join(parts)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def _generate_anthropic(
|
|
210
|
+
*,
|
|
211
|
+
model: str,
|
|
212
|
+
messages: list[dict[str, Any]] | str,
|
|
213
|
+
tools: list[Tool] | None,
|
|
214
|
+
client: Any | None,
|
|
215
|
+
**kwargs: Any,
|
|
216
|
+
) -> dict[str, Any]:
|
|
217
|
+
if client is None:
|
|
218
|
+
from anthropic import Anthropic
|
|
219
|
+
|
|
220
|
+
client = Anthropic()
|
|
221
|
+
if isinstance(messages, str):
|
|
222
|
+
anthropic_messages = [{"role": "user", "content": messages}]
|
|
223
|
+
else:
|
|
224
|
+
anthropic_messages = messages
|
|
225
|
+
payload: dict[str, Any] = {
|
|
226
|
+
"model": model,
|
|
227
|
+
"max_tokens": kwargs.pop("max_tokens", 1024),
|
|
228
|
+
"messages": anthropic_messages,
|
|
229
|
+
**kwargs,
|
|
230
|
+
}
|
|
231
|
+
if tools:
|
|
232
|
+
payload["tools"] = [tool.to_anthropic() for tool in tools]
|
|
233
|
+
response = client.messages.create(**payload)
|
|
234
|
+
content = _get(response, "content", []) or []
|
|
235
|
+
text = "".join(str(_get(block, "text", "")) for block in content if _get(block, "type", "") == "text")
|
|
236
|
+
tool_calls: list[dict[str, Any]] = []
|
|
237
|
+
for block in content:
|
|
238
|
+
if _get(block, "type", "") == "tool_use":
|
|
239
|
+
tool_calls.append(
|
|
240
|
+
{
|
|
241
|
+
"id": _get(block, "id", None),
|
|
242
|
+
"name": _get(block, "name", ""),
|
|
243
|
+
"input": _get(block, "input", {}),
|
|
244
|
+
}
|
|
245
|
+
)
|
|
246
|
+
usage = _get(response, "usage", {}) or {}
|
|
247
|
+
prompt_tokens = int(_get(usage, "input_tokens", 0) or 0)
|
|
248
|
+
completion_tokens = int(_get(usage, "output_tokens", 0) or 0)
|
|
249
|
+
return {
|
|
250
|
+
"text": text,
|
|
251
|
+
"tool_calls": tool_calls,
|
|
252
|
+
"usage": {
|
|
253
|
+
"prompt_tokens": prompt_tokens,
|
|
254
|
+
"completion_tokens": completion_tokens,
|
|
255
|
+
"total_tokens": prompt_tokens + completion_tokens,
|
|
256
|
+
},
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def _get(value: Any, key: str, default: Any = None) -> Any:
|
|
261
|
+
if isinstance(value, dict):
|
|
262
|
+
return value.get(key, default)
|
|
263
|
+
return getattr(value, key, default)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from ..tracing import get_tracer
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def wrap_anthropic(client: Any) -> Any:
|
|
10
|
+
"""Wrap an Anthropic client so messages.create calls are traced automatically."""
|
|
11
|
+
|
|
12
|
+
class MessagesProxy:
|
|
13
|
+
def __init__(self, messages: Any) -> None:
|
|
14
|
+
self._messages = messages
|
|
15
|
+
|
|
16
|
+
def create(self, *args: Any, **kwargs: Any) -> Any:
|
|
17
|
+
model = kwargs.get("model", "unknown")
|
|
18
|
+
prompt = kwargs.get("messages", args[0] if args else "")
|
|
19
|
+
response = self._messages.create(*args, **kwargs)
|
|
20
|
+
text = "".join(
|
|
21
|
+
getattr(block, "text", "")
|
|
22
|
+
for block in getattr(response, "content", [])
|
|
23
|
+
if getattr(block, "type", "") == "text"
|
|
24
|
+
)
|
|
25
|
+
usage = getattr(response, "usage", None)
|
|
26
|
+
prompt_tokens = int(getattr(usage, "input_tokens", 0) or 0) if usage else 0
|
|
27
|
+
completion_tokens = int(getattr(usage, "output_tokens", 0) or 0) if usage else 0
|
|
28
|
+
tracer = get_tracer()
|
|
29
|
+
with tracer.start_span(f"anthropic.messages.{model}", kind="LLM") as span:
|
|
30
|
+
span.set_attribute("llm.provider", "anthropic")
|
|
31
|
+
span.set_attribute("llm.model_name", model)
|
|
32
|
+
span.set_attribute(
|
|
33
|
+
"input.value",
|
|
34
|
+
prompt if isinstance(prompt, str) else json.dumps(prompt, default=str),
|
|
35
|
+
)
|
|
36
|
+
span.set_attribute("llm.token_count.prompt", prompt_tokens)
|
|
37
|
+
span.set_attribute("llm.token_count.completion", completion_tokens)
|
|
38
|
+
span.set_attribute("llm.token_count.total", prompt_tokens + completion_tokens)
|
|
39
|
+
span.set_output(text)
|
|
40
|
+
return response
|
|
41
|
+
|
|
42
|
+
class AnthropicProxy:
|
|
43
|
+
def __init__(self, inner: Any) -> None:
|
|
44
|
+
self._inner = inner
|
|
45
|
+
if hasattr(inner, "messages"):
|
|
46
|
+
self.messages = MessagesProxy(inner.messages)
|
|
47
|
+
|
|
48
|
+
def __getattr__(self, name: str) -> Any:
|
|
49
|
+
return getattr(self._inner, name)
|
|
50
|
+
|
|
51
|
+
return AnthropicProxy(client)
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from ..tracing import get_tracer
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _record_llm_span(
|
|
10
|
+
*,
|
|
11
|
+
name: str,
|
|
12
|
+
provider: str,
|
|
13
|
+
model: str,
|
|
14
|
+
input_value: Any,
|
|
15
|
+
output_value: Any,
|
|
16
|
+
prompt_tokens: int = 0,
|
|
17
|
+
completion_tokens: int = 0,
|
|
18
|
+
) -> None:
|
|
19
|
+
tracer = get_tracer()
|
|
20
|
+
with tracer.start_span(name, kind="LLM") as span:
|
|
21
|
+
span.set_attribute("llm.provider", provider)
|
|
22
|
+
span.set_attribute("llm.model_name", model)
|
|
23
|
+
span.set_attribute("input.value", input_value if isinstance(input_value, str) else json.dumps(input_value, default=str))
|
|
24
|
+
span.set_attribute("llm.token_count.prompt", prompt_tokens)
|
|
25
|
+
span.set_attribute("llm.token_count.completion", completion_tokens)
|
|
26
|
+
span.set_attribute("llm.token_count.total", prompt_tokens + completion_tokens)
|
|
27
|
+
span.set_output(output_value)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def wrap_openai(client: Any) -> Any:
|
|
31
|
+
"""Wrap an OpenAI client so chat/responses calls are traced automatically."""
|
|
32
|
+
|
|
33
|
+
class ChatCompletionsProxy:
|
|
34
|
+
def __init__(self, completions: Any) -> None:
|
|
35
|
+
self._completions = completions
|
|
36
|
+
|
|
37
|
+
def create(self, *args: Any, **kwargs: Any) -> Any:
|
|
38
|
+
model = kwargs.get("model", "unknown")
|
|
39
|
+
messages = kwargs.get("messages", kwargs.get("input", args[0] if args else ""))
|
|
40
|
+
response = self._completions.create(*args, **kwargs)
|
|
41
|
+
text, prompt_tokens, completion_tokens = _extract_openai_chat_response(response)
|
|
42
|
+
_record_llm_span(
|
|
43
|
+
name=f"openai.chat.{model}",
|
|
44
|
+
provider="openai",
|
|
45
|
+
model=model,
|
|
46
|
+
input_value=messages,
|
|
47
|
+
output_value=text,
|
|
48
|
+
prompt_tokens=prompt_tokens,
|
|
49
|
+
completion_tokens=completion_tokens,
|
|
50
|
+
)
|
|
51
|
+
return response
|
|
52
|
+
|
|
53
|
+
class ResponsesProxy:
|
|
54
|
+
def __init__(self, responses: Any) -> None:
|
|
55
|
+
self._responses = responses
|
|
56
|
+
|
|
57
|
+
def create(self, *args: Any, **kwargs: Any) -> Any:
|
|
58
|
+
model = kwargs.get("model", "unknown")
|
|
59
|
+
prompt = kwargs.get("input", args[0] if args else "")
|
|
60
|
+
response = self._responses.create(*args, **kwargs)
|
|
61
|
+
text = getattr(response, "output_text", str(response))
|
|
62
|
+
usage = getattr(response, "usage", None)
|
|
63
|
+
prompt_tokens = int(getattr(usage, "input_tokens", 0) or 0) if usage else 0
|
|
64
|
+
completion_tokens = int(getattr(usage, "output_tokens", 0) or 0) if usage else 0
|
|
65
|
+
_record_llm_span(
|
|
66
|
+
name=f"openai.responses.{model}",
|
|
67
|
+
provider="openai",
|
|
68
|
+
model=model,
|
|
69
|
+
input_value=prompt,
|
|
70
|
+
output_value=text,
|
|
71
|
+
prompt_tokens=prompt_tokens,
|
|
72
|
+
completion_tokens=completion_tokens,
|
|
73
|
+
)
|
|
74
|
+
return response
|
|
75
|
+
|
|
76
|
+
class ChatProxy:
|
|
77
|
+
def __init__(self, chat: Any) -> None:
|
|
78
|
+
self._chat = chat
|
|
79
|
+
self.completions = ChatCompletionsProxy(getattr(chat, "completions", chat))
|
|
80
|
+
|
|
81
|
+
class OpenAIProxy:
|
|
82
|
+
def __init__(self, inner: Any) -> None:
|
|
83
|
+
self._inner = inner
|
|
84
|
+
if hasattr(inner, "chat"):
|
|
85
|
+
self.chat = ChatProxy(inner.chat)
|
|
86
|
+
if hasattr(inner, "responses"):
|
|
87
|
+
self.responses = ResponsesProxy(inner.responses)
|
|
88
|
+
|
|
89
|
+
def __getattr__(self, name: str) -> Any:
|
|
90
|
+
return getattr(self._inner, name)
|
|
91
|
+
|
|
92
|
+
return OpenAIProxy(client)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _extract_openai_chat_response(response: Any) -> tuple[str, int, int]:
|
|
96
|
+
text = ""
|
|
97
|
+
choices = getattr(response, "choices", None) or []
|
|
98
|
+
if choices:
|
|
99
|
+
message = getattr(choices[0], "message", None)
|
|
100
|
+
if message is not None:
|
|
101
|
+
text = getattr(message, "content", "") or ""
|
|
102
|
+
usage = getattr(response, "usage", None)
|
|
103
|
+
prompt_tokens = int(getattr(usage, "prompt_tokens", 0) or 0) if usage else 0
|
|
104
|
+
completion_tokens = int(getattr(usage, "completion_tokens", 0) or 0) if usage else 0
|
|
105
|
+
return text, prompt_tokens, completion_tokens
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Model(str, Enum):
|
|
9
|
+
GPT_5_6 = "openai:gpt-5.6"
|
|
10
|
+
GPT_5_6_SOL = "openai:gpt-5.6-sol"
|
|
11
|
+
GPT_5_6_TERRA = "openai:gpt-5.6-terra"
|
|
12
|
+
GPT_5_6_LUNA = "openai:gpt-5.6-luna"
|
|
13
|
+
GPT_5_5 = "openai:gpt-5.5"
|
|
14
|
+
GPT_5_5_PRO = "openai:gpt-5.5-pro"
|
|
15
|
+
GPT_5_4 = "openai:gpt-5.4"
|
|
16
|
+
GPT_5_4_PRO = "openai:gpt-5.4-pro"
|
|
17
|
+
GPT_5_4_MINI = "openai:gpt-5.4-mini"
|
|
18
|
+
GPT_5_4_NANO = "openai:gpt-5.4-nano"
|
|
19
|
+
GPT_5_3_CODEX = "openai:gpt-5.3-codex"
|
|
20
|
+
GPT_5_2 = "openai:gpt-5.2"
|
|
21
|
+
GPT_5_2_PRO = "openai:gpt-5.2-pro"
|
|
22
|
+
GPT_5_1 = "openai:gpt-5.1"
|
|
23
|
+
GPT_5 = "openai:gpt-5"
|
|
24
|
+
GPT_5_MINI = "openai:gpt-5-mini"
|
|
25
|
+
GPT_5_NANO = "openai:gpt-5-nano"
|
|
26
|
+
GPT_5_PRO = "openai:gpt-5-pro"
|
|
27
|
+
GPT_4_1 = "openai:gpt-4.1"
|
|
28
|
+
GPT_4_1_MINI = "openai:gpt-4.1-mini"
|
|
29
|
+
GPT_4O_MINI = "openai:gpt-4o-mini"
|
|
30
|
+
|
|
31
|
+
CLAUDE_FABLE_5 = "anthropic:claude-fable-5"
|
|
32
|
+
CLAUDE_OPUS_4_8 = "anthropic:claude-opus-4-8"
|
|
33
|
+
CLAUDE_SONNET_5 = "anthropic:claude-sonnet-5"
|
|
34
|
+
CLAUDE_HAIKU_4_5 = "anthropic:claude-haiku-4-5"
|
|
35
|
+
CLAUDE_HAIKU_4_5_20251001 = "anthropic:claude-haiku-4-5-20251001"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass(frozen=True, slots=True)
|
|
39
|
+
class ModelSpec:
|
|
40
|
+
provider: str
|
|
41
|
+
model: str
|
|
42
|
+
interface: str
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
MODEL_SPECS: dict[Model, ModelSpec] = {
|
|
46
|
+
item: ModelSpec(
|
|
47
|
+
provider=item.value.split(":", 1)[0],
|
|
48
|
+
model=item.value.split(":", 1)[1],
|
|
49
|
+
interface="messages" if item.value.startswith("anthropic:") else "responses",
|
|
50
|
+
)
|
|
51
|
+
for item in Model
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
MODEL_ALIASES: dict[str, Model] = {}
|
|
55
|
+
for item in Model:
|
|
56
|
+
MODEL_ALIASES[item.value] = item
|
|
57
|
+
MODEL_ALIASES[item.value.split(":", 1)[1]] = item
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def resolve_model(value: Model | str | Any) -> ModelSpec | None:
|
|
61
|
+
if isinstance(value, Model):
|
|
62
|
+
return MODEL_SPECS[value]
|
|
63
|
+
if not isinstance(value, str):
|
|
64
|
+
return None
|
|
65
|
+
model = MODEL_ALIASES.get(value)
|
|
66
|
+
if model is None:
|
|
67
|
+
supported = ", ".join(item.value for item in Model)
|
|
68
|
+
raise ValueError(
|
|
69
|
+
f"unsupported model {value!r}; use one of Model.* or these ids: {supported}"
|
|
70
|
+
)
|
|
71
|
+
return MODEL_SPECS[model]
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def supported_models() -> list[str]:
|
|
75
|
+
return [item.value for item in Model]
|