agenthub-python 0.1.0__tar.gz → 0.2.0__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.
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: agenthub-python
3
- Version: 0.1.0
4
- Summary: AgentHub package
3
+ Version: 0.2.0
4
+ Summary: AgentHub is the only SDK you need to connect to state-of-the-art LLMs
5
5
  Requires-Dist: google-genai>=1.5.0
6
- Requires-Dist: httpx[socks]
7
6
  Requires-Dist: anthropic>=0.40.0
8
7
  Requires-Dist: flask>=3.0.0
8
+ Requires-Dist: openai>=1.0.0
9
9
  Requires-Python: >=3.11
@@ -13,8 +13,7 @@
13
13
  # limitations under the License.
14
14
 
15
15
  from .auto_client import AutoLLMClient
16
- from .tracer import Tracer
17
- from .types import ThinkingLevel
16
+ from .types import PromptCaching, ThinkingLevel
18
17
 
19
18
 
20
- __all__ = ["AutoLLMClient", "ThinkingLevel", "Tracer"]
19
+ __all__ = ["AutoLLMClient", "PromptCaching", "ThinkingLevel"]
@@ -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 os
15
16
  from typing import Any, AsyncIterator
16
17
 
17
18
  from .base_client import LLMClient
@@ -26,30 +27,50 @@ class AutoLLMClient(LLMClient):
26
27
  conversation history for that specific model.
27
28
  """
28
29
 
29
- def __init__(self, model: str, api_key: str | None = None):
30
+ def __init__(
31
+ self, model: str, api_key: str | None = None, base_url: str | None = None, client_type: str | None = None
32
+ ):
30
33
  """
31
34
  Initialize AutoLLMClient with a specific model.
32
35
 
33
36
  Args:
34
37
  model: Model identifier (determines which client to use)
35
38
  api_key: Optional API key
39
+ base_url: Optional base URL for API requests
40
+ client_type: Optional client type override
36
41
  """
37
- self._client = self._create_client_for_model(model, api_key)
42
+ self._client = self._create_client_for_model(model, api_key, base_url, client_type)
38
43
 
39
- def _create_client_for_model(self, model: str, api_key: str | None = None) -> LLMClient:
44
+ def _create_client_for_model(
45
+ self, model: str, api_key: str | None = None, base_url: str | None = None, client_type: str | None = None
46
+ ) -> LLMClient:
40
47
  """Create the appropriate client for the given model."""
41
- if "gemini-3" in model.lower(): # e.g., gemini-3-flash-preview
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
42
50
  from .gemini3 import Gemini3Client
43
51
 
44
- return Gemini3Client(model=model, api_key=api_key)
45
- elif "claude" in model.lower() and "4-5" in model.lower(): # e.g., claude-sonnet-4-5
52
+ return Gemini3Client(model=model, api_key=api_key, base_url=base_url)
53
+ elif "claude" in client_type and "4-5" in client_type: # e.g., claude-sonnet-4-5
46
54
  from .claude4_5 import Claude4_5Client
47
55
 
48
- return Claude4_5Client(model=model, api_key=api_key)
49
- elif "gpt-5.2" in model.lower(): # e.g., gpt-5.2
50
- raise NotImplementedError("GPT models not yet implemented.")
56
+ return Claude4_5Client(model=model, api_key=api_key, base_url=base_url)
57
+ elif "gpt-5.1" in client_type or "gpt-5.2" in client_type: # e.g., gpt-5.2
58
+ from .gpt5_2 import GPT5_2Client
59
+
60
+ return GPT5_2Client(model=model, api_key=api_key, base_url=base_url)
61
+ elif "glm-4.7" in client_type: # e.g., glm-4.7
62
+ from .glm4_7 import GLM4_7Client
63
+
64
+ return GLM4_7Client(model=model, api_key=api_key, base_url=base_url)
65
+ elif "qwen3" in client_type:
66
+ from .qwen3 import Qwen3Client
67
+
68
+ return Qwen3Client(model=model, api_key=api_key, base_url=base_url)
51
69
  else:
52
- raise ValueError(f"{model} is not supported.")
70
+ raise ValueError(
71
+ f"{client_type} is not supported. "
72
+ "Supported client types: gemini-3, claude-4-5, gpt-5.2, glm-4.7, qwen3."
73
+ )
53
74
 
54
75
  def transform_uni_config_to_model_config(self, config: UniConfig) -> Any:
55
76
  """Delegate to underlying client's transform_uni_config_to_model_config."""
