agenthub-python 0.3.1__tar.gz → 0.3.2__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.3.1 → agenthub_python-0.3.2}/PKG-INFO +7 -3
- {agenthub_python-0.3.1 → agenthub_python-0.3.2}/README.md +6 -2
- agenthub_python-0.3.2/agenthub/abort_signal.py +135 -0
- {agenthub_python-0.3.1 → agenthub_python-0.3.2}/agenthub/auto_client.py +27 -12
- {agenthub_python-0.3.1 → agenthub_python-0.3.2}/agenthub/base_client.py +68 -7
- {agenthub_python-0.3.1 → agenthub_python-0.3.2}/agenthub/claude4_6/client.py +5 -2
- agenthub_python-0.3.2/agenthub/claude4_8/__init__.py +18 -0
- agenthub_python-0.3.2/agenthub/claude4_8/client.py +429 -0
- agenthub_python-0.3.2/agenthub/deepseek_v4/__init__.py +18 -0
- agenthub_python-0.3.2/agenthub/deepseek_v4/client.py +337 -0
- {agenthub_python-0.3.1 → agenthub_python-0.3.2}/agenthub/gemini3/client.py +41 -0
- {agenthub_python-0.3.1/agenthub/qwen3 → agenthub_python-0.3.2/agenthub/glm5_1}/__init__.py +2 -2
- {agenthub_python-0.3.1/agenthub/glm5 → agenthub_python-0.3.2/agenthub/glm5_1}/client.py +10 -9
- {agenthub_python-0.3.1 → agenthub_python-0.3.2}/agenthub/gpt5_5/client.py +2 -6
- agenthub_python-0.3.2/agenthub/integration/playground.py +1192 -0
- {agenthub_python-0.3.1 → agenthub_python-0.3.2}/agenthub/integration/tracer.py +49 -9
- {agenthub_python-0.3.1/agenthub/kimi_k2_5 → agenthub_python-0.3.2/agenthub/kimi_k2_6}/__init__.py +2 -2
- {agenthub_python-0.3.1/agenthub/kimi_k2_5 → agenthub_python-0.3.2/agenthub/kimi_k2_6}/client.py +11 -10
- {agenthub_python-0.3.1/agenthub/glm5 → agenthub_python-0.3.2/agenthub/openai}/__init__.py +2 -2
- {agenthub_python-0.3.1/agenthub/qwen3 → agenthub_python-0.3.2/agenthub/openai}/client.py +92 -95
- {agenthub_python-0.3.1 → agenthub_python-0.3.2}/agenthub/types.py +14 -0
- {agenthub_python-0.3.1 → agenthub_python-0.3.2}/pyproject.toml +1 -1
- agenthub_python-0.3.1/agenthub/integration/playground.py +0 -762
- {agenthub_python-0.3.1 → agenthub_python-0.3.2}/agenthub/__init__.py +0 -0
- {agenthub_python-0.3.1 → agenthub_python-0.3.2}/agenthub/claude4_6/__init__.py +0 -0
- {agenthub_python-0.3.1 → agenthub_python-0.3.2}/agenthub/gemini3/__init__.py +0 -0
- {agenthub_python-0.3.1 → agenthub_python-0.3.2}/agenthub/gpt5_5/__init__.py +0 -0
- {agenthub_python-0.3.1 → agenthub_python-0.3.2}/agenthub/integration/__init__.py +0 -0
- {agenthub_python-0.3.1 → agenthub_python-0.3.2}/agenthub/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agenthub-python
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: AgentHub is the LLM API Hub for the Agent era, built for high-precision autonomous agents.
|
|
5
5
|
Keywords: agent,llm,gemini,claude,gpt
|
|
6
6
|
Author: PrismShadow
|
|
@@ -48,6 +48,9 @@ client = AutoLLMClient(model="gpt-5.5")
|
|
|
48
48
|
|
|
49
49
|
# Optionally specify API key (if not using environment variables)
|
|
50
50
|
client = AutoLLMClient(model="gpt-5.5", api_key="your-openai-api-key")
|
|
51
|
+
|
|
52
|
+
# Use OpenAI Chat Completions-compatible routing explicitly
|
|
53
|
+
client = AutoLLMClient(model="custom-model", client_type="openai")
|
|
51
54
|
```
|
|
52
55
|
|
|
53
56
|
The client automatically selects the appropriate client based on the model name.
|
|
@@ -211,7 +214,7 @@ async def main():
|
|
|
211
214
|
|
|
212
215
|
# Execute function and send result back with tool_call_id
|
|
213
216
|
if tool_call:
|
|
214
|
-
result = get_weather(**tool_call["
|
|
217
|
+
result = get_weather(**tool_call["arguments"])
|
|
215
218
|
|
|
216
219
|
# IMPORTANT: Include tool_call_id in the tool response
|
|
217
220
|
async for event in client.streaming_response_stateful(
|
|
@@ -242,7 +245,7 @@ asyncio.run(main())
|
|
|
242
245
|
"content_items": [
|
|
243
246
|
{"type": "text", "text": "Hello"},
|
|
244
247
|
{"type": "image_url", "image_url": "https://..."},
|
|
245
|
-
{"type": "tool_call", "name": "get_weather", "
|
|
248
|
+
{"type": "tool_call", "name": "get_weather", "arguments": {"location": "London"}, "tool_call_id": "call_abc123"}
|
|
246
249
|
]
|
|
247
250
|
}
|
|
248
251
|
```
|
|
@@ -345,3 +348,4 @@ python -m agenthub.integration.playground --host 127.0.0.1 --port 25751
|
|
|
345
348
|
```
|
|
346
349
|
|
|
347
350
|
Then visit `http://127.0.0.1:25751` in your browser to test with the playground.
|
|
351
|
+
The integrated tracer is available at `http://127.0.0.1:25751/tracer/`.
|
|
@@ -27,6 +27,9 @@ client = AutoLLMClient(model="gpt-5.5")
|
|
|
27
27
|
|
|
28
28
|
# Optionally specify API key (if not using environment variables)
|
|
29
29
|
client = AutoLLMClient(model="gpt-5.5", api_key="your-openai-api-key")
|
|
30
|
+
|
|
31
|
+
# Use OpenAI Chat Completions-compatible routing explicitly
|
|
32
|
+
client = AutoLLMClient(model="custom-model", client_type="openai")
|
|
30
33
|
```
|
|
31
34
|
|
|
32
35
|
The client automatically selects the appropriate client based on the model name.
|
|
@@ -190,7 +193,7 @@ async def main():
|
|
|
190
193
|
|
|
191
194
|
# Execute function and send result back with tool_call_id
|
|
192
195
|
if tool_call:
|
|
193
|
-
result = get_weather(**tool_call["
|
|
196
|
+
result = get_weather(**tool_call["arguments"])
|
|
194
197
|
|
|
195
198
|
# IMPORTANT: Include tool_call_id in the tool response
|
|
196
199
|
async for event in client.streaming_response_stateful(
|
|
@@ -221,7 +224,7 @@ asyncio.run(main())
|
|
|
221
224
|
"content_items": [
|
|
222
225
|
{"type": "text", "text": "Hello"},
|
|
223
226
|
{"type": "image_url", "image_url": "https://..."},
|
|
224
|
-
{"type": "tool_call", "name": "get_weather", "
|
|
227
|
+
{"type": "tool_call", "name": "get_weather", "arguments": {"location": "London"}, "tool_call_id": "call_abc123"}
|
|
225
228
|
]
|
|
226
229
|
}
|
|
227
230
|
```
|
|
@@ -324,3 +327,4 @@ python -m agenthub.integration.playground --host 127.0.0.1 --port 25751
|
|
|
324
327
|
```
|
|
325
328
|
|
|
326
329
|
Then visit `http://127.0.0.1:25751` in your browser to test with the playground.
|
|
330
|
+
The integrated tracer is available at `http://127.0.0.1:25751/tracer/`.
|
|
@@ -0,0 +1,135 @@
|
|
|
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
|
+
import asyncio
|
|
16
|
+
import threading
|
|
17
|
+
from contextlib import suppress
|
|
18
|
+
from typing import Any, Awaitable, TypeVar
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
T = TypeVar("T")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class AbortSignal:
|
|
25
|
+
"""Abort signal that can also trigger its own aborted state."""
|
|
26
|
+
|
|
27
|
+
def __init__(self) -> None:
|
|
28
|
+
self._lock = threading.Lock()
|
|
29
|
+
self._aborted = False
|
|
30
|
+
self._reason: Any = None
|
|
31
|
+
self._waiters: set[asyncio.Future[None]] = set()
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def aborted(self) -> bool:
|
|
35
|
+
with self._lock:
|
|
36
|
+
return self._aborted
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def reason(self) -> Any:
|
|
40
|
+
with self._lock:
|
|
41
|
+
return self._reason
|
|
42
|
+
|
|
43
|
+
def abort(self, reason: Any = None) -> None:
|
|
44
|
+
with self._lock:
|
|
45
|
+
if self._aborted:
|
|
46
|
+
return
|
|
47
|
+
|
|
48
|
+
self._aborted = True
|
|
49
|
+
self._reason = reason
|
|
50
|
+
waiters = tuple(self._waiters)
|
|
51
|
+
self._waiters.clear()
|
|
52
|
+
|
|
53
|
+
for waiter in waiters:
|
|
54
|
+
_notify_waiter(waiter)
|
|
55
|
+
|
|
56
|
+
async def wait(self) -> None:
|
|
57
|
+
loop = asyncio.get_running_loop()
|
|
58
|
+
waiter = loop.create_future()
|
|
59
|
+
with self._lock:
|
|
60
|
+
if self._aborted:
|
|
61
|
+
return
|
|
62
|
+
|
|
63
|
+
self._waiters.add(waiter)
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
await waiter
|
|
67
|
+
finally:
|
|
68
|
+
with self._lock:
|
|
69
|
+
self._waiters.discard(waiter)
|
|
70
|
+
|
|
71
|
+
def throw_if_aborted(self) -> None:
|
|
72
|
+
with self._lock:
|
|
73
|
+
aborted = self._aborted
|
|
74
|
+
reason = self._reason
|
|
75
|
+
|
|
76
|
+
if aborted:
|
|
77
|
+
raise _cancelled_error(reason)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
async def run_with_abort(awaitable: Awaitable[T], signal: AbortSignal) -> T:
|
|
81
|
+
"""Run an awaitable and cancel it when the signal is aborted."""
|
|
82
|
+
|
|
83
|
+
task = asyncio.ensure_future(awaitable)
|
|
84
|
+
|
|
85
|
+
if signal.aborted:
|
|
86
|
+
task.cancel(signal.reason)
|
|
87
|
+
with suppress(asyncio.CancelledError):
|
|
88
|
+
await task
|
|
89
|
+
raise _cancelled_error(signal.reason)
|
|
90
|
+
|
|
91
|
+
abort_task = asyncio.create_task(signal.wait())
|
|
92
|
+
|
|
93
|
+
try:
|
|
94
|
+
done, _ = await asyncio.wait((task, abort_task), return_when=asyncio.FIRST_COMPLETED)
|
|
95
|
+
if task in done:
|
|
96
|
+
return await task
|
|
97
|
+
|
|
98
|
+
task.cancel(signal.reason)
|
|
99
|
+
with suppress(asyncio.CancelledError):
|
|
100
|
+
await task
|
|
101
|
+
raise _cancelled_error(signal.reason)
|
|
102
|
+
except asyncio.CancelledError:
|
|
103
|
+
if not task.done():
|
|
104
|
+
task.cancel()
|
|
105
|
+
with suppress(asyncio.CancelledError):
|
|
106
|
+
await task
|
|
107
|
+
raise
|
|
108
|
+
finally:
|
|
109
|
+
if not abort_task.done():
|
|
110
|
+
abort_task.cancel()
|
|
111
|
+
with suppress(asyncio.CancelledError):
|
|
112
|
+
await abort_task
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _set_waiter_result(waiter: asyncio.Future[None]) -> None:
|
|
116
|
+
if not waiter.done():
|
|
117
|
+
waiter.set_result(None)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _notify_waiter(waiter: asyncio.Future[None]) -> None:
|
|
121
|
+
loop = waiter.get_loop()
|
|
122
|
+
if loop.is_closed():
|
|
123
|
+
return
|
|
124
|
+
|
|
125
|
+
if loop.is_running():
|
|
126
|
+
loop.call_soon_threadsafe(_set_waiter_result, waiter)
|
|
127
|
+
else:
|
|
128
|
+
_set_waiter_result(waiter)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _cancelled_error(reason: Any) -> asyncio.CancelledError:
|
|
132
|
+
if reason is None:
|
|
133
|
+
return asyncio.CancelledError()
|
|
134
|
+
|
|
135
|
+
return asyncio.CancelledError(reason)
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
import os
|
|
16
16
|
from typing import Any, AsyncIterator
|
|
17
17
|
|
|
18
|
+
from .abort_signal import AbortSignal
|
|
18
19
|
from .base_client import LLMClient
|
|
19
20
|
from .types import UniConfig, UniEvent, UniMessage
|
|
20
21
|
|
|
@@ -45,11 +46,17 @@ class AutoLLMClient(LLMClient):
|
|
|
45
46
|
self, model: str, api_key: str | None = None, base_url: str | None = None, client_type: str | None = None
|
|
46
47
|
) -> LLMClient:
|
|
47
48
|
"""Create the appropriate client for the given model."""
|
|
48
|
-
client_type = client_type or os.getenv("CLIENT_TYPE", model.lower()
|
|
49
|
-
if
|
|
49
|
+
client_type = (client_type or os.getenv("CLIENT_TYPE", model)).lower()
|
|
50
|
+
if any(
|
|
51
|
+
prefix in client_type for prefix in ("gemini-3", "gemini-embedding")
|
|
52
|
+
): # e.g., gemini-3-flash-preview, gemini-embedding-2
|
|
50
53
|
from .gemini3 import Gemini3Client
|
|
51
54
|
|
|
52
55
|
return Gemini3Client(model=model, api_key=api_key, base_url=base_url)
|
|
56
|
+
elif "claude" in client_type and ("4-7" in client_type or "4-8" in client_type): # e.g., claude-opus-4-7
|
|
57
|
+
from .claude4_8 import Claude4_8Client
|
|
58
|
+
|
|
59
|
+
return Claude4_8Client(model=model, api_key=api_key, base_url=base_url)
|
|
53
60
|
elif "claude" in client_type and "4-6" in client_type: # e.g., claude-sonnet-4-6
|
|
54
61
|
from .claude4_6 import Claude4_6Client
|
|
55
62
|
|
|
@@ -58,22 +65,26 @@ class AutoLLMClient(LLMClient):
|
|
|
58
65
|
from .gpt5_5 import GPT5_5Client
|
|
59
66
|
|
|
60
67
|
return GPT5_5Client(model=model, api_key=api_key, base_url=base_url)
|
|
61
|
-
elif "glm-5" in client_type:
|
|
62
|
-
from .
|
|
68
|
+
elif "glm-5" in client_type or "glm-5.1" in client_type:
|
|
69
|
+
from .glm5_1 import GLM5_1Client
|
|
70
|
+
|
|
71
|
+
return GLM5_1Client(model=model, api_key=api_key, base_url=base_url)
|
|
72
|
+
elif "kimi-k2.5" in client_type or "kimi-k2.6" in client_type:
|
|
73
|
+
from .kimi_k2_6 import KimiK2_6Client
|
|
63
74
|
|
|
64
|
-
return
|
|
65
|
-
elif "
|
|
66
|
-
from .
|
|
75
|
+
return KimiK2_6Client(model=model, api_key=api_key, base_url=base_url)
|
|
76
|
+
elif "deepseek-v4" in client_type:
|
|
77
|
+
from .deepseek_v4 import DeepSeekV4Client
|
|
67
78
|
|
|
68
|
-
return
|
|
69
|
-
elif "
|
|
70
|
-
from .
|
|
79
|
+
return DeepSeekV4Client(model=model, api_key=api_key, base_url=base_url)
|
|
80
|
+
elif "openai" in client_type:
|
|
81
|
+
from .openai import OpenaiClient
|
|
71
82
|
|
|
72
|
-
return
|
|
83
|
+
return OpenaiClient(model=model, api_key=api_key, base_url=base_url)
|
|
73
84
|
else:
|
|
74
85
|
raise ValueError(
|
|
75
86
|
f"{client_type} is not supported. "
|
|
76
|
-
"Supported client types: gemini-3, claude-4-6, gpt-5.
|
|
87
|
+
"Supported client types: gemini-3, claude-4-8, claude-4-7, claude-4-6, gpt-5.5, gpt-5.4, glm-5.1, kimi-k2.6, kimi-k2.5, deepseek-v4, openai."
|
|
77
88
|
)
|
|
78
89
|
|
|
79
90
|
def transform_uni_config_to_model_config(self, config: UniConfig) -> Any:
|
|
@@ -99,11 +110,13 @@ class AutoLLMClient(LLMClient):
|
|
|
99
110
|
self,
|
|
100
111
|
messages: list[UniMessage],
|
|
101
112
|
config: UniConfig,
|
|
113
|
+
signal: AbortSignal | None = None,
|
|
102
114
|
) -> AsyncIterator[UniEvent]:
|
|
103
115
|
"""Route to underlying client's streaming_response."""
|
|
104
116
|
async for event in self._client.streaming_response(
|
|
105
117
|
messages=messages,
|
|
106
118
|
config=config,
|
|
119
|
+
signal=signal,
|
|
107
120
|
):
|
|
108
121
|
yield event
|
|
109
122
|
|
|
@@ -111,11 +124,13 @@ class AutoLLMClient(LLMClient):
|
|
|
111
124
|
self,
|
|
112
125
|
message: UniMessage,
|
|
113
126
|
config: UniConfig,
|
|
127
|
+
signal: AbortSignal | None = None,
|
|
114
128
|
) -> AsyncIterator[UniEvent]:
|
|
115
129
|
"""Route to underlying client's streaming_response_stateful."""
|
|
116
130
|
async for event in self._client.streaming_response_stateful(
|
|
117
131
|
message=message,
|
|
118
132
|
config=config,
|
|
133
|
+
signal=signal,
|
|
119
134
|
):
|
|
120
135
|
yield event
|
|
121
136
|
|
|
@@ -12,11 +12,21 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
|
+
import asyncio
|
|
15
16
|
import time
|
|
16
17
|
from abc import ABC, abstractmethod
|
|
18
|
+
from contextlib import suppress
|
|
17
19
|
from typing import Any, AsyncIterator
|
|
18
20
|
|
|
19
|
-
from .
|
|
21
|
+
from .abort_signal import AbortSignal
|
|
22
|
+
from .types import (
|
|
23
|
+
ContentItem,
|
|
24
|
+
FinishReason,
|
|
25
|
+
UniConfig,
|
|
26
|
+
UniEvent,
|
|
27
|
+
UniMessage,
|
|
28
|
+
UsageMetadata,
|
|
29
|
+
)
|
|
20
30
|
|
|
21
31
|
|
|
22
32
|
class LLMClient(ABC):
|
|
@@ -156,6 +166,7 @@ class LLMClient(ABC):
|
|
|
156
166
|
self,
|
|
157
167
|
messages: list[UniMessage],
|
|
158
168
|
config: UniConfig,
|
|
169
|
+
signal: AbortSignal | None = None,
|
|
159
170
|
) -> AsyncIterator[UniEvent]:
|
|
160
171
|
"""
|
|
161
172
|
Generate content in streaming mode (stateless).
|
|
@@ -167,6 +178,7 @@ class LLMClient(ABC):
|
|
|
167
178
|
Args:
|
|
168
179
|
messages: List of universal message dictionaries containing conversation history
|
|
169
180
|
config: Universal configuration dict
|
|
181
|
+
signal: Optional abort signal used to cancel the active request
|
|
170
182
|
|
|
171
183
|
Yields:
|
|
172
184
|
Universal events from the streaming response
|
|
@@ -178,11 +190,58 @@ class LLMClient(ABC):
|
|
|
178
190
|
|
|
179
191
|
last_event: UniEvent | None = None
|
|
180
192
|
events = []
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
193
|
+
if signal is not None:
|
|
194
|
+
signal.throw_if_aborted()
|
|
195
|
+
|
|
196
|
+
stream = self._streaming_response_internal(messages, config)
|
|
197
|
+
abort_task: asyncio.Task[None] | None = None
|
|
198
|
+
waiting_for_stream = False
|
|
199
|
+
if signal is not None:
|
|
200
|
+
streaming_task = asyncio.current_task()
|
|
201
|
+
abort_task = asyncio.create_task(signal.wait())
|
|
202
|
+
|
|
203
|
+
def cancel_streaming_task(task: asyncio.Task[None]) -> None:
|
|
204
|
+
if (
|
|
205
|
+
task.cancelled()
|
|
206
|
+
or not signal.aborted
|
|
207
|
+
or not waiting_for_stream
|
|
208
|
+
or streaming_task is None
|
|
209
|
+
or streaming_task.done()
|
|
210
|
+
):
|
|
211
|
+
return
|
|
212
|
+
|
|
213
|
+
streaming_task.cancel(signal.reason)
|
|
214
|
+
|
|
215
|
+
abort_task.add_done_callback(cancel_streaming_task)
|
|
216
|
+
|
|
217
|
+
try:
|
|
218
|
+
while True:
|
|
219
|
+
try:
|
|
220
|
+
if signal is not None:
|
|
221
|
+
signal.throw_if_aborted()
|
|
222
|
+
waiting_for_stream = True
|
|
223
|
+
signal.throw_if_aborted()
|
|
224
|
+
|
|
225
|
+
event = await anext(stream)
|
|
226
|
+
except StopAsyncIteration:
|
|
227
|
+
break
|
|
228
|
+
except asyncio.CancelledError:
|
|
229
|
+
if signal is not None and signal.aborted:
|
|
230
|
+
signal.throw_if_aborted()
|
|
231
|
+
raise
|
|
232
|
+
finally:
|
|
233
|
+
waiting_for_stream = False
|
|
234
|
+
|
|
235
|
+
event["created_at"] = int(time.time() * 1000)
|
|
236
|
+
last_event = event
|
|
237
|
+
events.append(event)
|
|
238
|
+
yield event
|
|
239
|
+
finally:
|
|
240
|
+
if abort_task is not None and not abort_task.done():
|
|
241
|
+
abort_task.cancel()
|
|
242
|
+
with suppress(asyncio.CancelledError):
|
|
243
|
+
await abort_task
|
|
244
|
+
await stream.aclose()
|
|
186
245
|
|
|
187
246
|
self._validate_last_event(last_event)
|
|
188
247
|
|
|
@@ -198,6 +257,7 @@ class LLMClient(ABC):
|
|
|
198
257
|
self,
|
|
199
258
|
message: UniMessage,
|
|
200
259
|
config: UniConfig,
|
|
260
|
+
signal: AbortSignal | None = None,
|
|
201
261
|
) -> AsyncIterator[UniEvent]:
|
|
202
262
|
"""
|
|
203
263
|
Generate content in streaming mode (stateful).
|
|
@@ -209,6 +269,7 @@ class LLMClient(ABC):
|
|
|
209
269
|
Args:
|
|
210
270
|
message: Latest universal message dictionary to add to conversation
|
|
211
271
|
config: Universal configuration dict
|
|
272
|
+
signal: Optional abort signal used to cancel the active request
|
|
212
273
|
|
|
213
274
|
Yields:
|
|
214
275
|
Universal events from the streaming response
|
|
@@ -218,7 +279,7 @@ class LLMClient(ABC):
|
|
|
218
279
|
|
|
219
280
|
# Collect all events for history
|
|
220
281
|
events = []
|
|
221
|
-
async for event in self.streaming_response(messages=temp_messages, config=config):
|
|
282
|
+
async for event in self.streaming_response(messages=temp_messages, config=config, signal=signal):
|
|
222
283
|
events.append(event)
|
|
223
284
|
yield event
|
|
224
285
|
|
|
@@ -110,6 +110,7 @@ class Claude4_6Client(LLMClient):
|
|
|
110
110
|
ThinkingLevel.LOW: {"thinking": {"type": "adaptive"}, "output_config": {"effort": "low"}},
|
|
111
111
|
ThinkingLevel.MEDIUM: {"thinking": {"type": "adaptive"}, "output_config": {"effort": "medium"}},
|
|
112
112
|
ThinkingLevel.HIGH: {"thinking": {"type": "adaptive"}, "output_config": {"effort": "high"}},
|
|
113
|
+
ThinkingLevel.XHIGH: {"thinking": {"type": "adaptive"}, "output_config": {"effort": "high"}},
|
|
113
114
|
}
|
|
114
115
|
return mapping.get(thinking_level)
|
|
115
116
|
|
|
@@ -145,7 +146,7 @@ class Claude4_6Client(LLMClient):
|
|
|
145
146
|
if config.get("max_tokens") is not None:
|
|
146
147
|
claude_config["max_tokens"] = config["max_tokens"]
|
|
147
148
|
else:
|
|
148
|
-
claude_config["max_tokens"] =
|
|
149
|
+
claude_config["max_tokens"] = 64000 # Claude requires max_tokens to be specified
|
|
149
150
|
|
|
150
151
|
if config.get("temperature") is not None:
|
|
151
152
|
claude_config["temperature"] = config["temperature"]
|
|
@@ -373,7 +374,9 @@ class Claude4_6Client(LLMClient):
|
|
|
373
374
|
"arguments": "",
|
|
374
375
|
"tool_call_id": item["tool_call_id"],
|
|
375
376
|
}
|
|
376
|
-
|
|
377
|
+
|
|
378
|
+
if event["content_items"]:
|
|
379
|
+
yield event
|
|
377
380
|
|
|
378
381
|
if event["usage_metadata"] is not None:
|
|
379
382
|
# initialize partial_usage
|
|
@@ -0,0 +1,18 @@
|
|
|
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 .client import Claude4_8Client
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
__all__ = ["Claude4_8Client"]
|