agenthub-python 0.2.0__py3-none-any.whl → 0.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.
- agenthub/auto_client.py +26 -11
- agenthub/base_client.py +94 -15
- agenthub/{claude4_5 → claude4_6}/__init__.py +2 -2
- agenthub/claude4_6/client.py +426 -0
- agenthub/gemini3/client.py +170 -45
- agenthub/{gpt5_2 → glm5}/__init__.py +2 -2
- agenthub/{glm4_7 → glm5}/client.py +114 -62
- agenthub/{glm4_7 → gpt5_5}/__init__.py +2 -2
- agenthub/{gpt5_2 → gpt5_5}/client.py +59 -21
- agenthub/integration/playground.py +301 -310
- agenthub/integration/tracer.py +394 -398
- agenthub/kimi_k2_5/__init__.py +18 -0
- agenthub/kimi_k2_5/client.py +388 -0
- agenthub/qwen3/client.py +106 -60
- agenthub/types.py +48 -4
- agenthub/utils.py +35 -0
- agenthub_python-0.3.1.dist-info/METADATA +347 -0
- agenthub_python-0.3.1.dist-info/RECORD +23 -0
- {agenthub_python-0.2.0.dist-info → agenthub_python-0.3.1.dist-info}/WHEEL +2 -2
- agenthub/claude4_5/client.py +0 -347
- agenthub_python-0.2.0.dist-info/METADATA +0 -9
- agenthub_python-0.2.0.dist-info/RECORD +0 -20
agenthub/auto_client.py
CHANGED
|
@@ -46,22 +46,26 @@ class AutoLLMClient(LLMClient):
|
|
|
46
46
|
) -> LLMClient:
|
|
47
47
|
"""Create the appropriate client for the given model."""
|
|
48
48
|
client_type = client_type or os.getenv("CLIENT_TYPE", model.lower())
|
|
49
|
-
if "gemini-3" in client_type: # e.g., gemini-3-flash-preview
|
|
49
|
+
if "gemini-3-" in client_type or "gemini-3.1-" in client_type: # e.g., gemini-3-flash-preview
|
|
50
50
|
from .gemini3 import Gemini3Client
|
|
51
51
|
|
|
52
52
|
return Gemini3Client(model=model, api_key=api_key, base_url=base_url)
|
|
53
|
-
elif "claude" in client_type and "4-
|
|
54
|
-
from .
|
|
53
|
+
elif "claude" in client_type and "4-6" in client_type: # e.g., claude-sonnet-4-6
|
|
54
|
+
from .claude4_6 import Claude4_6Client
|
|
55
55
|
|
|
56
|
-
return
|
|
57
|
-
elif "gpt-5.
|
|
58
|
-
from .
|
|
56
|
+
return Claude4_6Client(model=model, api_key=api_key, base_url=base_url)
|
|
57
|
+
elif "gpt-5.4" in client_type or "gpt-5.5" in client_type: # e.g., gpt-5.5
|
|
58
|
+
from .gpt5_5 import GPT5_5Client
|
|
59
59
|
|
|
60
|
-
return
|
|
61
|
-
elif "glm-
|
|
62
|
-
from .
|
|
60
|
+
return GPT5_5Client(model=model, api_key=api_key, base_url=base_url)
|
|
61
|
+
elif "glm-5" in client_type:
|
|
62
|
+
from .glm5 import GLM5Client
|
|
63
63
|
|
|
64
|
-
return
|
|
64
|
+
return GLM5Client(model=model, api_key=api_key, base_url=base_url)
|
|
65
|
+
elif "kimi-k2.5" in client_type:
|
|
66
|
+
from .kimi_k2_5 import KimiK2_5Client
|
|
67
|
+
|
|
68
|
+
return KimiK2_5Client(model=model, api_key=api_key, base_url=base_url)
|
|
65
69
|
elif "qwen3" in client_type:
|
|
66
70
|
from .qwen3 import Qwen3Client
|
|
67
71
|
|
|
@@ -69,7 +73,7 @@ class AutoLLMClient(LLMClient):
|
|
|
69
73
|
else:
|
|
70
74
|
raise ValueError(
|
|
71
75
|
f"{client_type} is not supported. "
|
|
72
|
-
"Supported client types: gemini-3, claude-4-5, gpt-5.
|
|
76
|
+
"Supported client types: gemini-3, claude-4-6, gpt-5.4, gpt-5.5, glm-5, kimi-k2.5, qwen3."
|
|
73
77
|
)
|
|
74
78
|
|
|
75
79
|
def transform_uni_config_to_model_config(self, config: UniConfig) -> Any:
|
|
@@ -84,6 +88,13 @@ class AutoLLMClient(LLMClient):
|
|
|
84
88
|
"""Delegate to underlying client's transform_model_output_to_uni_event."""
|
|
85
89
|
return self._client.transform_model_output_to_uni_event(model_output)
|
|
86
90
|
|
|
91
|
+
async def _streaming_response_internal(
|
|
92
|
+
self,
|
|
93
|
+
messages: list[UniMessage],
|
|
94
|
+
config: UniConfig,
|
|
95
|
+
) -> AsyncIterator[UniEvent]:
|
|
96
|
+
raise NotImplementedError("Please use streaming_response instead.")
|
|
97
|
+
|
|
87
98
|
async def streaming_response(
|
|
88
99
|
self,
|
|
89
100
|
messages: list[UniMessage],
|
|
@@ -115,3 +126,7 @@ class AutoLLMClient(LLMClient):
|
|
|
115
126
|
def get_history(self) -> list[UniMessage]:
|
|
116
127
|
"""Get history from the underlying client."""
|
|
117
128
|
return self._client.get_history()
|
|
129
|
+
|
|
130
|
+
def set_history(self, history: list[UniMessage]) -> None:
|
|
131
|
+
"""Set history in the underlying client."""
|
|
132
|
+
self._client.set_history(history)
|
agenthub/base_client.py
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
|
+
import time
|
|
15
16
|
from abc import ABC, abstractmethod
|
|
16
17
|
from typing import Any, AsyncIterator
|
|
17
18
|
|
|
@@ -84,21 +85,31 @@ class LLMClient(ABC):
|
|
|
84
85
|
content_items: list[ContentItem] = []
|
|
85
86
|
usage_metadata: UsageMetadata | None = None
|
|
86
87
|
finish_reason: FinishReason | None = None
|
|
88
|
+
created_at: int | None = None
|
|
87
89
|
|
|
88
90
|
for event in events:
|
|
89
91
|
# Merge content_items from all events
|
|
90
92
|
for item in event["content_items"]:
|
|
91
93
|
if item["type"] == "text":
|
|
92
|
-
if
|
|
94
|
+
if (
|
|
95
|
+
content_items
|
|
96
|
+
and content_items[-1]["type"] == "text"
|
|
97
|
+
and content_items[-1].get("signature") is None # no signature yet
|
|
98
|
+
and item.get("phase") is None # no new phase
|
|
99
|
+
):
|
|
93
100
|
content_items[-1]["text"] += item["text"]
|
|
94
|
-
if "signature" in item: #
|
|
101
|
+
if "signature" in item: # finish the current item if signature is not None
|
|
95
102
|
content_items[-1]["signature"] = item["signature"]
|
|
96
|
-
elif item["text"]: #
|
|
103
|
+
elif item["text"] or item.get("phase") is not None: # text or new phase starts an item
|
|
97
104
|
content_items.append(item.copy())
|
|
98
105
|
elif item["type"] == "thinking":
|
|
99
|
-
if
|
|
106
|
+
if (
|
|
107
|
+
content_items
|
|
108
|
+
and content_items[-1]["type"] == "thinking"
|
|
109
|
+
and content_items[-1].get("signature") is None # no signature yet
|
|
110
|
+
):
|
|
100
111
|
content_items[-1]["thinking"] += item["thinking"]
|
|
101
|
-
if "signature" in item: #
|
|
112
|
+
if "signature" in item: # finish the current item if signature is not None
|
|
102
113
|
content_items[-1]["signature"] = item["signature"]
|
|
103
114
|
elif item["thinking"] or item.get("signature"): # omit empty thinking items
|
|
104
115
|
content_items.append(item.copy())
|
|
@@ -110,15 +121,37 @@ class LLMClient(ABC):
|
|
|
110
121
|
|
|
111
122
|
usage_metadata = event.get("usage_metadata") # usage_metadata is taken from the last event
|
|
112
123
|
finish_reason = event.get("finish_reason") # finish_reason is taken from the last event
|
|
124
|
+
created_at = event.get("created_at") # created_at is taken from the last event
|
|
113
125
|
|
|
114
126
|
return {
|
|
115
127
|
"role": "assistant",
|
|
116
128
|
"content_items": content_items,
|
|
117
129
|
"usage_metadata": usage_metadata,
|
|
118
130
|
"finish_reason": finish_reason,
|
|
131
|
+
"created_at": created_at,
|
|
119
132
|
}
|
|
120
133
|
|
|
121
134
|
@abstractmethod
|
|
135
|
+
async def _streaming_response_internal(
|
|
136
|
+
self,
|
|
137
|
+
messages: list[UniMessage],
|
|
138
|
+
config: UniConfig,
|
|
139
|
+
) -> AsyncIterator[UniEvent]:
|
|
140
|
+
"""
|
|
141
|
+
Internal method to handle streaming response.
|
|
142
|
+
|
|
143
|
+
This method should be implemented by each model client to handle
|
|
144
|
+
the actual streaming request and yield model-specific events.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
messages: List of universal message dictionaries
|
|
148
|
+
config: Universal configuration dict
|
|
149
|
+
|
|
150
|
+
Yields:
|
|
151
|
+
Model-specific events from the streaming response
|
|
152
|
+
"""
|
|
153
|
+
pass
|
|
154
|
+
|
|
122
155
|
async def streaming_response(
|
|
123
156
|
self,
|
|
124
157
|
messages: list[UniMessage],
|
|
@@ -138,7 +171,28 @@ class LLMClient(ABC):
|
|
|
138
171
|
Yields:
|
|
139
172
|
Universal events from the streaming response
|
|
140
173
|
"""
|
|
141
|
-
|
|
174
|
+
# Stamp any messages that don't yet have a created_at timestamp
|
|
175
|
+
for msg in messages:
|
|
176
|
+
if "created_at" not in msg:
|
|
177
|
+
msg["created_at"] = int(time.time() * 1000)
|
|
178
|
+
|
|
179
|
+
last_event: UniEvent | None = None
|
|
180
|
+
events = []
|
|
181
|
+
async for event in self._streaming_response_internal(messages, config):
|
|
182
|
+
event["created_at"] = int(time.time() * 1000)
|
|
183
|
+
last_event = event
|
|
184
|
+
events.append(event)
|
|
185
|
+
yield event
|
|
186
|
+
|
|
187
|
+
self._validate_last_event(last_event)
|
|
188
|
+
|
|
189
|
+
# Save history to file if trace_id is specified
|
|
190
|
+
if config.get("trace_id") and events:
|
|
191
|
+
from .integration.tracer import Tracer
|
|
192
|
+
|
|
193
|
+
assistant_message = self.concat_uni_events_to_uni_message(events)
|
|
194
|
+
tracer = Tracer()
|
|
195
|
+
tracer.save_history(self._model, messages + [assistant_message], config["trace_id"], config)
|
|
142
196
|
|
|
143
197
|
async def streaming_response_stateful(
|
|
144
198
|
self,
|
|
@@ -159,26 +213,43 @@ class LLMClient(ABC):
|
|
|
159
213
|
Yields:
|
|
160
214
|
Universal events from the streaming response
|
|
161
215
|
"""
|
|
162
|
-
#
|
|
163
|
-
self._history
|
|
216
|
+
# Build a temporary messages list for inference without mutating history yet
|
|
217
|
+
temp_messages = self._history + [message]
|
|
164
218
|
|
|
165
219
|
# Collect all events for history
|
|
166
220
|
events = []
|
|
167
|
-
async for event in self.streaming_response(messages=
|
|
221
|
+
async for event in self.streaming_response(messages=temp_messages, config=config):
|
|
168
222
|
events.append(event)
|
|
169
223
|
yield event
|
|
170
224
|
|
|
171
|
-
#
|
|
225
|
+
# Only update history after successful inference
|
|
226
|
+
# temp_messages[-1] is the user message, now stamped with created_at by streaming_response
|
|
172
227
|
if events:
|
|
173
228
|
assistant_message = self.concat_uni_events_to_uni_message(events)
|
|
229
|
+
self._history.append(temp_messages[-1])
|
|
174
230
|
self._history.append(assistant_message)
|
|
175
231
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
232
|
+
@staticmethod
|
|
233
|
+
def _validate_last_event(last_event: UniEvent | None) -> None:
|
|
234
|
+
"""Validate that the last event has usage_metadata and finish_reason.
|
|
179
235
|
|
|
180
|
-
|
|
181
|
-
|
|
236
|
+
This validation guards against servers that silently terminate streaming
|
|
237
|
+
output partway through without sending a proper final event.
|
|
238
|
+
|
|
239
|
+
Args:
|
|
240
|
+
last_event: The last event yielded by streaming_response
|
|
241
|
+
|
|
242
|
+
Raises:
|
|
243
|
+
ValueError: If last_event is None or missing usage_metadata/finish_reason
|
|
244
|
+
"""
|
|
245
|
+
if last_event is None:
|
|
246
|
+
raise ValueError("Streaming response yielded no events")
|
|
247
|
+
|
|
248
|
+
if last_event["usage_metadata"] is None:
|
|
249
|
+
raise ValueError(f"Last event must carry usage_metadata, got: {last_event}")
|
|
250
|
+
|
|
251
|
+
if last_event["finish_reason"] is None:
|
|
252
|
+
raise ValueError(f"Last event must carry finish_reason, got: {last_event}")
|
|
182
253
|
|
|
183
254
|
def clear_history(self) -> None:
|
|
184
255
|
"""Clear the message history."""
|
|
@@ -187,3 +258,11 @@ class LLMClient(ABC):
|
|
|
187
258
|
def get_history(self) -> list[UniMessage]:
|
|
188
259
|
"""Get the current message history."""
|
|
189
260
|
return self._history.copy()
|
|
261
|
+
|
|
262
|
+
def set_history(self, history: list[UniMessage]) -> None:
|
|
263
|
+
"""Replace the message history with a copy of the provided history.
|
|
264
|
+
|
|
265
|
+
Args:
|
|
266
|
+
history: List of universal message dictionaries to set as the new history
|
|
267
|
+
"""
|
|
268
|
+
self._history = list(history)
|
|
@@ -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 Claude4_6Client
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
__all__ = ["
|
|
18
|
+
__all__ = ["Claude4_6Client"]
|