@@ -26,6 +26,7 @@ class LLMClient(ABC):
26
26
  the required abstract methods for complete SDK abstraction.
27
27
  """
28
28
 
29
+ _model: str
29
30
  _history: list[UniMessage] = []
30
31
 
31
32
  @abstractmethod
@@ -99,8 +100,11 @@ class LLMClient(ABC):
99
100
  content_items[-1]["thinking"] += item["thinking"]
100
101
  if "signature" in item: # signature may appear at the last item
101
102
  content_items[-1]["signature"] = item["signature"]
102
- elif item["thinking"]: # omit empty thinking items
103
+ elif item["thinking"] or item.get("signature"): # omit empty thinking items
103
104
  content_items.append(item.copy())
105
+ elif item["type"] == "partial_tool_call":
106
+ # Skip partial_tool_call items - they should already be converted to tool_call
107
+ pass
104
108
  else:
105
109
  content_items.append(item.copy())
106
110
 
@@ -171,10 +175,10 @@ class LLMClient(ABC):
171
175
 
172
176
  # Save history to file if trace_id is specified
173
177
  if config.get("trace_id"):
174
- from .tracer import Tracer
178
+ from .integration.tracer import Tracer
175
179
 
176
180
  tracer = Tracer()
177
- tracer.save_history(self._history, config["trace_id"], config)
181
+ tracer.save_history(self._model, self._history, config["trace_id"], config)
178
182
 
179
183
  def clear_history(self) -> None:
180
184
  """Clear the message history."""
@@ -21,9 +21,10 @@ from anthropic.types import MessageParam, MessageStreamEvent
21
21
 
22
22
  from ..base_client import LLMClient
23
23
  from ..types import (
24
+ EventType,
24
25
  FinishReason,
25
26
  PartialContentItem,
26
- PartialUniEvent,
27
+ PromptCaching,
27
28
  ThinkingLevel,
28
29
  ToolChoice,
29
30
  UniConfig,
@@ -36,14 +37,15 @@ from ..types import (
36
37
  class Claude4_5Client(LLMClient):
37
38
  """Claude 4.5-specific LLM client implementation."""
38
39
 
39
- def __init__(self, model: str, api_key: str | None = None):
40
+ def __init__(self, model: str, api_key: str | None = None, base_url: str | None = None):
40
41
  """Initialize Claude 4.5 client with model and API key."""
41
42
  self._model = model
42
43
  api_key = api_key or os.getenv("ANTHROPIC_API_KEY")
43
- self._client = AsyncAnthropic(api_key=api_key)
44
+ base_url = base_url or os.getenv("ANTHROPIC_BASE_URL")
45
+ self._client = AsyncAnthropic(api_key=api_key, base_url=base_url)
44
46
  self._history: list[UniMessage] = []
45
47
 
46
- def _convert_thinking_level_to_budget(self, thinking_level: ThinkingLevel) -> dict:
48
+ def _convert_thinking_level_to_budget(self, thinking_level: ThinkingLevel) -> dict[str, Any]:
47
49
  """Convert ThinkingLevel enum to Claude's budget_tokens."""
48
50
 
49
51
  mapping = {
@@ -54,7 +56,7 @@ class Claude4_5Client(LLMClient):
54
56
  }
55
57
  return mapping.get(thinking_level)
56
58
 
57
- def _convert_tool_choice(self, tool_choice: ToolChoice) -> dict[str, Any]:
59
+ def _convert_tool_choice(self, tool_choice: ToolChoice) -> dict[str, str]:
58
60
  """Convert ToolChoice to Claude's tool_choice format."""
59
61
  if isinstance(tool_choice, list):
60
62
  if len(tool_choice) > 1:
