agenthub-python 0.4.9__tar.gz → 0.4.10__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.
- {agenthub_python-0.4.9 → agenthub_python-0.4.10}/PKG-INFO +1 -1
- {agenthub_python-0.4.9 → agenthub_python-0.4.10}/agenthub/ant_messages/client.py +9 -2
- {agenthub_python-0.4.9 → agenthub_python-0.4.10}/agenthub/auto_client.py +19 -7
- {agenthub_python-0.4.9 → agenthub_python-0.4.10}/agenthub/claude5/client.py +8 -2
- {agenthub_python-0.4.9 → agenthub_python-0.4.10}/agenthub/deepseek_v4/client.py +8 -1
- {agenthub_python-0.4.9/agenthub/gemini3_7 → agenthub_python-0.4.10/agenthub/gemini3_8}/__init__.py +2 -2
- {agenthub_python-0.4.9/agenthub/gemini3_7 → agenthub_python-0.4.10/agenthub/gemini3_8}/client.py +18 -20
- {agenthub_python-0.4.9 → agenthub_python-0.4.10}/agenthub/glm5_3/client.py +14 -14
- {agenthub_python-0.4.9 → agenthub_python-0.4.10}/agenthub/gpt5_6/client.py +6 -2
- agenthub_python-0.4.10/agenthub/openai_chat_vllm_adapter/__init__.py +4 -0
- agenthub_python-0.4.10/agenthub/openai_chat_vllm_adapter/client.py +122 -0
- {agenthub_python-0.4.9 → agenthub_python-0.4.10}/agenthub/openai_responses/client.py +5 -0
- {agenthub_python-0.4.9 → agenthub_python-0.4.10}/agenthub/registry.py +22 -8
- {agenthub_python-0.4.9 → agenthub_python-0.4.10}/pyproject.toml +1 -1
- {agenthub_python-0.4.9 → agenthub_python-0.4.10}/pyproject.toml.orig +1 -1
- {agenthub_python-0.4.9 → agenthub_python-0.4.10}/README.md +0 -0
- {agenthub_python-0.4.9 → agenthub_python-0.4.10}/agenthub/__init__.py +0 -0
- {agenthub_python-0.4.9 → agenthub_python-0.4.10}/agenthub/abort_signal.py +0 -0
- {agenthub_python-0.4.9 → agenthub_python-0.4.10}/agenthub/ant_messages/__init__.py +0 -0
- {agenthub_python-0.4.9 → agenthub_python-0.4.10}/agenthub/base_client.py +0 -0
- {agenthub_python-0.4.9 → agenthub_python-0.4.10}/agenthub/claude5/__init__.py +0 -0
- {agenthub_python-0.4.9 → agenthub_python-0.4.10}/agenthub/deepseek_v4/__init__.py +0 -0
- {agenthub_python-0.4.9 → agenthub_python-0.4.10}/agenthub/errors.py +0 -0
- {agenthub_python-0.4.9 → agenthub_python-0.4.10}/agenthub/glm5_3/__init__.py +0 -0
- {agenthub_python-0.4.9 → agenthub_python-0.4.10}/agenthub/gpt5_6/__init__.py +0 -0
- {agenthub_python-0.4.9 → agenthub_python-0.4.10}/agenthub/integration/__init__.py +0 -0
- {agenthub_python-0.4.9 → agenthub_python-0.4.10}/agenthub/integration/playground.py +0 -0
- {agenthub_python-0.4.9 → agenthub_python-0.4.10}/agenthub/integration/tracer.py +0 -0
- {agenthub_python-0.4.9 → agenthub_python-0.4.10}/agenthub/kimi_k3/__init__.py +0 -0
- {agenthub_python-0.4.9 → agenthub_python-0.4.10}/agenthub/kimi_k3/client.py +0 -0
- {agenthub_python-0.4.9 → agenthub_python-0.4.10}/agenthub/minimax_m3/__init__.py +0 -0
- {agenthub_python-0.4.9 → agenthub_python-0.4.10}/agenthub/minimax_m3/client.py +0 -0
- {agenthub_python-0.4.9 → agenthub_python-0.4.10}/agenthub/openai_chat/__init__.py +0 -0
- {agenthub_python-0.4.9 → agenthub_python-0.4.10}/agenthub/openai_chat/client.py +0 -0
- {agenthub_python-0.4.9 → agenthub_python-0.4.10}/agenthub/openai_embedding/__init__.py +0 -0
- {agenthub_python-0.4.9 → agenthub_python-0.4.10}/agenthub/openai_embedding/client.py +0 -0
- {agenthub_python-0.4.9 → agenthub_python-0.4.10}/agenthub/openai_responses/__init__.py +0 -0
- {agenthub_python-0.4.9 → agenthub_python-0.4.10}/agenthub/types.py +0 -0
- {agenthub_python-0.4.9 → agenthub_python-0.4.10}/agenthub/utils.py +0 -0
|
@@ -128,8 +128,15 @@ class AntMessagesClient(LLMClient):
|
|
|
128
128
|
|
|
129
129
|
if config.get("thinking_level") is not None:
|
|
130
130
|
ant_config.update(self._convert_thinking_level_to_thinking_config(config["thinking_level"]))
|
|
131
|
-
|
|
132
|
-
|
|
131
|
+
|
|
132
|
+
if config.get("thinking_summary") is not None:
|
|
133
|
+
# display lives on the thinking block, so a summary asked for on its own selects
|
|
134
|
+
# adaptive thinking. A disabled block is the one place it cannot ride along --
|
|
135
|
+
# "thinking.disabled.display: Extra inputs are not permitted" (400, verified live
|
|
136
|
+
# 2026-09-03) -- and thinking_level NONE disables thinking, leaving nothing to show.
|
|
137
|
+
thinking = ant_config.setdefault("thinking", {"type": "adaptive"})
|
|
138
|
+
if thinking["type"] != "disabled":
|
|
139
|
+
thinking["display"] = "summarized" if config["thinking_summary"] else "omitted"
|
|
133
140
|
|
|
134
141
|
# Convert tools to the Messages API tool schema
|
|
135
142
|
if config.get("tools") is not None:
|
|
@@ -21,7 +21,13 @@ from .types import UniConfig, UniEvent, UniMessage
|
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
# The generic protocol clients are named explicitly rather than deduced from a model id.
|
|
24
|
-
_PROTOCOL_CLIENT_TYPES = (
|
|
24
|
+
_PROTOCOL_CLIENT_TYPES = (
|
|
25
|
+
"openai-chat",
|
|
26
|
+
"openai-chat-vllm-adapter",
|
|
27
|
+
"openai-responses",
|
|
28
|
+
"ant-messages",
|
|
29
|
+
"openai-embedding",
|
|
30
|
+
)
|
|
25
31
|
|
|
26
32
|
|
|
27
33
|
class AutoLLMClient(LLMClient):
|
|
@@ -65,13 +71,13 @@ class AutoLLMClient(LLMClient):
|
|
|
65
71
|
type[LLMClient] | None: The client class, or None when no client claims the type.
|
|
66
72
|
"""
|
|
67
73
|
# every Gemini generation shares the unified client ("gemini-3" also matches the
|
|
68
|
-
# gemini-3.7/gemini-3.6/gemini-3.5-flash-lite client types)
|
|
74
|
+
# gemini-3.8/gemini-3.7/gemini-3.6/gemini-3.5-flash-lite client types)
|
|
69
75
|
if any(
|
|
70
76
|
prefix in client_type for prefix in ("gemini-3", "gemini-embedding")
|
|
71
|
-
): # e.g., gemini-3.
|
|
72
|
-
from .
|
|
77
|
+
): # e.g., gemini-3.8-flash, gemini-3-flash-preview, gemini-embedding-2
|
|
78
|
+
from .gemini3_8 import Gemini3_8Client
|
|
73
79
|
|
|
74
|
-
return
|
|
80
|
+
return Gemini3_8Client
|
|
75
81
|
elif "claude" in client_type and (
|
|
76
82
|
"4-6" in client_type or "4-7" in client_type or "4-8" in client_type or "-5" in client_type
|
|
77
83
|
): # the whole Claude 4.6+ series shares the unified client, e.g., claude-sonnet-4-6
|
|
@@ -99,6 +105,12 @@ class AutoLLMClient(LLMClient):
|
|
|
99
105
|
from .deepseek_v4 import DeepSeekV4Client
|
|
100
106
|
|
|
101
107
|
return DeepSeekV4Client
|
|
108
|
+
elif client_type == "openai-chat-vllm-adapter":
|
|
109
|
+
# exact match: "openai-chat-vllm-adapter" contains "openai", so the substring
|
|
110
|
+
# branches below would otherwise claim it
|
|
111
|
+
from .openai_chat_vllm_adapter import OpenaiChatVllmAdapterClient
|
|
112
|
+
|
|
113
|
+
return OpenaiChatVllmAdapterClient
|
|
102
114
|
elif "ant-messages" in client_type:
|
|
103
115
|
from .ant_messages import AntMessagesClient
|
|
104
116
|
|
|
@@ -131,10 +143,10 @@ class AutoLLMClient(LLMClient):
|
|
|
131
143
|
if client_class is None:
|
|
132
144
|
raise ValueError(
|
|
133
145
|
f"{client_type} is not supported. "
|
|
134
|
-
"Supported client types: minimax-m3, gemini-3.7, gemini-3.6, gemini-3, "
|
|
146
|
+
"Supported client types: minimax-m3, gemini-3.8, gemini-3.7, gemini-3.6, gemini-3, "
|
|
135
147
|
"claude-5, claude-4-8, claude-4-7, claude-4-6, gpt-5.6, gpt-5.5, gpt-5.4, "
|
|
136
148
|
"glm-5.3, glm-5.2, glm-5.1, kimi-k3, kimi-k2.6, kimi-k2.5, deepseek-v4, "
|
|
137
|
-
"openai-embedding, ant-messages, openai-responses, openai-chat."
|
|
149
|
+
"openai-chat-vllm-adapter, openai-embedding, ant-messages, openai-responses, openai-chat."
|
|
138
150
|
)
|
|
139
151
|
|
|
140
152
|
return client_class(model=model, api_key=api_key, base_url=base_url, default_headers=default_headers)
|
|
@@ -176,8 +176,14 @@ class Claude5Client(LLMClient):
|
|
|
176
176
|
|
|
177
177
|
if config.get("thinking_level") is not None:
|
|
178
178
|
claude_config.update(self._convert_thinking_level_to_thinking_config(config["thinking_level"]))
|
|
179
|
-
|
|
180
|
-
|
|
179
|
+
|
|
180
|
+
if config.get("thinking_summary") is not None:
|
|
181
|
+
# display lives on the thinking block, so a summary asked for on its own selects
|
|
182
|
+
# adaptive thinking, which is what this family runs by default anyway; a block
|
|
183
|
+
# carrying display but no output_config is accepted on 4.6 through 5 (verified
|
|
184
|
+
# live 2026-09-03). NONE omits the block, so the request lands on that default.
|
|
185
|
+
thinking = claude_config.setdefault("thinking", {"type": "adaptive"})
|
|
186
|
+
thinking["display"] = "summarized" if config["thinking_summary"] else "omitted"
|
|
181
187
|
|
|
182
188
|
# Convert tools to Claude's tool schema
|
|
183
189
|
if config.get("tools") is not None:
|
|
@@ -103,10 +103,17 @@ class DeepSeekV4Client(LLMClient):
|
|
|
103
103
|
self.__class__.__name__, "temperature", "DeepSeek V4 does not support setting temperature."
|
|
104
104
|
)
|
|
105
105
|
|
|
106
|
-
# a thinking summary is accepted but never generated, so the parameter is left out
|
|
107
106
|
if config.get("thinking_level") is not None:
|
|
108
107
|
deepseek_config["reasoning"] = {"effort": self._convert_thinking_level_to_effort(config["thinking_level"])}
|
|
109
108
|
|
|
109
|
+
if config.get("thinking_summary"):
|
|
110
|
+
# DeepSeek takes reasoning.summary with or without an effort and returns an empty
|
|
111
|
+
# summary list for now (verified live 2026-09-03 on api.deepseek.com and
|
|
112
|
+
# OpenRouter), so the request carries the preference instead of dropping it and
|
|
113
|
+
# picks up summaries as soon as the vendor generates them. False needs no key:
|
|
114
|
+
# the Responses API returns no summary unless one is asked for.
|
|
115
|
+
deepseek_config.setdefault("reasoning", {})["summary"] = "concise"
|
|
116
|
+
|
|
110
117
|
if config.get("tools") is not None:
|
|
111
118
|
deepseek_config["tools"] = [{"type": "function", **tool} for tool in config["tools"]]
|
|
112
119
|
|
{agenthub_python-0.4.9/agenthub/gemini3_7 → agenthub_python-0.4.10/agenthub/gemini3_8}/__init__.py
RENAMED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
|
-
from .client import
|
|
15
|
+
from .client import Gemini3_8Client
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
__all__ = ["
|
|
18
|
+
__all__ = ["Gemini3_8Client"]
|
{agenthub_python-0.4.9/agenthub/gemini3_7 → agenthub_python-0.4.10/agenthub/gemini3_8}/client.py
RENAMED
|
@@ -61,14 +61,14 @@ def _split_function_response_runs(parts: list[types.Part]) -> list[list[types.Pa
|
|
|
61
61
|
return runs if runs else [parts]
|
|
62
62
|
|
|
63
63
|
|
|
64
|
-
class
|
|
65
|
-
"""Unified client for the Gemini family, named for the newest generation it serves (3.
|
|
66
|
-
|
|
67
|
-
It serves every generateContent model generation (3.
|
|
68
|
-
and embedding models
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
64
|
+
class Gemini3_8Client(LLMClient):
|
|
65
|
+
"""Unified client for the Gemini family, named for the newest generation it serves (3.8).
|
|
66
|
+
|
|
67
|
+
It serves every generateContent model generation (3.8 back through 3.x text, image, TTS,
|
|
68
|
+
and embedding models). The API deprecated the temperature/top_p/top_k sampling parameters
|
|
69
|
+
starting with the 3.6 generation (silently ignored today, HTTP 400 in future generations),
|
|
70
|
+
and this client applies that contract to the whole family: temperature is rejected
|
|
71
|
+
everywhere.
|
|
72
72
|
"""
|
|
73
73
|
|
|
74
74
|
def __init__(
|
|
@@ -78,7 +78,7 @@ class Gemini3_7Client(LLMClient):
|
|
|
78
78
|
base_url: str | None = None,
|
|
79
79
|
default_headers: dict[str, str] | None = None,
|
|
80
80
|
):
|
|
81
|
-
"""Initialize Gemini 3.
|
|
81
|
+
"""Initialize Gemini 3.8 client with model and API key."""
|
|
82
82
|
self._model = model
|
|
83
83
|
api_key = api_key or os.getenv("GEMINI_API_KEY")
|
|
84
84
|
base_url = base_url or os.getenv("GEMINI_BASE_URL")
|
|
@@ -139,15 +139,11 @@ class Gemini3_7Client(LLMClient):
|
|
|
139
139
|
)
|
|
140
140
|
|
|
141
141
|
def _supported_thinking_levels(self) -> tuple[types.ThinkingLevel, ...]:
|
|
142
|
-
"""Thinking levels the target model accepts (llmsdk_docs/
|
|
142
|
+
"""Thinking levels the target model accepts (llmsdk_docs/gemini3_8/docs/thinking.md).
|
|
143
143
|
|
|
144
144
|
An empty tuple means the model rejects the thinking_level parameter
|
|
145
145
|
entirely, so it must be omitted from the request.
|
|
146
146
|
"""
|
|
147
|
-
if "gemini-2.5" in self._model:
|
|
148
|
-
# The vendor table claims low/medium/high, but the live API rejects
|
|
149
|
-
# every thinking_level value for the 2.5 series (verified 2026-07-24).
|
|
150
|
-
return ()
|
|
151
147
|
if "-image" in self._model:
|
|
152
148
|
return (types.ThinkingLevel.MINIMAL, types.ThinkingLevel.HIGH)
|
|
153
149
|
if "gemini-3-pro" in self._model:
|
|
@@ -159,8 +155,9 @@ class Gemini3_7Client(LLMClient):
|
|
|
159
155
|
# would have accepted costs a little accuracy, forwarding an
|
|
160
156
|
# unsupported one is a 400).
|
|
161
157
|
return (types.ThinkingLevel.LOW, types.ThinkingLevel.MEDIUM, types.ThinkingLevel.HIGH)
|
|
162
|
-
if "gemini-3.7" in self._model:
|
|
163
|
-
#
|
|
158
|
+
if "gemini-3.7" in self._model or "gemini-3.8" in self._model:
|
|
159
|
+
# Both generations reject "minimal" with a 400 (3.7 verified live 2026-08-13;
|
|
160
|
+
# 3.8 documented at ai.google.dev/gemini-api/docs/latest-model).
|
|
164
161
|
return (types.ThinkingLevel.LOW, types.ThinkingLevel.MEDIUM, types.ThinkingLevel.HIGH)
|
|
165
162
|
return self._GEMINI_LEVEL_ORDER
|
|
166
163
|
|
|
@@ -180,8 +177,9 @@ class Gemini3_7Client(LLMClient):
|
|
|
180
177
|
return None
|
|
181
178
|
supported = self._supported_thinking_levels()
|
|
182
179
|
if not supported:
|
|
183
|
-
#
|
|
184
|
-
#
|
|
180
|
+
# A model that takes no thinking_level at all has nothing to clamp onto, so the
|
|
181
|
+
# parameter is omitted rather than turned into a failed request. thinking_summary
|
|
182
|
+
# is unaffected -- include_thoughts still rides along.
|
|
185
183
|
return None
|
|
186
184
|
if level in supported:
|
|
187
185
|
return level
|
|
@@ -281,7 +279,7 @@ class Gemini3_7Client(LLMClient):
|
|
|
281
279
|
config_params["system_instruction"] = config["system_prompt"]
|
|
282
280
|
|
|
283
281
|
# include_thoughts asks for thought summaries, but whether generateContent returns any
|
|
284
|
-
# is model-dependent (llmsdk_docs/
|
|
282
|
+
# is model-dependent (llmsdk_docs/gemini3_8/docs/thinking.md)
|
|
285
283
|
thinking_summary = config.get("thinking_summary")
|
|
286
284
|
thinking_level = config.get("thinking_level")
|
|
287
285
|
if thinking_summary is not None or thinking_level is not None:
|
|
@@ -570,7 +568,7 @@ class Gemini3_7Client(LLMClient):
|
|
|
570
568
|
|
|
571
569
|
for item in event["content_items"]:
|
|
572
570
|
if item["type"] == "tool_call":
|
|
573
|
-
#
|
|
571
|
+
# the Gemini API does not stream partial tool calls, mock a partial tool call event
|
|
574
572
|
yield {
|
|
575
573
|
"role": "assistant",
|
|
576
574
|
"event_type": "delta",
|
|
@@ -75,9 +75,11 @@ class GLM5_3Client(LLMClient):
|
|
|
75
75
|
|
|
76
76
|
GLM-5.3 accepts only low/high/max and errors on anything else, so the client
|
|
77
77
|
clamps to the closest value; NONE rides on low because 5.3 cannot disable
|
|
78
|
-
thinking.
|
|
79
|
-
(low/medium to high, xhigh to max)
|
|
80
|
-
|
|
78
|
+
thinking. Every earlier generation takes the vocabulary unchanged: 5.2 maps it
|
|
79
|
+
server-side (low/medium to high, xhigh to max), and 5.1 and below accept the
|
|
80
|
+
parameter and ignore it (verified live 2026-09-03 on Z.AI, OpenRouter and
|
|
81
|
+
SiliconFlow), so the level is forwarded there rather than dropped. Outside 5.3
|
|
82
|
+
NONE disables thinking outright, which leaves no effort to send.
|
|
81
83
|
"""
|
|
82
84
|
model = self._model.lower() # provider-hosted ids keep their own casing
|
|
83
85
|
if "glm-5.3" in model:
|
|
@@ -90,17 +92,15 @@ class GLM5_3Client(LLMClient):
|
|
|
90
92
|
ThinkingLevel.MAX: "max",
|
|
91
93
|
}
|
|
92
94
|
return mapping.get(thinking_level)
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
return mapping.get(thinking_level)
|
|
103
|
-
return None
|
|
95
|
+
mapping = {
|
|
96
|
+
ThinkingLevel.NONE: None,
|
|
97
|
+
ThinkingLevel.LOW: "low",
|
|
98
|
+
ThinkingLevel.MEDIUM: "medium",
|
|
99
|
+
ThinkingLevel.HIGH: "high",
|
|
100
|
+
ThinkingLevel.XHIGH: "xhigh",
|
|
101
|
+
ThinkingLevel.MAX: "max",
|
|
102
|
+
}
|
|
103
|
+
return mapping.get(thinking_level)
|
|
104
104
|
|
|
105
105
|
def _convert_tool_choice(self, tool_choice: ToolChoice) -> str:
|
|
106
106
|
"""Convert ToolChoice to OpenAI's tool_choice format."""
|
|
@@ -106,8 +106,12 @@ class GPT5_6Client(LLMClient):
|
|
|
106
106
|
|
|
107
107
|
if config.get("thinking_level") is not None:
|
|
108
108
|
openai_config["reasoning"] = {"effort": self._convert_thinking_level_to_effort(config["thinking_level"])}
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
|
|
110
|
+
if config.get("thinking_summary"):
|
|
111
|
+
# reasoning.summary stands on its own, with or without an effort (verified live
|
|
112
|
+
# 2026-09-03 on the OpenAI and OpenRouter endpoints). False needs no key: the
|
|
113
|
+
# Responses API returns no summary unless one is asked for.
|
|
114
|
+
openai_config.setdefault("reasoning", {})["summary"] = "concise"
|
|
111
115
|
|
|
112
116
|
if config.get("tools") is not None:
|
|
113
117
|
openai_config["tools"] = [{"type": "function", **tool} for tool in config["tools"]]
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Copyright 2025 Prism Shadow. and/or its affiliates
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
from ..openai_chat import OpenaiChatClient
|
|
18
|
+
from ..types import ThinkingLevel, UniConfig
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# vLLM passes chat_template_kwargs straight to the served model's chat template, so the
|
|
22
|
+
# switch that turns thinking on is whatever that template happens to read. Each profile
|
|
23
|
+
# below maps an AgentHub level onto one family's kwargs; an empty mapping means the
|
|
24
|
+
# request carries no chat_template_kwargs at all.
|
|
25
|
+
#
|
|
26
|
+
# The upstream artifacts these profiles are read off, and the clamping those artifacts
|
|
27
|
+
# force, are snapshotted in llmsdk_docs/openai_chat_vllm_adapter/. Update that snapshot
|
|
28
|
+
# whenever a model is added here.
|
|
29
|
+
|
|
30
|
+
# Qwen3 templates read a single enable_thinking boolean and no effort key at all.
|
|
31
|
+
_QWEN3_THINKING: dict[ThinkingLevel, dict[str, Any]] = {
|
|
32
|
+
ThinkingLevel.NONE: {"enable_thinking": False},
|
|
33
|
+
ThinkingLevel.LOW: {"enable_thinking": True},
|
|
34
|
+
ThinkingLevel.MEDIUM: {"enable_thinking": True},
|
|
35
|
+
ThinkingLevel.HIGH: {"enable_thinking": True},
|
|
36
|
+
ThinkingLevel.XHIGH: {"enable_thinking": True},
|
|
37
|
+
ThinkingLevel.MAX: {"enable_thinking": True},
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
# Qwen3.8-27B and Qwen3.8-Flash-Next ship the same chat template, byte for byte, so they
|
|
41
|
+
# share a profile. It keeps enable_thinking as the off switch and takes its adaptive modes
|
|
42
|
+
# as reasoning_effort, validated against low/medium/xhigh, so high and max clamp to xhigh.
|
|
43
|
+
# The template defaults the key to xhigh, so a model on this template that is not sent the
|
|
44
|
+
# key runs every level at full effort.
|
|
45
|
+
_QWEN3_8_THINKING: dict[ThinkingLevel, dict[str, Any]] = {
|
|
46
|
+
ThinkingLevel.NONE: {"enable_thinking": False},
|
|
47
|
+
ThinkingLevel.LOW: {"reasoning_effort": "low"},
|
|
48
|
+
ThinkingLevel.MEDIUM: {"reasoning_effort": "medium"},
|
|
49
|
+
ThinkingLevel.HIGH: {"reasoning_effort": "xhigh"},
|
|
50
|
+
ThinkingLevel.XHIGH: {"reasoning_effort": "xhigh"},
|
|
51
|
+
ThinkingLevel.MAX: {"reasoning_effort": "xhigh"},
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
# DeepSeek V4 publishes no chat template; vLLM reads a thinking flag paired with
|
|
55
|
+
# reasoning_effort, and thinking is off whenever the flag is absent, which is what NONE
|
|
56
|
+
# sends. DeepSeek-V4-Pro and DeepSeek-V4-Flash share an encoding module that asserts
|
|
57
|
+
# reasoning_effort in ['max', None, 'high'], so low is a failed request rather than a
|
|
58
|
+
# weaker answer and high is the lowest value they take. That module then branches on 'max'
|
|
59
|
+
# alone, which means LOW through XHIGH all render the same prompt on these two models.
|
|
60
|
+
_DEEPSEEK_V4_PRO_FLASH_THINKING: dict[ThinkingLevel, dict[str, Any]] = {
|
|
61
|
+
ThinkingLevel.NONE: {},
|
|
62
|
+
ThinkingLevel.LOW: {"thinking": True, "reasoning_effort": "high"},
|
|
63
|
+
ThinkingLevel.MEDIUM: {"thinking": True, "reasoning_effort": "high"},
|
|
64
|
+
ThinkingLevel.HIGH: {"thinking": True, "reasoning_effort": "high"},
|
|
65
|
+
ThinkingLevel.XHIGH: {"thinking": True, "reasoning_effort": "high"},
|
|
66
|
+
ThinkingLevel.MAX: {"thinking": True, "reasoning_effort": "max"},
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
# DeepSeek-V4-Flash-Vision-Exp ships a different copy of that encoding module, one that
|
|
70
|
+
# validates reasoning_effort against a low/high/max table, so it keeps the finer scale;
|
|
71
|
+
# medium and xhigh clamp to high.
|
|
72
|
+
_DEEPSEEK_V4_VISION_EXP_THINKING: dict[ThinkingLevel, dict[str, Any]] = {
|
|
73
|
+
ThinkingLevel.NONE: {},
|
|
74
|
+
ThinkingLevel.LOW: {"thinking": True, "reasoning_effort": "low"},
|
|
75
|
+
ThinkingLevel.MEDIUM: {"thinking": True, "reasoning_effort": "high"},
|
|
76
|
+
ThinkingLevel.HIGH: {"thinking": True, "reasoning_effort": "high"},
|
|
77
|
+
ThinkingLevel.XHIGH: {"thinking": True, "reasoning_effort": "high"},
|
|
78
|
+
ThinkingLevel.MAX: {"thinking": True, "reasoning_effort": "max"},
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
# Keys are matched as substrings of the lowercased model id, so a served id keeps whatever
|
|
82
|
+
# prefix the deployment gave it (Qwen/Qwen3.6-35B-A3B, deepseek-ai/DeepSeek-V4-Pro). The
|
|
83
|
+
# first match wins, so a key that contains another must come first: deepseek-v4-flash is a
|
|
84
|
+
# prefix of deepseek-v4-flash-vision-exp.
|
|
85
|
+
_MODEL_THINKING_PROFILES: tuple[tuple[str, dict[ThinkingLevel, dict[str, Any]]], ...] = (
|
|
86
|
+
("qwen3.8-flash-next", _QWEN3_8_THINKING),
|
|
87
|
+
("qwen3.8-27b", _QWEN3_8_THINKING),
|
|
88
|
+
("qwen3.6-35b-a3b", _QWEN3_THINKING),
|
|
89
|
+
("qwen3.5-0.8b", _QWEN3_THINKING),
|
|
90
|
+
("qwen3.5-9b", _QWEN3_THINKING),
|
|
91
|
+
("deepseek-v4-flash-vision-exp", _DEEPSEEK_V4_VISION_EXP_THINKING),
|
|
92
|
+
("deepseek-v4-pro", _DEEPSEEK_V4_PRO_FLASH_THINKING),
|
|
93
|
+
("deepseek-v4-flash", _DEEPSEEK_V4_PRO_FLASH_THINKING),
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class OpenaiChatVllmAdapterClient(OpenaiChatClient):
|
|
98
|
+
"""Models served through vLLM's OpenAI-compatible Chat Completions API."""
|
|
99
|
+
|
|
100
|
+
def _thinking_chat_template_kwargs(self, thinking_level: ThinkingLevel) -> dict[str, Any]:
|
|
101
|
+
"""Return the chat_template_kwargs this model's template reads for the level.
|
|
102
|
+
|
|
103
|
+
A model outside the table falls back to Qwen3's enable_thinking, the most
|
|
104
|
+
widespread of the conventions and inert on a template that ignores the key.
|
|
105
|
+
"""
|
|
106
|
+
model = self._model.lower()
|
|
107
|
+
for name, profile in _MODEL_THINKING_PROFILES:
|
|
108
|
+
if name in model:
|
|
109
|
+
return dict(profile[thinking_level])
|
|
110
|
+
|
|
111
|
+
return dict(_QWEN3_THINKING[thinking_level])
|
|
112
|
+
|
|
113
|
+
def transform_uni_config_to_model_config(self, config: UniConfig) -> dict[str, Any]:
|
|
114
|
+
"""Map AgentHub's level onto the thinking switch this model's chat template reads."""
|
|
115
|
+
vllm_config = super().transform_uni_config_to_model_config(config)
|
|
116
|
+
|
|
117
|
+
if config.get("thinking_level") is not None:
|
|
118
|
+
chat_template_kwargs = self._thinking_chat_template_kwargs(config["thinking_level"])
|
|
119
|
+
if chat_template_kwargs:
|
|
120
|
+
vllm_config["chat_template_kwargs"] = chat_template_kwargs
|
|
121
|
+
|
|
122
|
+
return vllm_config
|
|
@@ -100,6 +100,11 @@ class OpenaiResponsesClient(LLMClient):
|
|
|
100
100
|
if config.get("temperature") is not None:
|
|
101
101
|
openai_config["temperature"] = config["temperature"]
|
|
102
102
|
|
|
103
|
+
# Unlike the model-specific Responses clients, the summary stays inside this branch:
|
|
104
|
+
# OpenRouter reads a reasoning object carrying no effort as "reasoning disabled" and
|
|
105
|
+
# refuses it on a forced-thinking model -- "Reasoning is mandatory for this endpoint
|
|
106
|
+
# and cannot be disabled" (400, verified live 2026-09-03 with z-ai/glm-5.3) -- so a
|
|
107
|
+
# summary sent on its own would turn a dropped value into a failed request.
|
|
103
108
|
if config.get("thinking_level") is not None:
|
|
104
109
|
openai_config["reasoning"] = {"effort": self._convert_thinking_level_to_effort(config["thinking_level"])}
|
|
105
110
|
if config.get("thinking_summary"):
|
|
@@ -42,6 +42,9 @@ class SupportedModel(TypedDict):
|
|
|
42
42
|
``AutoLLMClient(model=entry["model"], base_url=entry["base_url"], client_type=entry["client"])``.
|
|
43
43
|
Modalities describe what is usable through that client; ``context_window`` and
|
|
44
44
|
``pricing`` are omitted where the platform publishes no authoritative value.
|
|
45
|
+
|
|
46
|
+
``pricing`` is always the LIST price. A running promotion is deliberately not recorded:
|
|
47
|
+
the registry's job is the catalog price, and applying a promotion is the consumer's.
|
|
45
48
|
"""
|
|
46
49
|
|
|
47
50
|
model: str
|
|
@@ -95,20 +98,31 @@ def _cny(prompt: float, output: float, cached: float | None = None) -> ModelPric
|
|
|
95
98
|
# /models APIs on 2026-07-22, SiliconFlow CNY prices from the vendors' official price lists.
|
|
96
99
|
_SUPPORTED_MODELS: list[SupportedModel] = [
|
|
97
100
|
# official vendor endpoints
|
|
101
|
+
{
|
|
102
|
+
"model": "gemini-3.8-flash",
|
|
103
|
+
"base_url": _GOOGLE,
|
|
104
|
+
"client": "gemini-3.8",
|
|
105
|
+
"input_modalities": ["Text", "Image", "Video", "Audio"],
|
|
106
|
+
"output_modalities": ["Text"],
|
|
107
|
+
"context_window": 1048576,
|
|
108
|
+
# Google runs a launch discount through 2026-12-31 on this row and on the two flash
|
|
109
|
+
# rows below; the list price is stored regardless, because applying a running
|
|
110
|
+
# promotion belongs to the consumer, not to the registry.
|
|
111
|
+
"pricing": _usd(1.5, 7.5, cached=0.15),
|
|
112
|
+
},
|
|
98
113
|
{
|
|
99
114
|
"model": "gemini-3.7-flash",
|
|
100
115
|
"base_url": _GOOGLE,
|
|
101
|
-
"client": "gemini-3.
|
|
116
|
+
"client": "gemini-3.8",
|
|
102
117
|
"input_modalities": ["Text", "Image", "Video", "Audio"],
|
|
103
118
|
"output_modalities": ["Text"],
|
|
104
119
|
"context_window": 1048576,
|
|
105
|
-
# official list price; a launch discount halves all three rates through 2026-12-31
|
|
106
120
|
"pricing": _usd(1.5, 7.5, cached=0.15),
|
|
107
121
|
},
|
|
108
122
|
{
|
|
109
123
|
"model": "gemini-3.6-flash",
|
|
110
124
|
"base_url": _GOOGLE,
|
|
111
|
-
"client": "gemini-3.
|
|
125
|
+
"client": "gemini-3.8",
|
|
112
126
|
"input_modalities": ["Text", "Image", "Video", "Audio"],
|
|
113
127
|
"output_modalities": ["Text"],
|
|
114
128
|
"context_window": 1048576,
|
|
@@ -117,7 +131,7 @@ _SUPPORTED_MODELS: list[SupportedModel] = [
|
|
|
117
131
|
{
|
|
118
132
|
"model": "gemini-3.5-flash-lite",
|
|
119
133
|
"base_url": _GOOGLE,
|
|
120
|
-
"client": "gemini-3.
|
|
134
|
+
"client": "gemini-3.8",
|
|
121
135
|
"input_modalities": ["Text", "Image", "Video", "Audio"],
|
|
122
136
|
"output_modalities": ["Text"],
|
|
123
137
|
"context_window": 1048576,
|
|
@@ -126,7 +140,7 @@ _SUPPORTED_MODELS: list[SupportedModel] = [
|
|
|
126
140
|
{
|
|
127
141
|
"model": "gemini-3.5-flash",
|
|
128
142
|
"base_url": _GOOGLE,
|
|
129
|
-
"client": "gemini-3.
|
|
143
|
+
"client": "gemini-3.8",
|
|
130
144
|
"input_modalities": ["Text", "Image", "Video", "Audio"],
|
|
131
145
|
"output_modalities": ["Text"],
|
|
132
146
|
"context_window": 1048576,
|
|
@@ -135,21 +149,21 @@ _SUPPORTED_MODELS: list[SupportedModel] = [
|
|
|
135
149
|
{
|
|
136
150
|
"model": "gemini-3.1-flash-image",
|
|
137
151
|
"base_url": _GOOGLE,
|
|
138
|
-
"client": "gemini-3.
|
|
152
|
+
"client": "gemini-3.8",
|
|
139
153
|
"input_modalities": ["Text", "Image"],
|
|
140
154
|
"output_modalities": ["Image"],
|
|
141
155
|
},
|
|
142
156
|
{
|
|
143
157
|
"model": "gemini-3.1-flash-tts-preview",
|
|
144
158
|
"base_url": _GOOGLE,
|
|
145
|
-
"client": "gemini-3.
|
|
159
|
+
"client": "gemini-3.8",
|
|
146
160
|
"input_modalities": ["Text"],
|
|
147
161
|
"output_modalities": ["Audio"],
|
|
148
162
|
},
|
|
149
163
|
{
|
|
150
164
|
"model": "gemini-embedding-2",
|
|
151
165
|
"base_url": _GOOGLE,
|
|
152
|
-
"client": "gemini-3.
|
|
166
|
+
"client": "gemini-3.8",
|
|
153
167
|
"input_modalities": ["Text"],
|
|
154
168
|
"output_modalities": ["Embed"],
|
|
155
169
|
},
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|