Skip to content

Commit 613723b

Browse files
OhYeeCopilot
andauthored
Fix agui (#23)
* fix(credential): correct typo in access key secret parameter name fix(agui_protocol): optimize import statements and event handling logic - Move imports to local scope to avoid unnecessary dependencies - Simplify conditional expressions and method calls - Improve type hinting for better code clarity 修复凭据模型中的访问密钥参数拼写错误 优化 AGUI 协议处理器的导入语句和事件处理逻辑: - 将部分导入移至函数内部以减少不必要的依赖 - 简化条件表达式与方法调用 - 改进类型提示以增强代码可读性 Change-Id: I1f4b89fa0a0ceeed09bcf1e6bb03370dfd1547de Signed-off-by: OhYee <oyohyee@oyohyee.com> * fix(agui_protocol): import run event classes directly the runstartedevent and runfinishedevent classes are now imported directly from ag_ui.core to ensure proper event handling in the sse stream. these events are essential for signaling the start and completion of runs within the agui protocol, improving reliability and maintainability of the streaming mechanism. 修复了 agui 协议中运行事件类的导入问题,确保 SSE 流能够正确处理运行开始和结束事件。此举增强了流机制的可靠性和可维护性。 Change-Id: Ied78843c9533c640a4706b379c8bd9109acc3b0b Signed-off-by: OhYee <oyohyee@oyohyee.com> * refactor(core): implement lazy loading for server module to handle optional dependencies This change introduces a lazy loading mechanism for the server module to avoid import errors when optional dependencies are not installed. The server-related exports are now imported on-demand through __getattr__, with clear error messages when required optional dependencies are missing. The implementation includes: - TYPE_CHECKING import for type hints during development - Delayed imports wrapped in TYPE_CHECKING blocks - __getattr__ function to handle runtime imports - Clear error messages directing users to install optional dependencies - Server exports defined in _SERVER_EXPORTS set for tracking This resolves issues where the package would fail to import when server optional dependencies were not available. refactor(core): 实现服务器模块的延迟加载以处理可选依赖 此更改引入了服务器模块的延迟加载机制,以避免在未安装可选依赖项时出现导入错误。现在通过 __getattr__ 按需导入服务器相关导出,并在缺少必需的可选依赖项时提供清晰的错误消息。 实现包括: - 用于开发期间类型提示的 TYPE_CHECKING 导入 - 包装在 TYPE_CHECKING 块中的延迟导入 - 用于处理运行时导入的 __getattr__ 函数 - 指向用户安装可选依赖项的清晰错误消息 - 定义在 _SERVER_EXPORTS 集合中用于跟踪的服务器导出 这解决了在服务器可选依赖项不可用时包导入失败的问题。 Change-Id: I9d8f7dd8e5c6d34dfd0619714ead5aea22105293 Signed-off-by: OhYee <oyohyee@oyohyee.com> * refactor(core): use generic package mapping for optional dependency checks (#25) * Initial plan * fix(core): add 'ag_ui' to optional dependency error check Add "ag_ui" to the error message checking logic in __getattr__ to properly handle import errors when ag-ui-protocol is not installed. This ensures users get helpful error messages when trying to use AGUIProtocolHandler or other AG-UI related components without the server optional dependencies installed. Addresses feedback from PR #23 review comment. Co-authored-by: OhYee <13498329+OhYee@users.noreply.github.com> * refactor(core): use generic package mapping for optional dependency checks Refactor the import error handling to use a more maintainable and scalable approach with a package mapping structure instead of hardcoded string checks. Changes: - Added _OPTIONAL_PACKAGES list with (import_name, install_name) tuples - Refactored __getattr__ to iterate through the package mapping - Now easier to add new optional dependencies in the future - All three server optional dependencies (fastapi, uvicorn, ag_ui) registered This makes the codebase more maintainable when adding new optional dependencies in the future. Addresses feedback from @OhYee on PR #23. Co-authored-by: OhYee <13498329+OhYee@users.noreply.github.com> * fix(core): restore specificity in error message and add clarifying comment Address code review feedback: - Restored "server" context in error message for better clarity - Added comment clarifying that all entries are for server-related dependencies - Maintains generic approach while preserving helpful context for users This ensures users get clear, specific error messages while keeping the code maintainable for future extensions. Co-authored-by: OhYee <13498329+OhYee@users.noreply.github.com> * refactor(core): use constant for server install command to reduce duplication Extract the repeated install command string into a constant _SERVER_INSTALL_CMD to make the code more maintainable and easier to update in the future. This addresses code review feedback about reducing duplication in the package mapping structure. Co-authored-by: OhYee <13498329+OhYee@users.noreply.github.com> * refactor(core): group packages by install command in _OPTIONAL_PACKAGES Refactor _OPTIONAL_PACKAGES from a list of tuples to a dictionary that groups packages by their installation command. This provides better organization when multiple packages share the same installation command. Changes: - Changed from list of (package, install_cmd) tuples to dict of {install_cmd: [packages]} - Removed _SERVER_INSTALL_CMD constant (now a dict key) - Updated iteration logic to handle nested structure - All three server packages (fastapi, uvicorn, ag_ui) grouped together Benefits: - Clearer relationship: install command -> packages it provides - More compact when packages share the same install command - Easier to see all packages for a given installation - Scalable for future optional dependency groups Addresses feedback from @OhYee about merging packages with the same command. Co-authored-by: OhYee <13498329+OhYee@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: OhYee <13498329+OhYee@users.noreply.github.com> --------- Signed-off-by: OhYee <oyohyee@oyohyee.com> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: OhYee <13498329+OhYee@users.noreply.github.com>
1 parent 2582fa6 commit 613723b

File tree

4 files changed

+180
-56
lines changed

4 files changed

+180
-56
lines changed

agentrun/__init__.py

Lines changed: 139 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
- Integration: 框架集成 / Framework integration
1717
"""
1818

19+
from typing import TYPE_CHECKING
20+
1921
__version__ = "0.0.8"
2022

2123
# Agent Runtime
@@ -87,27 +89,50 @@
8789
SandboxClient,
8890
Template,
8991
)
90-
# Server
91-
from agentrun.server import (
92-
AgentRequest,
93-
AgentResult,
94-
AgentRunServer,
95-
AsyncInvokeAgentHandler,
96-
InvokeAgentHandler,
97-
Message,
98-
MessageRole,
99-
OpenAIProtocolHandler,
100-
ProtocolHandler,
101-
SyncInvokeAgentHandler,
102-
)
10392
# ToolSet
10493
from agentrun.toolset import ToolSet, ToolSetClient
94+
from agentrun.utils.config import Config
10595
from agentrun.utils.exception import (
10696
ResourceAlreadyExistError,
10797
ResourceNotExistError,
10898
)
10999
from agentrun.utils.model import Status
110100

101+
# Server - 延迟导入以避免可选依赖问题
102+
# Type hints for IDE and type checkers
103+
if TYPE_CHECKING:
104+
from agentrun.server import (
105+
AgentEvent,
106+
AgentEventItem,
107+
AgentRequest,
108+
AgentResult,
109+
AgentResultItem,
110+
AgentReturnType,
111+
AgentRunServer,
112+
AguiEventNormalizer,
113+
AGUIProtocolConfig,
114+
AGUIProtocolHandler,
115+
AsyncAgentEventGenerator,
116+
AsyncAgentResultGenerator,
117+
AsyncInvokeAgentHandler,
118+
BaseProtocolHandler,
119+
EventType,
120+
InvokeAgentHandler,
121+
MergeOptions,
122+
Message,
123+
MessageRole,
124+
OpenAIProtocolConfig,
125+
OpenAIProtocolHandler,
126+
ProtocolConfig,
127+
ProtocolHandler,
128+
ServerConfig,
129+
SyncAgentEventGenerator,
130+
SyncAgentResultGenerator,
131+
SyncInvokeAgentHandler,
132+
Tool,
133+
ToolCall,
134+
)
135+
111136
__all__ = [
112137
######## Agent Runtime ########
113138
# base
@@ -185,22 +210,116 @@
185210
######## ToolSet ########
186211
"ToolSetClient",
187212
"ToolSet",
188-
######## Server ########
213+
######## Server (延迟加载) ########
214+
# Server
189215
"AgentRunServer",
216+
# Config
217+
"ServerConfig",
218+
"ProtocolConfig",
219+
"OpenAIProtocolConfig",
220+
"AGUIProtocolConfig",
221+
# Request/Response Models
190222
"AgentRequest",
191-
"AgentResponse",
223+
"AgentEvent",
192224
"AgentResult",
193-
"AgentStreamResponse",
225+
"Message",
226+
"MessageRole",
227+
"Tool",
228+
"ToolCall",
229+
# Event Types
230+
"EventType",
231+
# Type Aliases
232+
"AgentEventItem",
233+
"AgentResultItem",
234+
"AgentReturnType",
235+
"SyncAgentEventGenerator",
236+
"SyncAgentResultGenerator",
237+
"AsyncAgentEventGenerator",
238+
"AsyncAgentResultGenerator",
194239
"InvokeAgentHandler",
195240
"AsyncInvokeAgentHandler",
196241
"SyncInvokeAgentHandler",
197-
"Message",
198-
"MessageRole",
242+
# Protocol Base
199243
"ProtocolHandler",
244+
"BaseProtocolHandler",
245+
# Protocol - OpenAI
200246
"OpenAIProtocolHandler",
201-
"AgentStreamIterator",
247+
# Protocol - AG-UI
248+
"AGUIProtocolHandler",
249+
# Event Normalizer
250+
"AguiEventNormalizer",
251+
# Helpers
252+
"MergeOptions",
202253
######## Others ########
203-
"Status",
204254
"ResourceNotExistError",
205255
"ResourceAlreadyExistError",
256+
"Config",
206257
]
258+
259+
# Server 模块的所有导出
260+
_SERVER_EXPORTS = {
261+
"AgentRunServer",
262+
"ServerConfig",
263+
"ProtocolConfig",
264+
"OpenAIProtocolConfig",
265+
"AGUIProtocolConfig",
266+
"AgentRequest",
267+
"AgentEvent",
268+
"AgentResult",
269+
"Message",
270+
"MessageRole",
271+
"Tool",
272+
"ToolCall",
273+
"EventType",
274+
"AgentEventItem",
275+
"AgentResultItem",
276+
"AgentReturnType",
277+
"SyncAgentEventGenerator",
278+
"SyncAgentResultGenerator",
279+
"AsyncAgentEventGenerator",
280+
"AsyncAgentResultGenerator",
281+
"InvokeAgentHandler",
282+
"AsyncInvokeAgentHandler",
283+
"SyncInvokeAgentHandler",
284+
"ProtocolHandler",
285+
"BaseProtocolHandler",
286+
"OpenAIProtocolHandler",
287+
"AGUIProtocolHandler",
288+
"AguiEventNormalizer",
289+
"MergeOptions",
290+
}
291+
292+
# 可选依赖包映射:安装命令 -> 导入错误的包名列表
293+
# Optional dependency mapping: installation command -> list of import error package names
294+
# 将使用相同安装命令的包合并到一起 / Group packages with the same installation command
295+
_OPTIONAL_PACKAGES = {
296+
"agentrun-sdk[server]": ["fastapi", "uvicorn", "ag_ui"],
297+
}
298+
299+
300+
def __getattr__(name: str):
301+
"""延迟加载 server 模块的导出,避免可选依赖导致导入失败
302+
303+
当用户访问 server 相关的类时,才尝试导入 server 模块。
304+
如果 server 可选依赖未安装,会抛出清晰的错误提示。
305+
"""
306+
if name in _SERVER_EXPORTS:
307+
try:
308+
from agentrun import server
309+
310+
return getattr(server, name)
311+
except ImportError as e:
312+
# 检查是否是缺少可选依赖导致的错误
313+
error_str = str(e)
314+
for install_cmd, package_names in _OPTIONAL_PACKAGES.items():
315+
for package_name in package_names:
316+
if package_name in error_str:
317+
raise ImportError(
318+
f"'{name}' requires the 'server' optional dependencies. "
319+
f"Install with: pip install {install_cmd}\n"
320+
f"Original error: {e}"
321+
) from e
322+
# 其他导入错误继续抛出
323+
raise
324+
325+
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")

agentrun/credential/model.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ def outbound_tool_ak_sk(
134134
cls,
135135
provider: str,
136136
access_key_id: str,
137-
access_key_secred: str,
137+
access_key_secret: str,
138138
account_id: str,
139139
):
140140
"""配置访问第三方工具的 ak/sk 凭证"""
@@ -148,7 +148,7 @@ def outbound_tool_ak_sk(
148148
"accountId": account_id,
149149
},
150150
},
151-
credential_secret=access_key_secred,
151+
credential_secret=access_key_secret,
152152
)
153153

154154
@classmethod

agentrun/model/model.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ class ProxyConfigTokenRateLimiter(BaseModel):
131131

132132
class ProxyConfigAIGuardrailConfig(BaseModel):
133133
"""AI 防护配置"""
134+
134135
check_request: Optional[bool] = None
135136
check_response: Optional[bool] = None
136137

agentrun/server/agui_protocol.py

Lines changed: 38 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -19,36 +19,12 @@
1919
)
2020
import uuid
2121

22-
from ag_ui.core import AssistantMessage
23-
from ag_ui.core import CustomEvent as AguiCustomEvent
24-
from ag_ui.core import EventType as AguiEventType
25-
from ag_ui.core import Message as AguiMessage
26-
from ag_ui.core import MessagesSnapshotEvent
27-
from ag_ui.core import RawEvent as AguiRawEvent
28-
from ag_ui.core import (
29-
RunErrorEvent,
30-
RunFinishedEvent,
31-
RunStartedEvent,
32-
StateDeltaEvent,
33-
StateSnapshotEvent,
34-
StepFinishedEvent,
35-
StepStartedEvent,
36-
SystemMessage,
37-
TextMessageContentEvent,
38-
TextMessageEndEvent,
39-
TextMessageStartEvent,
40-
)
41-
from ag_ui.core import Tool as AguiTool
42-
from ag_ui.core import ToolCall as AguiToolCall
43-
from ag_ui.core import (
44-
ToolCallArgsEvent,
45-
ToolCallEndEvent,
46-
ToolCallResultEvent,
47-
ToolCallStartEvent,
48-
)
49-
from ag_ui.core import ToolMessage as AguiToolMessage
50-
from ag_ui.core import UserMessage
51-
from ag_ui.encoder import EventEncoder
22+
if TYPE_CHECKING:
23+
from ag_ui.core import (
24+
Message as AguiMessage,
25+
)
26+
from ag_ui.encoder import EventEncoder
27+
5228
from fastapi import APIRouter, Request
5329
from fastapi.responses import StreamingResponse
5430
import pydash
@@ -101,16 +77,20 @@ class StreamStateMachine:
10177
run_errored: bool = False
10278

10379
def end_all_tools(
104-
self, encoder: EventEncoder, exclude: Optional[str] = None
80+
self, encoder: "EventEncoder", exclude: Optional[str] = None
10581
) -> Iterator[str]:
82+
from ag_ui.core import ToolCallEndEvent
83+
10684
for tool_id, state in self.tool_call_states.items():
10785
if exclude and tool_id == exclude:
10886
continue
10987
if state.started and not state.ended:
11088
yield encoder.encode(ToolCallEndEvent(tool_call_id=tool_id))
11189
state.ended = True
11290

113-
def ensure_text_started(self, encoder: EventEncoder) -> Iterator[str]:
91+
def ensure_text_started(self, encoder: "EventEncoder") -> Iterator[str]:
92+
from ag_ui.core import TextMessageStartEvent
93+
11494
if not self.text.started or self.text.ended:
11595
if self.text.ended:
11696
self.text = TextState()
@@ -123,7 +103,9 @@ def ensure_text_started(self, encoder: EventEncoder) -> Iterator[str]:
123103
self.text.started = True
124104
self.text.ended = False
125105

126-
def end_text_if_open(self, encoder: EventEncoder) -> Iterator[str]:
106+
def end_text_if_open(self, encoder: "EventEncoder") -> Iterator[str]:
107+
from ag_ui.core import TextMessageEndEvent
108+
127109
if self.text.started and not self.text.ended:
128110
yield encoder.encode(
129111
TextMessageEndEvent(message_id=self.text.message_id)
@@ -168,6 +150,8 @@ class AGUIProtocolHandler(BaseProtocolHandler):
168150
name = "ag-ui"
169151

170152
def __init__(self, config: Optional[ServerConfig] = None):
153+
from ag_ui.encoder import EventEncoder
154+
171155
self._config = config.agui if config else None
172156
self._encoder = EventEncoder()
173157

@@ -363,6 +347,8 @@ async def _format_stream(
363347
Yields:
364348
SSE 格式的字符串
365349
"""
350+
from ag_ui.core import RunFinishedEvent, RunStartedEvent
351+
366352
state = StreamStateMachine()
367353

368354
# 发送 RUN_STARTED
@@ -420,6 +406,18 @@ def _process_event_with_boundaries(
420406
"""处理事件并注入边界事件"""
421407
import json
422408

409+
from ag_ui.core import CustomEvent as AguiCustomEvent
410+
from ag_ui.core import (
411+
RunErrorEvent,
412+
StateDeltaEvent,
413+
StateSnapshotEvent,
414+
TextMessageContentEvent,
415+
ToolCallArgsEvent,
416+
ToolCallEndEvent,
417+
ToolCallResultEvent,
418+
ToolCallStartEvent,
419+
)
420+
423421
# RAW 事件直接透传
424422
if event.event == EventType.RAW:
425423
raw_data = event.data.get("raw", "")
@@ -703,7 +701,7 @@ def _process_event_with_boundaries(
703701

704702
def _convert_messages_for_snapshot(
705703
self, messages: List[Dict[str, Any]]
706-
) -> List[AguiMessage]:
704+
) -> List["AguiMessage"]:
707705
"""将消息列表转换为 ag-ui-protocol 格式
708706
709707
Args:
@@ -712,6 +710,10 @@ def _convert_messages_for_snapshot(
712710
Returns:
713711
ag-ui-protocol 消息列表
714712
"""
713+
from ag_ui.core import AssistantMessage, SystemMessage
714+
from ag_ui.core import ToolMessage as AguiToolMessage
715+
from ag_ui.core import UserMessage
716+
715717
result = []
716718
for msg in messages:
717719
if not isinstance(msg, dict):
@@ -779,6 +781,8 @@ async def _error_stream(self, message: str) -> AsyncIterator[str]:
779781
Yields:
780782
SSE 格式的错误事件
781783
"""
784+
from ag_ui.core import RunErrorEvent, RunStartedEvent
785+
782786
thread_id = str(uuid.uuid4())
783787
run_id = str(uuid.uuid4())
784788

0 commit comments

Comments
 (0)