@@ -80,21 +82,17 @@ class Claude4_5Client(LLMClient):
80
82
  """
81
83
  claude_config = {"model": self._model}
82
84
 
83
- # Add max_tokens (required for Claude)
85
+ if config.get("system_prompt") is not None:
86
+ claude_config["system"] = config["system_prompt"]
87
+
84
88
  if config.get("max_tokens") is not None:
85
89
  claude_config["max_tokens"] = config["max_tokens"]
86
90
  else:
87
91
  claude_config["max_tokens"] = 32768 # Claude requires max_tokens to be specified
88
92
 
89
- # Add temperature
90
93
  if config.get("temperature") is not None:
91
94
  claude_config["temperature"] = config["temperature"]
92
95
 
93
- # Add system prompt
94
- if config.get("system_prompt") is not None:
95
- claude_config["system"] = config["system_prompt"]
96
-
97
- # Convert thinking configuration
98
96
  # NOTE: Claude always provides thinking summary
99
97
  if config.get("thinking_level") is not None:
100
98
  claude_config["temperature"] = 1.0 # `temperature` may only be set to 1 when thinking is enabled
@@ -148,7 +146,7 @@ class Claude4_5Client(LLMClient):
148
146
  "type": "tool_use",
149
147
  "id": item["tool_call_id"],
150
148
  "name": item["name"],
151
- "input": item["argument"],
149
+ "input": item["arguments"],
152
150
  }
153
151
  )
154
152
  elif item["type"] == "tool_result":
@@ -165,7 +163,7 @@ class Claude4_5Client(LLMClient):
165
163
 
166
164
  return claude_messages
167
165
 
168
- def transform_model_output_to_uni_event(self, model_output: MessageStreamEvent) -> PartialUniEvent:
166
+ def transform_model_output_to_uni_event(self, model_output: MessageStreamEvent) -> UniEvent:
169
167
  """
170
168
  Transform Claude model output to universal event format.
171
169
 
@@ -177,7 +175,7 @@ class Claude4_5Client(LLMClient):
177
175
  Returns:
178
176
  Universal event dictionary
