agentrun-inner-test 0.0.46__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.
Files changed (135) hide show
  1. agentrun/__init__.py +325 -0
  2. agentrun/agent_runtime/__client_async_template.py +466 -0
  3. agentrun/agent_runtime/__endpoint_async_template.py +345 -0
  4. agentrun/agent_runtime/__init__.py +53 -0
  5. agentrun/agent_runtime/__runtime_async_template.py +477 -0
  6. agentrun/agent_runtime/api/__data_async_template.py +58 -0
  7. agentrun/agent_runtime/api/__init__.py +6 -0
  8. agentrun/agent_runtime/api/control.py +1362 -0
  9. agentrun/agent_runtime/api/data.py +98 -0
  10. agentrun/agent_runtime/client.py +868 -0
  11. agentrun/agent_runtime/endpoint.py +649 -0
  12. agentrun/agent_runtime/model.py +362 -0
  13. agentrun/agent_runtime/runtime.py +904 -0
  14. agentrun/credential/__client_async_template.py +177 -0
  15. agentrun/credential/__credential_async_template.py +216 -0
  16. agentrun/credential/__init__.py +28 -0
  17. agentrun/credential/api/__init__.py +5 -0
  18. agentrun/credential/api/control.py +606 -0
  19. agentrun/credential/client.py +319 -0
  20. agentrun/credential/credential.py +381 -0
  21. agentrun/credential/model.py +248 -0
  22. agentrun/integration/__init__.py +21 -0
  23. agentrun/integration/agentscope/__init__.py +12 -0
  24. agentrun/integration/agentscope/adapter.py +17 -0
  25. agentrun/integration/agentscope/builtin.py +65 -0
  26. agentrun/integration/agentscope/message_adapter.py +185 -0
  27. agentrun/integration/agentscope/model_adapter.py +60 -0
  28. agentrun/integration/agentscope/tool_adapter.py +59 -0
  29. agentrun/integration/builtin/__init__.py +16 -0
  30. agentrun/integration/builtin/model.py +93 -0
  31. agentrun/integration/builtin/sandbox.py +1234 -0
  32. agentrun/integration/builtin/toolset.py +47 -0
  33. agentrun/integration/crewai/__init__.py +12 -0
  34. agentrun/integration/crewai/adapter.py +9 -0
  35. agentrun/integration/crewai/builtin.py +65 -0
  36. agentrun/integration/crewai/model_adapter.py +31 -0
  37. agentrun/integration/crewai/tool_adapter.py +26 -0
  38. agentrun/integration/google_adk/__init__.py +12 -0
  39. agentrun/integration/google_adk/adapter.py +15 -0
  40. agentrun/integration/google_adk/builtin.py +65 -0
  41. agentrun/integration/google_adk/message_adapter.py +144 -0
  42. agentrun/integration/google_adk/model_adapter.py +46 -0
  43. agentrun/integration/google_adk/tool_adapter.py +235 -0
  44. agentrun/integration/langchain/__init__.py +30 -0
  45. agentrun/integration/langchain/adapter.py +15 -0
  46. agentrun/integration/langchain/builtin.py +71 -0
  47. agentrun/integration/langchain/message_adapter.py +141 -0
  48. agentrun/integration/langchain/model_adapter.py +37 -0
  49. agentrun/integration/langchain/tool_adapter.py +50 -0
  50. agentrun/integration/langgraph/__init__.py +35 -0
  51. agentrun/integration/langgraph/adapter.py +20 -0
  52. agentrun/integration/langgraph/agent_converter.py +1073 -0
  53. agentrun/integration/langgraph/builtin.py +65 -0
  54. agentrun/integration/pydantic_ai/__init__.py +12 -0
  55. agentrun/integration/pydantic_ai/adapter.py +13 -0
  56. agentrun/integration/pydantic_ai/builtin.py +65 -0
  57. agentrun/integration/pydantic_ai/model_adapter.py +44 -0
  58. agentrun/integration/pydantic_ai/tool_adapter.py +19 -0
  59. agentrun/integration/utils/__init__.py +112 -0
  60. agentrun/integration/utils/adapter.py +560 -0
  61. agentrun/integration/utils/canonical.py +164 -0
  62. agentrun/integration/utils/converter.py +134 -0
  63. agentrun/integration/utils/model.py +110 -0
  64. agentrun/integration/utils/tool.py +1759 -0
  65. agentrun/model/__client_async_template.py +357 -0
  66. agentrun/model/__init__.py +57 -0
  67. agentrun/model/__model_proxy_async_template.py +270 -0
  68. agentrun/model/__model_service_async_template.py +267 -0
  69. agentrun/model/api/__init__.py +6 -0
  70. agentrun/model/api/control.py +1173 -0
  71. agentrun/model/api/data.py +196 -0
  72. agentrun/model/client.py +674 -0
  73. agentrun/model/model.py +235 -0
  74. agentrun/model/model_proxy.py +439 -0
  75. agentrun/model/model_service.py +438 -0
  76. agentrun/sandbox/__aio_sandbox_async_template.py +523 -0
  77. agentrun/sandbox/__browser_sandbox_async_template.py +110 -0
  78. agentrun/sandbox/__client_async_template.py +491 -0
  79. agentrun/sandbox/__code_interpreter_sandbox_async_template.py +463 -0
  80. agentrun/sandbox/__init__.py +69 -0
  81. agentrun/sandbox/__sandbox_async_template.py +463 -0
  82. agentrun/sandbox/__template_async_template.py +152 -0
  83. agentrun/sandbox/aio_sandbox.py +905 -0
  84. agentrun/sandbox/api/__aio_data_async_template.py +335 -0
  85. agentrun/sandbox/api/__browser_data_async_template.py +140 -0
  86. agentrun/sandbox/api/__code_interpreter_data_async_template.py +206 -0
  87. agentrun/sandbox/api/__init__.py +19 -0
  88. agentrun/sandbox/api/__sandbox_data_async_template.py +107 -0
  89. agentrun/sandbox/api/aio_data.py +551 -0
  90. agentrun/sandbox/api/browser_data.py +172 -0
  91. agentrun/sandbox/api/code_interpreter_data.py +396 -0
  92. agentrun/sandbox/api/control.py +1051 -0
  93. agentrun/sandbox/api/playwright_async.py +492 -0
  94. agentrun/sandbox/api/playwright_sync.py +492 -0
  95. agentrun/sandbox/api/sandbox_data.py +154 -0
  96. agentrun/sandbox/browser_sandbox.py +185 -0
  97. agentrun/sandbox/client.py +925 -0
  98. agentrun/sandbox/code_interpreter_sandbox.py +823 -0
  99. agentrun/sandbox/model.py +397 -0
  100. agentrun/sandbox/sandbox.py +848 -0
  101. agentrun/sandbox/template.py +217 -0
  102. agentrun/server/__init__.py +191 -0
  103. agentrun/server/agui_normalizer.py +180 -0
  104. agentrun/server/agui_protocol.py +797 -0
  105. agentrun/server/invoker.py +309 -0
  106. agentrun/server/model.py +427 -0
  107. agentrun/server/openai_protocol.py +535 -0
  108. agentrun/server/protocol.py +140 -0
  109. agentrun/server/server.py +208 -0
  110. agentrun/toolset/__client_async_template.py +62 -0
  111. agentrun/toolset/__init__.py +51 -0
  112. agentrun/toolset/__toolset_async_template.py +204 -0
  113. agentrun/toolset/api/__init__.py +17 -0
  114. agentrun/toolset/api/control.py +262 -0
  115. agentrun/toolset/api/mcp.py +100 -0
  116. agentrun/toolset/api/openapi.py +1251 -0
  117. agentrun/toolset/client.py +102 -0
  118. agentrun/toolset/model.py +321 -0
  119. agentrun/toolset/toolset.py +270 -0
  120. agentrun/utils/__data_api_async_template.py +720 -0
  121. agentrun/utils/__init__.py +5 -0
  122. agentrun/utils/__resource_async_template.py +158 -0
  123. agentrun/utils/config.py +258 -0
  124. agentrun/utils/control_api.py +78 -0
  125. agentrun/utils/data_api.py +1120 -0
  126. agentrun/utils/exception.py +151 -0
  127. agentrun/utils/helper.py +108 -0
  128. agentrun/utils/log.py +77 -0
  129. agentrun/utils/model.py +168 -0
  130. agentrun/utils/resource.py +291 -0
  131. agentrun_inner_test-0.0.46.dist-info/METADATA +263 -0
  132. agentrun_inner_test-0.0.46.dist-info/RECORD +135 -0
  133. agentrun_inner_test-0.0.46.dist-info/WHEEL +5 -0
  134. agentrun_inner_test-0.0.46.dist-info/licenses/LICENSE +201 -0
  135. agentrun_inner_test-0.0.46.dist-info/top_level.txt +1 -0
