agentrun-inner-test 0.0.62__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.
Potentially problematic release.
This version of agentrun-inner-test might be problematic. Click here for more details.
- agentrun/__init__.py +358 -0
- agentrun/agent_runtime/__client_async_template.py +466 -0
- agentrun/agent_runtime/__endpoint_async_template.py +345 -0
- agentrun/agent_runtime/__init__.py +53 -0
- agentrun/agent_runtime/__runtime_async_template.py +477 -0
- agentrun/agent_runtime/api/__data_async_template.py +58 -0
- agentrun/agent_runtime/api/__init__.py +6 -0
- agentrun/agent_runtime/api/control.py +1362 -0
- agentrun/agent_runtime/api/data.py +98 -0
- agentrun/agent_runtime/client.py +868 -0
- agentrun/agent_runtime/endpoint.py +649 -0
- agentrun/agent_runtime/model.py +362 -0
- agentrun/agent_runtime/runtime.py +904 -0
- agentrun/credential/__client_async_template.py +177 -0
- agentrun/credential/__credential_async_template.py +216 -0
- agentrun/credential/__init__.py +28 -0
- agentrun/credential/api/__init__.py +5 -0
- agentrun/credential/api/control.py +606 -0
- agentrun/credential/client.py +319 -0
- agentrun/credential/credential.py +381 -0
- agentrun/credential/model.py +248 -0
- agentrun/integration/__init__.py +21 -0
- agentrun/integration/agentscope/__init__.py +13 -0
- agentrun/integration/agentscope/adapter.py +17 -0
- agentrun/integration/agentscope/builtin.py +88 -0
- agentrun/integration/agentscope/message_adapter.py +185 -0
- agentrun/integration/agentscope/model_adapter.py +60 -0
- agentrun/integration/agentscope/tool_adapter.py +59 -0
- agentrun/integration/builtin/__init__.py +18 -0
- agentrun/integration/builtin/knowledgebase.py +137 -0
- agentrun/integration/builtin/model.py +93 -0
- agentrun/integration/builtin/sandbox.py +1234 -0
- agentrun/integration/builtin/toolset.py +47 -0
- agentrun/integration/crewai/__init__.py +13 -0
- agentrun/integration/crewai/adapter.py +9 -0
- agentrun/integration/crewai/builtin.py +88 -0
- agentrun/integration/crewai/model_adapter.py +31 -0
- agentrun/integration/crewai/tool_adapter.py +26 -0
- agentrun/integration/google_adk/__init__.py +13 -0
- agentrun/integration/google_adk/adapter.py +15 -0
- agentrun/integration/google_adk/builtin.py +88 -0
- agentrun/integration/google_adk/message_adapter.py +144 -0
- agentrun/integration/google_adk/model_adapter.py +46 -0
- agentrun/integration/google_adk/tool_adapter.py +235 -0
- agentrun/integration/langchain/__init__.py +31 -0
- agentrun/integration/langchain/adapter.py +15 -0
- agentrun/integration/langchain/builtin.py +94 -0
- agentrun/integration/langchain/message_adapter.py +141 -0
- agentrun/integration/langchain/model_adapter.py +37 -0
- agentrun/integration/langchain/tool_adapter.py +50 -0
- agentrun/integration/langgraph/__init__.py +36 -0
- agentrun/integration/langgraph/adapter.py +20 -0
- agentrun/integration/langgraph/agent_converter.py +1073 -0
- agentrun/integration/langgraph/builtin.py +88 -0
- agentrun/integration/pydantic_ai/__init__.py +13 -0
- agentrun/integration/pydantic_ai/adapter.py +13 -0
- agentrun/integration/pydantic_ai/builtin.py +88 -0
- agentrun/integration/pydantic_ai/model_adapter.py +44 -0
- agentrun/integration/pydantic_ai/tool_adapter.py +19 -0
- agentrun/integration/utils/__init__.py +112 -0
- agentrun/integration/utils/adapter.py +560 -0
- agentrun/integration/utils/canonical.py +164 -0
- agentrun/integration/utils/converter.py +134 -0
- agentrun/integration/utils/model.py +110 -0
- agentrun/integration/utils/tool.py +1759 -0
- agentrun/knowledgebase/__client_async_template.py +173 -0
- agentrun/knowledgebase/__init__.py +53 -0
- agentrun/knowledgebase/__knowledgebase_async_template.py +438 -0
- agentrun/knowledgebase/api/__data_async_template.py +414 -0
- agentrun/knowledgebase/api/__init__.py +19 -0
- agentrun/knowledgebase/api/control.py +606 -0
- agentrun/knowledgebase/api/data.py +624 -0
- agentrun/knowledgebase/client.py +311 -0
- agentrun/knowledgebase/knowledgebase.py +748 -0
- agentrun/knowledgebase/model.py +270 -0
- agentrun/memory_collection/__client_async_template.py +178 -0
- agentrun/memory_collection/__init__.py +37 -0
- agentrun/memory_collection/__memory_collection_async_template.py +457 -0
- agentrun/memory_collection/api/__init__.py +5 -0
- agentrun/memory_collection/api/control.py +610 -0
- agentrun/memory_collection/client.py +323 -0
- agentrun/memory_collection/memory_collection.py +844 -0
- agentrun/memory_collection/model.py +162 -0
- agentrun/model/__client_async_template.py +357 -0
- agentrun/model/__init__.py +57 -0
- agentrun/model/__model_proxy_async_template.py +270 -0
- agentrun/model/__model_service_async_template.py +267 -0
- agentrun/model/api/__init__.py +6 -0
- agentrun/model/api/control.py +1173 -0
- agentrun/model/api/data.py +196 -0
- agentrun/model/client.py +674 -0
- agentrun/model/model.py +235 -0
- agentrun/model/model_proxy.py +439 -0
- agentrun/model/model_service.py +438 -0
- agentrun/sandbox/__aio_sandbox_async_template.py +523 -0
- agentrun/sandbox/__browser_sandbox_async_template.py +110 -0
- agentrun/sandbox/__client_async_template.py +491 -0
- agentrun/sandbox/__code_interpreter_sandbox_async_template.py +463 -0
- agentrun/sandbox/__init__.py +69 -0
- agentrun/sandbox/__sandbox_async_template.py +463 -0
- agentrun/sandbox/__template_async_template.py +152 -0
- agentrun/sandbox/aio_sandbox.py +912 -0
- agentrun/sandbox/api/__aio_data_async_template.py +335 -0
- agentrun/sandbox/api/__browser_data_async_template.py +140 -0
- agentrun/sandbox/api/__code_interpreter_data_async_template.py +206 -0
- agentrun/sandbox/api/__init__.py +19 -0
- agentrun/sandbox/api/__sandbox_data_async_template.py +107 -0
- agentrun/sandbox/api/aio_data.py +551 -0
- agentrun/sandbox/api/browser_data.py +172 -0
- agentrun/sandbox/api/code_interpreter_data.py +396 -0
- agentrun/sandbox/api/control.py +1051 -0
- agentrun/sandbox/api/playwright_async.py +492 -0
- agentrun/sandbox/api/playwright_sync.py +492 -0
- agentrun/sandbox/api/sandbox_data.py +154 -0
- agentrun/sandbox/browser_sandbox.py +185 -0
- agentrun/sandbox/client.py +925 -0
- agentrun/sandbox/code_interpreter_sandbox.py +823 -0
- agentrun/sandbox/model.py +384 -0
- agentrun/sandbox/sandbox.py +848 -0
- agentrun/sandbox/template.py +217 -0
- agentrun/server/__init__.py +191 -0
- agentrun/server/agui_normalizer.py +180 -0
- agentrun/server/agui_protocol.py +797 -0
- agentrun/server/invoker.py +309 -0
- agentrun/server/model.py +427 -0
- agentrun/server/openai_protocol.py +535 -0
- agentrun/server/protocol.py +140 -0
- agentrun/server/server.py +208 -0
- agentrun/toolset/__client_async_template.py +62 -0
- agentrun/toolset/__init__.py +51 -0
- agentrun/toolset/__toolset_async_template.py +204 -0
- agentrun/toolset/api/__init__.py +17 -0
- agentrun/toolset/api/control.py +262 -0
- agentrun/toolset/api/mcp.py +100 -0
- agentrun/toolset/api/openapi.py +1251 -0
- agentrun/toolset/client.py +102 -0
- agentrun/toolset/model.py +321 -0
- agentrun/toolset/toolset.py +271 -0
- agentrun/utils/__data_api_async_template.py +721 -0
- agentrun/utils/__init__.py +5 -0
- agentrun/utils/__resource_async_template.py +158 -0
- agentrun/utils/config.py +270 -0
- agentrun/utils/control_api.py +105 -0
- agentrun/utils/data_api.py +1121 -0
- agentrun/utils/exception.py +151 -0
- agentrun/utils/helper.py +108 -0
- agentrun/utils/log.py +77 -0
- agentrun/utils/model.py +168 -0
- agentrun/utils/resource.py +291 -0
- agentrun_inner_test-0.0.62.dist-info/METADATA +265 -0
- agentrun_inner_test-0.0.62.dist-info/RECORD +154 -0
- agentrun_inner_test-0.0.62.dist-info/WHEEL +5 -0
- agentrun_inner_test-0.0.62.dist-info/licenses/LICENSE +201 -0
- agentrun_inner_test-0.0.62.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]
|