179
177
  """
180
- event_type = None
178
+ event_type: EventType | None = None
181
179
  content_items: list[PartialContentItem] = []
182
180
  usage_metadata: UsageMetadata | None = None
183
181
  finish_reason: FinishReason | None = None
@@ -188,7 +186,7 @@ class Claude4_5Client(LLMClient):
188
186
  block = model_output.content_block
189
187
  if block.type == "tool_use":
190
188
  content_items.append(
191
- {"type": "partial_tool_call", "name": block.name, "argument": "", "tool_call_id": block.id}
189
+ {"type": "partial_tool_call", "name": block.name, "arguments": "", "tool_call_id": block.id}
192
190
  )
193
191
 
194
192
  elif claude_event_type == "content_block_delta":
@@ -199,7 +197,9 @@ class Claude4_5Client(LLMClient):
199
197
  elif delta.type == "text_delta":
200
198
  content_items.append({"type": "text", "text": delta.text})
201
199
  elif delta.type == "input_json_delta":
202
- content_items.append({"type": "partial_tool_call", "argument": delta.partial_json})
200
+ content_items.append(
201
+ {"type": "partial_tool_call", "name": "", "arguments": delta.partial_json, "tool_call_id": ""}
202
+ )
203
203
  elif delta.type == "signature_delta":
204
204
  content_items.append({"type": "thinking", "thinking": "", "signature": delta.signature})
205
205
 
@@ -214,6 +214,7 @@ class Claude4_5Client(LLMClient):
214
214
  "prompt_tokens": message.usage.input_tokens,
215
215
  "thoughts_tokens": None,
216
216
  "response_tokens": None,
217
+ "cached_tokens": message.usage.cache_read_input_tokens,
217
218
  }
218
219
 
219
220
  elif claude_event_type == "message_delta":
@@ -233,6 +234,7 @@ class Claude4_5Client(LLMClient):
233
234
  "prompt_tokens": None,
234
235
  "thoughts_tokens": None,
235
236
  "response_tokens": model_output.usage.output_tokens,
237
+ "cached_tokens": None,
236
238
  }
237
239
 
238
240
  elif claude_event_type == "message_stop":
@@ -246,7 +248,7 @@ class Claude4_5Client(LLMClient):
246
248
 
247
249
  return {
248
250
  "role": "assistant",
249
- "event": event_type,
251
+ "event_type": event_type,
250
252
  "content_items": content_items,
251
253
  "usage_metadata": usage_metadata,
252
254
  "finish_reason": finish_reason,
@@ -264,51 +266,81 @@ class Claude4_5Client(LLMClient):
264
266
  # Use unified message conversion
265
267
  claude_messages = self.transform_uni_message_to_model_input(messages)
266
268
 
269
+ # Add cache_control to last user message's last item if enabled
270
+ prompt_caching = config.get("prompt_caching", PromptCaching.ENABLE)
271
+ if prompt_caching != PromptCaching.DISABLE and claude_messages:
272
+ try:
273
+ last_user_message = next(filter(lambda x: x["role"] == "user", claude_messages[::-1]))
274
+ last_content_item = last_user_message["content"][-1]
275
+ last_content_item["cache_control"] = {
276
+ "type": "ephemeral",
277
+ "ttl": "1h" if prompt_caching == PromptCaching.ENHANCE else "5m",
278
+ }
279
+ except StopIteration:
280
+ pass
281
+
267
282
  # Stream generate
268
283
  partial_tool_call = {}
269
284
  partial_usage = {}
270
285
  async with self._client.messages.stream(**claude_config, messages=claude_messages) as stream:
271
286
  async for event in stream:
272
287
  event = self.transform_model_output_to_uni_event(event)
273
- if event["event"] == "start":
274
- if event["content_items"] and event["content_items"][0]["type"] == "partial_tool_call":
275
- partial_tool_call["name"] = event["content_items"][0]["name"]
276
- partial_tool_call["argument"] = ""
277
- partial_tool_call["tool_call_id"] = event["content_items"][0]["tool_call_id"]
288
+ if event["event_type"] == "start":
289
+ for item in event["content_items"]:
290
+ if item["type"] == "partial_tool_call":
291
+ # initialize partial_tool_call
292
+ partial_tool_call = {
293
+ "name": item["name"],
294
+ "arguments": "",
295
+ "tool_call_id": item["tool_call_id"],
296
+ }
297
+ yield event
278
298
 
279
299
  if event["usage_metadata"] is not None:
280
- partial_usage["prompt_tokens"] = event["usage_metadata"]["prompt_tokens"]
300
+ # initialize partial_usage
301
+ partial_usage = {
302
+ "prompt_tokens": event["usage_metadata"]["prompt_tokens"],
303
+ "cached_tokens": event["usage_metadata"]["cached_tokens"],
304
+ }
305
+
306
+ elif event["event_type"] == "delta":
307
+ for item in event["content_items"]:
308
+ if item["type"] == "partial_tool_call":
309
+ # update partial_tool_call
310
+ partial_tool_call["arguments"] += item["arguments"]
281
311
 
282
- elif event["event"] == "delta":
283
- if event["content_items"][0]["type"] == "partial_tool_call":
284
- partial_tool_call["argument"] += event["content_items"][0]["argument"]
285
- else:
286
- event.pop("event")
287
- yield event
312
+ yield event
288
313
 
289
- elif event["event"] == "stop":
290
- if "name" in partial_tool_call and "argument" in partial_tool_call:
314
+ elif event["event_type"] == "stop":
315
+ if "name" in partial_tool_call and "arguments" in partial_tool_call:
316
+ # finish partial_tool_call
291
317
  yield {
292
318
  "role": "assistant",
319
+ "event_type": "delta",
293
320
  "content_items": [
294
321
  {
295
322
  "type": "tool_call",
296
323
  "name": partial_tool_call["name"],
297
- "argument": json.loads(partial_tool_call["argument"]),
324
+ "arguments": json.loads(partial_tool_call["arguments"]),
298
325
  "tool_call_id": partial_tool_call["tool_call_id"],
299
326
  }
300
327
  ],
328
+ "usage_metadata": None,
329
+ "finish_reason": None,
301
330
  }
302
331
  partial_tool_call = {}
303
332
 
304
333
  if "prompt_tokens" in partial_usage and event["usage_metadata"] is not None:
334
+ # finish partial_usage
305
335
  yield {
306
336
  "role": "assistant",
337
+ "event_type": "stop",
307
338
  "content_items": [],
308
339
  "usage_metadata": {
309
340
  "prompt_tokens": partial_usage["prompt_tokens"],
310
341
  "thoughts_tokens": None,
311
342
  "response_tokens": event["usage_metadata"]["response_tokens"],
343
+ "cached_tokens": partial_usage["cached_tokens"],
312
344
  },
313
345
  "finish_reason": event["finish_reason"],
314
346
  }
@@ -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 json
15
16
  import os
16
17
  from typing import AsyncIterator
17
18
 
@@ -20,9 +21,10 @@ from google.genai import types
20
21
 
21
22
  from ..base_client import LLMClient
22
23
  from ..types import (
24
+ EventType,
23
25
  FinishReason,
24
26
  PartialContentItem,
25
- PartialUniEvent,
27
+ PromptCaching,
26
28
  ThinkingLevel,
27
29
  ToolChoice,
28
30
  UniConfig,
@@ -35,11 +37,14 @@ from ..types import (
35
37
  class Gemini3Client(LLMClient):
36
38
  """Gemini 3-specific LLM client implementation."""
37
39
 
38
- def __init__(self, model: str, api_key: str | None = None):
40
+ def __init__(self, model: str, api_key: str | None = None, base_url: str | None = None):
39
41
  """Initialize Gemini 3 client with model and API key."""
40
42
  self._model = model
41
43
  api_key = api_key or os.getenv("GEMINI_API_KEY") or os.getenv("GOOGLE_API_KEY")
42
- self._client = genai.Client(api_key=api_key) if api_key else genai.Client()
44
+ base_url = base_url or os.getenv("GOOGLE_GEMINI_BASE_URL")
45
+ self._client = (
46
+ genai.Client(api_key=api_key, http_options={"base_url": base_url}) if api_key else genai.Client()
47
+ )
43
48
  self._history: list[UniMessage] = []
44
49
 
45
50
  def _detect_mime_type(self, url: str) -> str | None:
@@ -49,7 +54,7 @@ class Gemini3Client(LLMClient):
49
54
  mime_type, _ = mimetypes.guess_type(url)
50
55
  return mime_type
51
56
 
52
- def _convert_thinking_level(self, thinking_level: ThinkingLevel) -> types.ThinkingLevel | None:
57
+ def _convert_thinking_level(self, thinking_level: ThinkingLevel | None) -> types.ThinkingLevel | None:
53
58
  """Convert ThinkingLevel enum to Gemini's ThinkingLevel."""