@@ -0,0 +1,535 @@
1
+ """OpenAI Completions API 协议实现 / OpenAI Completions API Protocol Implementation
2
+
3
+ 实现 OpenAI Chat Completions API 兼容接口。
4
+ 参考: https://platform.openai.com/docs/api-reference/chat/create
5
+
6
+ 本实现将 AgentResult 事件转换为 OpenAI 流式响应格式。
7
+ """
8
+
9
+ import json
10
+ import time
11
+ from typing import Any, AsyncIterator, Dict, List, Optional, TYPE_CHECKING
12
+ import uuid
13
+
14
+ from fastapi import APIRouter, Request
15
+ from fastapi.responses import JSONResponse, StreamingResponse
16
+ import pydash
17
+
18
+ from ..utils.helper import merge, MergeOptions
19
+ from .model import (
20
+ AgentEvent,
21
+ AgentRequest,
22
+ EventType,
23
+ Message,
24
+ MessageRole,
25
+ OpenAIProtocolConfig,
26
+ ServerConfig,
27
+ Tool,
28
+ ToolCall,
29
+ )
30
+ from .protocol import BaseProtocolHandler
31
+
32
+ if TYPE_CHECKING:
33
+ from .invoker import AgentInvoker
34
+
35
+
36
+ # ============================================================================
37
+ # OpenAI 协议处理器
38
+ # ============================================================================
39
+
40
+
41
+ DEFAULT_PREFIX = "/openai/v1"
42
+
43
+
44
+ class OpenAIProtocolHandler(BaseProtocolHandler):
45
+ """OpenAI Completions API 协议处理器
46
+
47
+ 实现 OpenAI Chat Completions API 兼容接口。
48
+ 参考: https://platform.openai.com/docs/api-reference/chat/create
49
+
50
+ 特点:
51
+ - 完全兼容 OpenAI API 格式
52
+ - 支持流式和非流式响应
53
+ - 支持工具调用
54
+ - AgentResult 事件自动转换为 OpenAI 格式
55
+
56
+ 支持的事件映射:
57
+ - TEXT_MESSAGE_* → delta.content
58
+ - TOOL_CALL_* → delta.tool_calls
59
+ - RUN_FINISHED → [DONE]
60
+ - 其他事件 → 忽略
61
+
62
+ Example:
63
+ >>> from agentrun.server import AgentRunServer
64
+ >>>
65
+ >>> def my_agent(request):
66
+ ... return "Hello, world!"
67
+ >>>
68
+ >>> server = AgentRunServer(invoke_agent=my_agent)
69
+ >>> server.start(port=8000)
70
+ # 可访问: POST http://localhost:8000/openai/v1/chat/completions
71
+ """
72
+
73
+ name = "openai_chat_completions"
74
+
75
+ def __init__(self, config: Optional[ServerConfig] = None):
76
+ self.config = config.openai if config else None
77
+
78
+ def get_prefix(self) -> str:
79
+ """OpenAI 协议建议使用 /openai/v1 前缀"""
80
+ return pydash.get(self.config, "prefix", DEFAULT_PREFIX)
81
+
82
+ def get_model_name(self) -> str:
83
+ """获取默认模型名称"""
84
+ return pydash.get(self.config, "model_name", "agentrun")
85
+
86
+ def as_fastapi_router(self, agent_invoker: "AgentInvoker") -> APIRouter:
87
+ """创建 OpenAI 协议的 FastAPI Router"""
88
+ router = APIRouter()
89
+
90
+ @router.post("/chat/completions")
91
+ async def chat_completions(request: Request):
92
+ """OpenAI Chat Completions 端点"""
93
+ sse_headers = {
94
+ "Cache-Control": "no-cache",
95
+ "Connection": "keep-alive",
96
+ "X-Accel-Buffering": "no",
97
+ }
98
+
99
+ try:
100
+ request_data = await request.json()
101
+ agent_request, context = await self.parse_request(
102
+ request, request_data
103
+ )
104
+
105
+ if agent_request.stream:
106
+ # 流式响应
107
+ event_stream = self._format_stream(
108
+ agent_invoker.invoke_stream(agent_request),
109
+ context,
110
+ )
111
+ return StreamingResponse(
112
+ event_stream,
113
+ media_type="text/event-stream",
114
+ headers=sse_headers,
115
+ )
116
+ else:
117
+ # 非流式响应
118
+ results = await agent_invoker.invoke(agent_request)
119
+ if hasattr(results, "__aiter__"):
120
+ # 收集流式结果
121
+ result_list = []
122
+ async for r in results:
123
+ result_list.append(r)
124
+ results = result_list
125
+
126
+ formatted = self._format_non_stream(results, context)
127
+ return JSONResponse(formatted)
128
+
129
+ except ValueError as e:
130
+ return JSONResponse(
131
+ {
132
+ "error": {
133
+ "message": str(e),
134
+ "type": "invalid_request_error",
135
+ }
136
+ },
137
+ status_code=400,
138
+ )
139
+ except Exception as e:
140
+ return JSONResponse(
141
+ {"error": {"message": str(e), "type": "internal_error"}},
142
+ status_code=500,
143
+ )
144
+
145
+ @router.get("/models")
146
+ async def list_models():
147
+ """列出可用模型"""
148
+ return {
149
+ "object": "list",
150
+ "data": [{
151
+ "id": self.get_model_name(),
152
+ "object": "model",
153
+ "created": int(time.time()),
154
+ "owned_by": "agentrun",
155
+ }],
156
+ }
157
+
158
+ return router
159
+
160
+ async def parse_request(
161
+ self,
162
+ request: Request,
163
+ request_data: Dict[str, Any],
164
+ ) -> tuple[AgentRequest, Dict[str, Any]]:
165
+ """解析 OpenAI 格式的请求
166
+
167
+ Args:
168
+ request: FastAPI Request 对象
169
+ request_data: HTTP 请求体 JSON 数据
170
+
171
+ Returns:
172
+ tuple: (AgentRequest, context)
173
+ """
174
+ # 验证必需字段
175
+ if "messages" not in request_data:
176
+ raise ValueError("Missing required field: messages")
177
+
178
+ # 创建上下文
179
+ context = {
180
+ "response_id": f"chatcmpl-{uuid.uuid4().hex[:12]}",
181
+ "model": request_data.get("model", self.get_model_name()),
182
+ "created": int(time.time()),
183
+ }
184
+
185
+ # 解析消息列表
186
+ messages = self._parse_messages(request_data["messages"])
187
+
188
+ # 解析工具列表
189
+ tools = self._parse_tools(request_data.get("tools"))
190
+
191
+ # 构建 AgentRequest
192
+ agent_request = AgentRequest(
193
+ protocol="openai", # 设置协议名称
194
+ messages=messages,
195
+ stream=request_data.get("stream", False),
196
+ tools=tools,
197
+ raw_request=request, # 保留原始请求对象
198
+ )
199
+
200
+ return agent_request, context
201
+
202
+ def _parse_messages(
203
+ self, raw_messages: List[Dict[str, Any]]
204
+ ) -> List[Message]:
205
+ """解析消息列表
206
+
207
+ Args:
208
+ raw_messages: 原始消息数据
209
+
210
+ Returns:
211
+ 标准化的消息列表
212
+ """
213
+ messages = []
214
+
215
+ for msg_data in raw_messages:
216
+ if not isinstance(msg_data, dict):
217
+ raise ValueError(f"Invalid message format: {msg_data}")
218
+
219
+ if "role" not in msg_data:
220
+ raise ValueError("Message missing 'role' field")
221
+
222
+ try:
223
+ role = MessageRole(msg_data["role"])
224
+ except ValueError as e:
225
+ raise ValueError(
226
+ f"Invalid message role: {msg_data['role']}"
227
+ ) from e
228
+
229
+ # 解析 tool_calls
230
+ tool_calls = None
231
+ if msg_data.get("tool_calls"):
232
+ tool_calls = [
233
+ ToolCall(
234
+ id=tc.get("id", ""),
235
+ type=tc.get("type", "function"),
236
+ function=tc.get("function", {}),
237
+ )
238
+ for tc in msg_data["tool_calls"]
239
+ ]
240
+
241
+ messages.append(
242
+ Message(
243
+ role=role,
244
+ content=msg_data.get("content"),
245
+ name=msg_data.get("name"),
246
+ tool_calls=tool_calls,
247
+ tool_call_id=msg_data.get("tool_call_id"),
248
+ )
249
+ )
250
+
251
+ return messages
252
+
253
+ def _parse_tools(
254
+ self, raw_tools: Optional[List[Dict[str, Any]]]
255
+ ) -> Optional[List[Tool]]:
256
+ """解析工具列表
257
+
258
+ Args:
259
+ raw_tools: 原始工具数据
260
+
261
+ Returns:
262
+ 标准化的工具列表
263
+ """
264
+ if not raw_tools:
265
+ return None
266
+
267
+ tools = []
268
+ for tool_data in raw_tools:
269
+ if not isinstance(tool_data, dict):
270
+ continue
271
+
272
+ tools.append(
273
+ Tool(
274
+ type=tool_data.get("type", "function"),
275
+ function=tool_data.get("function", {}),
276
+ )
277
+ )
278
+
279
+ return tools if tools else None
280
+
281
+ async def _format_stream(
282
+ self,
283
+ event_stream: AsyncIterator[AgentEvent],
284
+ context: Dict[str, Any],
285
+ ) -> AsyncIterator[str]:
286
+ """将 AgentEvent 流转换为 OpenAI SSE 格式
287
+
288
+ 自动生成边界事件:
289
+ - 首个 TEXT 事件前发送 role: assistant
290
+ - 工具调用自动追踪索引
291
+ - 流结束发送 finish_reason 和 [DONE]
292
+
293
+ Args:
294
+ event_stream: AgentEvent 流
295
+ context: 上下文信息
296
+
297
+ Yields:
298
+ SSE 格式的字符串
299
+ """
300
+ # 状态追踪
301
+ sent_role = False
302
+ has_text = False
303
+ tool_call_index = -1 # 从 -1 开始,第一个工具调用时变为 0
304
+ # 工具调用状态:{tool_id: {"started": bool, "index": int}}
305
+ tool_call_states: Dict[str, Dict[str, Any]] = {}
306
+ has_tool_calls = False
307
+
308
+ async for event in event_stream:
309
+ # RAW 事件直接透传
310
+ if event.event == EventType.RAW:
311
+ raw = event.data.get("raw", "")
312
+ if raw:
313
+ if not raw.endswith("\n\n"):
314
+ raw = raw.rstrip("\n") + "\n\n"
315
+ yield raw
316
+ continue
317
+
318
+ # TEXT 事件
319
+ if event.event == EventType.TEXT:
320
+ delta: Dict[str, Any] = {}
321
+ # 首个 TEXT 事件,发送 role
322
+ if not sent_role:
323
+ delta["role"] = "assistant"
324
+ sent_role = True
325
+
326
+ content = event.data.get("delta", "")
327
+ if content:
328
+ delta["content"] = content
329
+ has_text = True
330
+
331
+ # 应用 addition
332
+ if event.addition:
333
+ delta = self._apply_addition(
334
+ delta,
335
+ event.addition,
336
+ event.addition_merge_options,
337
+ )
338
+
339
+ yield self._build_chunk(context, delta)
340
+ continue
341
+
342
+ # TOOL_CALL_CHUNK 事件
343
+ if event.event == EventType.TOOL_CALL_CHUNK:
344
+ tool_id = event.data.get("id", "")
345
+ tool_name = event.data.get("name", "")
346
+ args_delta = event.data.get("args_delta", "")
347
+
348
+ delta = {}
349
+
350
+ # 首次见到这个工具调用
351
+ if tool_id and tool_id not in tool_call_states:
352
+ tool_call_index += 1
353
+ tool_call_states[tool_id] = {
354
+ "started": True,
355
+ "index": tool_call_index,
356
+ }
357
+ has_tool_calls = True
358
+
359
+ # 发送工具调用开始(包含 id, name)
360
+ delta["tool_calls"] = [{
361
+ "index": tool_call_index,
362
+ "id": tool_id,
363
+ "type": "function",
364
+ "function": {"name": tool_name, "arguments": ""},
365
+ }]
366
+ yield self._build_chunk(context, delta)
367
+ delta = {}
368
+
369
+ # 发送参数增量
370
+ if args_delta:
371
+ current_index = tool_call_states.get(tool_id, {}).get(
372
+ "index", tool_call_index
373
+ )
374
+ delta["tool_calls"] = [{
375
+ "index": current_index,
376
+ "function": {"arguments": args_delta},
377
+ }]
378
+
379
+ # 应用 addition
380
+ if event.addition:
381
+ delta = self._apply_addition(
382
+ delta,
383
+ event.addition,
384
+ event.addition_merge_options,
385
+ )
386
+
387
+ yield self._build_chunk(context, delta)
388
+ continue
389
+
390
+ # TOOL_RESULT 事件:OpenAI 协议通常不在流中输出工具结果
391
+ if event.event == EventType.TOOL_RESULT:
392
+ continue
393
+
394
+ # TOOL_RESULT_CHUNK 事件:OpenAI 协议不支持流式工具输出
395
+ if event.event == EventType.TOOL_RESULT_CHUNK:
396
+ continue
397
+
398
+ # HITL 事件:OpenAI 协议不支持
399
+ if event.event == EventType.HITL:
400
+ continue
401
+
402
+ # 其他事件忽略
403
+ # (ERROR, STATE, CUSTOM 等不直接映射到 OpenAI 格式)
404
+
405
+ # 流结束后发送 finish_reason 和 [DONE]
406
+ if has_tool_calls:
407
+ yield self._build_chunk(context, {}, finish_reason="tool_calls")
408
+ elif has_text:
409
+ yield self._build_chunk(context, {}, finish_reason="stop")
410
+ yield "data: [DONE]\n\n"
411
+
412
+ def _build_chunk(
413
+ self,
414
+ context: Dict[str, Any],
415
+ delta: Dict[str, Any],
416
+ finish_reason: Optional[str] = None,
417
+ ) -> str:
418
+ """构建 OpenAI 流式响应块
419
+
420
+ Args:
421
+ context: 上下文信息
422
+ delta: delta 数据
423
+ finish_reason: 结束原因
424
+
425
+ Returns:
426
+ SSE 格式的字符串
427
+ """
428
+ chunk = {
429
+ "id": context.get(
430
+ "response_id", f"chatcmpl-{uuid.uuid4().hex[:8]}"
431
+ ),
432
+ "object": "chat.completion.chunk",
433
+ "created": context.get("created", int(time.time())),
434
+ "model": context.get("model", "agentrun"),
435
+ "choices": [{
436
+ "index": 0,
437
+ "delta": delta,
438
+ "finish_reason": finish_reason,
439
+ }],
440
+ }
441
+ json_str = json.dumps(chunk, ensure_ascii=False)
442
+ return f"data: {json_str}\n\n"
443
+
444
+ def _format_non_stream(
445
+ self,
446
+ events: List[AgentEvent],
447
+ context: Dict[str, Any],
448
+ ) -> Dict[str, Any]:
449
+ """将 AgentEvent 列表转换为 OpenAI 非流式响应
450
+
451
+ 自动追踪工具调用状态。
452
+
453
+ Args:
454
+ events: AgentEvent 列表
455
+ context: 上下文信息
456
+
457
+ Returns:
458
+ OpenAI 格式的响应字典
459
+ """
460
+ content_parts: List[str] = []
461
+ # 工具调用状态:{tool_id: {id, name, arguments}}
462
+ tool_call_map: Dict[str, Dict[str, Any]] = {}
463
+ has_tool_calls = False
464
+
465
+ for event in events:
466
+ if event.event == EventType.TEXT:
467
+ content_parts.append(event.data.get("delta", ""))
468
+
469
+ elif event.event == EventType.TOOL_CALL_CHUNK:
470
+ tool_id = event.data.get("id", "")
471
+ tool_name = event.data.get("name", "")
472
+ args_delta = event.data.get("args_delta", "")
473
+
474
+ if tool_id:
475
+ if tool_id not in tool_call_map:
476
+ tool_call_map[tool_id] = {
477
+ "id": tool_id,
478
+ "type": "function",
479
+ "function": {"name": tool_name, "arguments": ""},
480
+ }
481
+ has_tool_calls = True
482
+
483
+ if args_delta:
484
+ tool_call_map[tool_id]["function"][
485
+ "arguments"
486
+ ] += args_delta
487
+
488
+ # 构建响应
489
+ content = "".join(content_parts) if content_parts else None
490
+ finish_reason = "tool_calls" if has_tool_calls else "stop"
491
+
492
+ message: Dict[str, Any] = {
493
+ "role": "assistant",
494
+ "content": content,
495
+ }
496
+
497
+ if tool_call_map:
498
+ message["tool_calls"] = list(tool_call_map.values())
499
+
500
+ response = {
501
+ "id": context.get(
502
+ "response_id", f"chatcmpl-{uuid.uuid4().hex[:12]}"
503
+ ),
504
+ "object": "chat.completion",
505
+ "created": context.get("created", int(time.time())),
506
+ "model": context.get("model", "agentrun"),
507
+ "choices": [{
508
+ "index": 0,
509
+ "message": message,
510
+ "finish_reason": finish_reason,
511
+ }],
512
+ }
513
+
514
+ return response
515
+
516
+ def _apply_addition(
517
+ self,
518
+ delta: Dict[str, Any],
519
+ addition: Optional[Dict[str, Any]],
520
+ merge_options: Optional[MergeOptions] = None,
521
+ ) -> Dict[str, Any]:
522
+ """应用 addition 字段
523
+
524
+ Args:
525
+ delta: 原始 delta 数据
526
+ addition: 附加字段
527
+ merge_options: 合并选项,透传给 utils.helper.merge
528
+
529
+ Returns:
530
+ 合并后的 delta 数据
531
+ """
532
+ if not addition:
533
+ return delta
534
+
535
+ return merge(delta, addition, **(merge_options or {}))
@@ -0,0 +1,140 @@
1
+ """协议抽象层 / Protocol Abstraction Layer
2
+
3
+ 定义协议接口,支持多种协议格式(OpenAI, AG-UI 等)。
4
+
5
+ 基于 Router 的设计:
6
+ - 每个协议提供自己的 FastAPI Router
7
+ - Server 负责挂载 Router 并管理路由前缀
8
+ - 协议完全自治,无需向 Server 声明接口
9
+ """
10
+
11
+ from abc import ABC, abstractmethod
12
+ from typing import Any, Awaitable, Callable, Dict, TYPE_CHECKING, Union
13
+
14
+ from .model import AgentRequest, AgentReturnType
15
+
16
+ if TYPE_CHECKING:
17
+ from fastapi import APIRouter, Request
18
+
19
+ from .invoker import AgentInvoker
20
+
21
+
22
+ # ============================================================================
23
+ # 协议处理器基类
24
+ # ============================================================================
25
+
26
+
27
+ class ProtocolHandler(ABC):
28
+ """协议处理器基类 / Protocol Handler Base Class
29
+
30
+ 基于 Router 的设计:
31
+ 协议通过 as_fastapi_router() 方法提供完整的路由定义,
32
+ 包括所有端点、请求处理、响应格式化等。
33
+
34
+ Server 只需挂载 Router 并管理路由前缀,无需了解协议细节。
35
+
36
+ Example:
37
+ >>> class MyProtocolHandler(ProtocolHandler):
38
+ ... name = "my_protocol"
39
+ ...
40
+ ... def as_fastapi_router(self, agent_invoker):
41
+ ... router = APIRouter()
42
+ ...
43
+ ... @router.post("/run")
44
+ ... async def run(request: Request):
45
+ ... ...
46
+ ...
47
+ ... return router
48
+ """
49
+
50
+ name: str
51
+
52
+ @abstractmethod
53
+ def as_fastapi_router(self, agent_invoker: "AgentInvoker") -> "APIRouter":
54
+ """将协议转换为 FastAPI Router
55
+
56
+ 协议自己决定:
57
+ - 有哪些端点
58
+ - 端点的路径
59
+ - HTTP 方法
60
+ - 请求/响应处理
61
+
62
+ Args:
63
+ agent_invoker: Agent 调用器,用于执行用户的 invoke_agent
64
+
65
+ Returns:
66
+ APIRouter: FastAPI 路由器,包含该协议的所有端点
67
+ """
68
+ pass
69
+
70
+ def get_prefix(self) -> str:
71
+ """获取协议建议的路由前缀
72
+
73
+ Server 会优先使用用户指定的前缀,如果没有指定则使用此建议值。
74
+
75
+ Returns:
76
+ str: 建议的前缀,如 "/v1" 或 ""
77
+
78
+ Example:
79
+ - OpenAI 协议: "/openai/v1"
80
+ - AG-UI 协议: "/agui/v1"
81
+ - 无前缀: ""
82
+ """
83
+ return ""
84
+
85
+
86
+ class BaseProtocolHandler(ProtocolHandler):
87
+ """协议处理器扩展基类 / Extended Protocol Handler Base Class
88
+
89
+ 提供通用的请求解析和响应格式化逻辑。
90
+ 子类需要实现具体的协议转换。
91
+ """
92
+
93
+ async def parse_request(
94
+ self,
95
+ request: "Request",
96
+ request_data: Dict[str, Any],
97
+ ) -> tuple[AgentRequest, Dict[str, Any]]:
98
+ """解析 HTTP 请求为 AgentRequest
99
+
100
+ 子类应该重写此方法来实现协议特定的解析逻辑。
101
+
102
+ Args:
103
+ request: FastAPI Request 对象
104
+ request_data: 请求体 JSON 数据
105
+
106
+ Returns:
107
+ tuple: (AgentRequest, context)
108
+ - AgentRequest: 标准化的请求对象
109
+ - context: 协议特定的上下文信息
110
+ """
111
+ raise NotImplementedError("Subclass must implement parse_request")
112
+
113
+ def _is_iterator(self, obj: Any) -> bool:
114
+ """检查对象是否是迭代器
115
+
116
+ Args:
117
+ obj: 要检查的对象
118
+
119
+ Returns:
120
+ bool: 是否是迭代器
121
+ """
122
+ return (
123
+ hasattr(obj, "__iter__")
124
+ and not isinstance(obj, (str, bytes, dict, list))
125
+ ) or hasattr(obj, "__aiter__")
126
+
127
+
128
+ # ============================================================================
129
+ # Handler 类型定义
130
+ # ============================================================================
131
+
132
+
133
+ # 同步 handler: 返回 AgentReturnType
134
+ SyncInvokeAgentHandler = Callable[[AgentRequest], AgentReturnType]
135
+
136
+ # 异步 handler: 返回 Awaitable[AgentReturnType]
137
+ AsyncInvokeAgentHandler = Callable[[AgentRequest], Awaitable[AgentReturnType]]
138
+
139
+ # 通用 handler: 同步或异步
140
+ InvokeAgentHandler = Union[SyncInvokeAgentHandler, AsyncInvokeAgentHandler]