54
59
  mapping = {
55
60
  ThinkingLevel.NONE: types.ThinkingLevel.MINIMAL,
@@ -90,7 +95,6 @@ class Gemini3Client(LLMClient):
90
95
  if config.get("temperature") is not None:
91
96
  config_params["temperature"] = config["temperature"]
92
97
 
93
- # Convert thinking level
94
98
  thinking_summary = config.get("thinking_summary")
95
99
  thinking_level = config.get("thinking_level")
96
100
  if thinking_summary is not None or thinking_level is not None:
@@ -98,7 +102,6 @@ class Gemini3Client(LLMClient):
98
102
  include_thoughts=thinking_summary, thinking_level=self._convert_thinking_level(thinking_level)
99
103
  )
100
104
 
101
- # Convert tools and tool choice
102
105
  if config.get("tools") is not None:
103
106
  config_params["tools"] = [types.Tool(function_declarations=config["tools"])]
104
107
  tool_choice = config.get("tool_choice")
@@ -106,6 +109,9 @@ class Gemini3Client(LLMClient):
106
109
  tool_config = self._convert_tool_choice(tool_choice)
107
110
  config_params["tool_config"] = types.ToolConfig(function_calling_config=tool_config)
108
111
 
112
+ if config.get("prompt_caching") is not None and config["prompt_caching"] != PromptCaching.ENABLE:
113
+ raise ValueError("prompt_caching must be ENABLE for Gemini 3.")
114
+
109
115
  return types.GenerateContentConfig(**config_params) if config_params else None
110
116
 
111
117
  def transform_uni_message_to_model_input(self, messages: list[UniMessage]) -> list[types.Content]:
@@ -135,7 +141,7 @@ class Gemini3Client(LLMClient):
135
141
  types.Part(text=item["thinking"], thought=True, thought_signature=item.get("signature"))
136
142
  )
137
143
  elif item["type"] == "tool_call":
138
- function_call = types.FunctionCall(name=item["name"], args=item["argument"])
144
+ function_call = types.FunctionCall(name=item["name"], args=item["arguments"])
139
145
  parts.append(types.Part(function_call=function_call, thought_signature=item.get("signature")))
140
146
  elif item["type"] == "tool_result":
141
147
  if "tool_call_id" not in item:
@@ -153,7 +159,7 @@ class Gemini3Client(LLMClient):
153
159
 
154
160
  return contents
155
161
 
156
- def transform_model_output_to_uni_event(self, model_output: types.GenerateContentResponse) -> PartialUniEvent:
162
+ def transform_model_output_to_uni_event(self, model_output: types.GenerateContentResponse) -> UniEvent:
157
163
  """
158
164
  Transform Gemini model output to universal event format.
159
165
 
@@ -163,6 +169,7 @@ class Gemini3Client(LLMClient):
163
169
  Returns:
164
170
  Universal event dictionary
165
171
  """
172
+ event_type: EventType = "delta"
166
173
  content_items: list[PartialContentItem] = []
167
174
  usage_metadata: UsageMetadata | None = None
168
175
  finish_reason: FinishReason | None = None
@@ -174,7 +181,7 @@ class Gemini3Client(LLMClient):
174
181
  {
175
182
  "type": "tool_call",
176
183
  "name": part.function_call.name,
177
- "argument": part.function_call.args,
184
+ "arguments": part.function_call.args,
178
185
  "tool_call_id": part.function_call.name,
179
186
  "signature": part.thought_signature,
180
187
  }
@@ -186,23 +193,26 @@ class Gemini3Client(LLMClient):
186
193
  else:
187
194
  raise ValueError(f"Unknown output: {part}")
188
195
 
189
- if model_output.usage_metadata:
190
- usage_metadata = {
191
- "prompt_tokens": model_output.usage_metadata.prompt_token_count,
192
- "thoughts_tokens": model_output.usage_metadata.thoughts_token_count,
193
- "response_tokens": model_output.usage_metadata.candidates_token_count,
194
- }
195
-
196
196
  if candidate.finish_reason:
197
+ event_type = "stop"
197
198
  stop_reason_mapping = {
198
199
  types.FinishReason.STOP: "stop",
199
200
  types.FinishReason.MAX_TOKENS: "length",
200
201
  }
201
202
  finish_reason = stop_reason_mapping.get(candidate.finish_reason, "unknown")
202
203
 
204
+ if model_output.usage_metadata:
205
+ event_type = event_type or "delta" # deal with separate usage data
206
+ usage_metadata = {
207
+ "prompt_tokens": model_output.usage_metadata.prompt_token_count,
208
+ "thoughts_tokens": model_output.usage_metadata.thoughts_token_count,
209
+ "response_tokens": model_output.usage_metadata.candidates_token_count,
210
+ "cached_tokens": model_output.usage_metadata.cached_content_token_count,
211
+ }
212
+
203
213
  return {
204
214
  "role": "assistant",
205
- "event": "delta",
215
+ "event_type": event_type,
206
216
  "content_items": content_items,
207
217
  "usage_metadata": usage_metadata,
208
218
  "finish_reason": finish_reason,
@@ -226,6 +236,23 @@ class Gemini3Client(LLMClient):
226
236
  )
227
237
  async for chunk in response_stream:
228
238
  event = self.transform_model_output_to_uni_event(chunk)
229
- if event["event"] == "delta":
230
- event.pop("event")
231
- yield event
239
+ for item in event["content_items"]:
240
+ if item["type"] == "tool_call":
241
+ # gemini 3 does not support partial tool call, mock a partial tool call event
242
+ yield {
243
+ "role": "assistant",
244
+ "event_type": "delta",
245
+ "content_items": [
246
+ {
247
+ "type": "partial_tool_call",
248
+ "name": item["name"],
249
+ "arguments": json.dumps(item["arguments"], ensure_ascii=False),
250
+ "tool_call_id": item["tool_call_id"],
251
+ "signature": item.get("signature"),
252
+ }
253
+ ],
254
+ "usage_metadata": None,
255
+ "finish_reason": None,
256
+ }
257
+
258
+ yield event
@@ -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 GLM4_7Client
16
+
17
+
18
+ __all__ = ["GLM4_7Client"]