aury-agent 0.0.4__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.
- aury/__init__.py +2 -0
- aury/agents/__init__.py +55 -0
- aury/agents/a2a/__init__.py +168 -0
- aury/agents/backends/__init__.py +196 -0
- aury/agents/backends/artifact/__init__.py +9 -0
- aury/agents/backends/artifact/memory.py +130 -0
- aury/agents/backends/artifact/types.py +133 -0
- aury/agents/backends/code/__init__.py +65 -0
- aury/agents/backends/file/__init__.py +11 -0
- aury/agents/backends/file/local.py +66 -0
- aury/agents/backends/file/types.py +40 -0
- aury/agents/backends/invocation/__init__.py +8 -0
- aury/agents/backends/invocation/memory.py +81 -0
- aury/agents/backends/invocation/types.py +110 -0
- aury/agents/backends/memory/__init__.py +8 -0
- aury/agents/backends/memory/memory.py +179 -0
- aury/agents/backends/memory/types.py +136 -0
- aury/agents/backends/message/__init__.py +9 -0
- aury/agents/backends/message/memory.py +122 -0
- aury/agents/backends/message/types.py +124 -0
- aury/agents/backends/sandbox.py +275 -0
- aury/agents/backends/session/__init__.py +8 -0
- aury/agents/backends/session/memory.py +93 -0
- aury/agents/backends/session/types.py +124 -0
- aury/agents/backends/shell/__init__.py +11 -0
- aury/agents/backends/shell/local.py +110 -0
- aury/agents/backends/shell/types.py +55 -0
- aury/agents/backends/shell.py +209 -0
- aury/agents/backends/snapshot/__init__.py +19 -0
- aury/agents/backends/snapshot/git.py +95 -0
- aury/agents/backends/snapshot/hybrid.py +125 -0
- aury/agents/backends/snapshot/memory.py +86 -0
- aury/agents/backends/snapshot/types.py +59 -0
- aury/agents/backends/state/__init__.py +29 -0
- aury/agents/backends/state/composite.py +49 -0
- aury/agents/backends/state/file.py +57 -0
- aury/agents/backends/state/memory.py +52 -0
- aury/agents/backends/state/sqlite.py +262 -0
- aury/agents/backends/state/types.py +178 -0
- aury/agents/backends/subagent/__init__.py +165 -0
- aury/agents/cli/__init__.py +41 -0
- aury/agents/cli/chat.py +239 -0
- aury/agents/cli/config.py +236 -0
- aury/agents/cli/extensions.py +460 -0
- aury/agents/cli/main.py +189 -0
- aury/agents/cli/session.py +337 -0
- aury/agents/cli/workflow.py +276 -0
- aury/agents/context_providers/__init__.py +66 -0
- aury/agents/context_providers/artifact.py +299 -0
- aury/agents/context_providers/base.py +177 -0
- aury/agents/context_providers/memory.py +70 -0
- aury/agents/context_providers/message.py +130 -0
- aury/agents/context_providers/skill.py +50 -0
- aury/agents/context_providers/subagent.py +46 -0
- aury/agents/context_providers/tool.py +68 -0
- aury/agents/core/__init__.py +83 -0
- aury/agents/core/base.py +573 -0
- aury/agents/core/context.py +797 -0
- aury/agents/core/context_builder.py +303 -0
- aury/agents/core/event_bus/__init__.py +15 -0
- aury/agents/core/event_bus/bus.py +203 -0
- aury/agents/core/factory.py +169 -0
- aury/agents/core/isolator.py +97 -0
- aury/agents/core/logging.py +95 -0
- aury/agents/core/parallel.py +194 -0
- aury/agents/core/runner.py +139 -0
- aury/agents/core/services/__init__.py +5 -0
- aury/agents/core/services/file_session.py +144 -0
- aury/agents/core/services/message.py +53 -0
- aury/agents/core/services/session.py +53 -0
- aury/agents/core/signals.py +109 -0
- aury/agents/core/state.py +363 -0
- aury/agents/core/types/__init__.py +107 -0
- aury/agents/core/types/action.py +176 -0
- aury/agents/core/types/artifact.py +135 -0
- aury/agents/core/types/block.py +736 -0
- aury/agents/core/types/message.py +350 -0
- aury/agents/core/types/recall.py +144 -0
- aury/agents/core/types/session.py +257 -0
- aury/agents/core/types/subagent.py +154 -0
- aury/agents/core/types/tool.py +205 -0
- aury/agents/eval/__init__.py +331 -0
- aury/agents/hitl/__init__.py +57 -0
- aury/agents/hitl/ask_user.py +242 -0
- aury/agents/hitl/compaction.py +230 -0
- aury/agents/hitl/exceptions.py +87 -0
- aury/agents/hitl/permission.py +617 -0
- aury/agents/hitl/revert.py +216 -0
- aury/agents/llm/__init__.py +31 -0
- aury/agents/llm/adapter.py +367 -0
- aury/agents/llm/openai.py +294 -0
- aury/agents/llm/provider.py +476 -0
- aury/agents/mcp/__init__.py +153 -0
- aury/agents/memory/__init__.py +46 -0
- aury/agents/memory/compaction.py +394 -0
- aury/agents/memory/manager.py +465 -0
- aury/agents/memory/processor.py +177 -0
- aury/agents/memory/store.py +187 -0
- aury/agents/memory/types.py +137 -0
- aury/agents/messages/__init__.py +40 -0
- aury/agents/messages/config.py +47 -0
- aury/agents/messages/raw_store.py +224 -0
- aury/agents/messages/store.py +118 -0
- aury/agents/messages/types.py +88 -0
- aury/agents/middleware/__init__.py +31 -0
- aury/agents/middleware/base.py +341 -0
- aury/agents/middleware/chain.py +342 -0
- aury/agents/middleware/message.py +129 -0
- aury/agents/middleware/message_container.py +126 -0
- aury/agents/middleware/raw_message.py +153 -0
- aury/agents/middleware/truncation.py +139 -0
- aury/agents/middleware/types.py +81 -0
- aury/agents/plugin.py +162 -0
- aury/agents/react/__init__.py +4 -0
- aury/agents/react/agent.py +1923 -0
- aury/agents/sandbox/__init__.py +23 -0
- aury/agents/sandbox/local.py +239 -0
- aury/agents/sandbox/remote.py +200 -0
- aury/agents/sandbox/types.py +115 -0
- aury/agents/skill/__init__.py +16 -0
- aury/agents/skill/loader.py +180 -0
- aury/agents/skill/types.py +83 -0
- aury/agents/tool/__init__.py +39 -0
- aury/agents/tool/builtin/__init__.py +23 -0
- aury/agents/tool/builtin/ask_user.py +155 -0
- aury/agents/tool/builtin/bash.py +107 -0
- aury/agents/tool/builtin/delegate.py +726 -0
- aury/agents/tool/builtin/edit.py +121 -0
- aury/agents/tool/builtin/plan.py +277 -0
- aury/agents/tool/builtin/read.py +91 -0
- aury/agents/tool/builtin/thinking.py +111 -0
- aury/agents/tool/builtin/yield_result.py +130 -0
- aury/agents/tool/decorator.py +252 -0
- aury/agents/tool/set.py +204 -0
- aury/agents/usage/__init__.py +12 -0
- aury/agents/usage/tracker.py +236 -0
- aury/agents/workflow/__init__.py +85 -0
- aury/agents/workflow/adapter.py +268 -0
- aury/agents/workflow/dag.py +116 -0
- aury/agents/workflow/dsl.py +575 -0
- aury/agents/workflow/executor.py +659 -0
- aury/agents/workflow/expression.py +136 -0
- aury/agents/workflow/parser.py +182 -0
- aury/agents/workflow/state.py +145 -0
- aury/agents/workflow/types.py +86 -0
- aury_agent-0.0.4.dist-info/METADATA +90 -0
- aury_agent-0.0.4.dist-info/RECORD +149 -0
- aury_agent-0.0.4.dist-info/WHEEL +4 -0
- aury_agent-0.0.4.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
"""
|
|
2
|
+
会话管理命令
|
|
3
|
+
============
|
|
4
|
+
|
|
5
|
+
管理 Agent 会话。
|
|
6
|
+
"""
|
|
7
|
+
import asyncio
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Optional
|
|
11
|
+
|
|
12
|
+
import typer
|
|
13
|
+
from rich.console import Console
|
|
14
|
+
from rich.panel import Panel
|
|
15
|
+
from rich.table import Table
|
|
16
|
+
|
|
17
|
+
from aury.agents.cli.config import load_config, DEFAULT_CONFIG_DIR
|
|
18
|
+
|
|
19
|
+
session_app = typer.Typer(help="会话管理命令")
|
|
20
|
+
console = Console()
|
|
21
|
+
|
|
22
|
+
# 默认会话存储路径
|
|
23
|
+
DEFAULT_SESSIONS_DIR = DEFAULT_CONFIG_DIR / "sessions"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _get_session_service(config: dict):
|
|
27
|
+
"""获取会话服务实例。"""
|
|
28
|
+
from aury.agents.core.services import FileSessionService
|
|
29
|
+
|
|
30
|
+
storage_path = config.get("session", {}).get(
|
|
31
|
+
"storage_path",
|
|
32
|
+
str(DEFAULT_SESSIONS_DIR)
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
return FileSessionService(storage_path=storage_path)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@session_app.command("list")
|
|
39
|
+
def list_sessions(
|
|
40
|
+
config_file: Optional[Path] = typer.Option(
|
|
41
|
+
None,
|
|
42
|
+
"--config", "-c",
|
|
43
|
+
help="配置文件路径",
|
|
44
|
+
),
|
|
45
|
+
limit: int = typer.Option(
|
|
46
|
+
20,
|
|
47
|
+
"--limit", "-n",
|
|
48
|
+
help="显示数量限制",
|
|
49
|
+
),
|
|
50
|
+
):
|
|
51
|
+
"""列出所有会话。"""
|
|
52
|
+
config = load_config(config_file)
|
|
53
|
+
service = _get_session_service(config)
|
|
54
|
+
|
|
55
|
+
# 获取会话列表
|
|
56
|
+
sessions = asyncio.run(service.list())
|
|
57
|
+
|
|
58
|
+
if not sessions:
|
|
59
|
+
console.print("[dim]暂无会话[/dim]")
|
|
60
|
+
return
|
|
61
|
+
|
|
62
|
+
table = Table(title=f"会话列表 (共 {len(sessions)} 个)")
|
|
63
|
+
table.add_column("会话 ID", style="cyan")
|
|
64
|
+
table.add_column("创建时间")
|
|
65
|
+
table.add_column("消息数")
|
|
66
|
+
table.add_column("状态")
|
|
67
|
+
|
|
68
|
+
for session_id in sessions[:limit]:
|
|
69
|
+
try:
|
|
70
|
+
session = asyncio.run(service.load(session_id))
|
|
71
|
+
created = session.metadata.get("created_at", "-")
|
|
72
|
+
msg_count = len(session.invocations)
|
|
73
|
+
status = "活跃" if session.invocations else "空闲"
|
|
74
|
+
table.add_row(session_id, str(created)[:19], str(msg_count), status)
|
|
75
|
+
except Exception:
|
|
76
|
+
table.add_row(session_id, "-", "-", "[red]错误[/red]")
|
|
77
|
+
|
|
78
|
+
console.print(table)
|
|
79
|
+
|
|
80
|
+
if len(sessions) > limit:
|
|
81
|
+
console.print(f"[dim]还有 {len(sessions) - limit} 个会话未显示[/dim]")
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@session_app.command("show")
|
|
85
|
+
def show_session(
|
|
86
|
+
session_id: str = typer.Argument(
|
|
87
|
+
...,
|
|
88
|
+
help="会话 ID",
|
|
89
|
+
),
|
|
90
|
+
config_file: Optional[Path] = typer.Option(
|
|
91
|
+
None,
|
|
92
|
+
"--config", "-c",
|
|
93
|
+
help="配置文件路径",
|
|
94
|
+
),
|
|
95
|
+
):
|
|
96
|
+
"""显示会话详情。"""
|
|
97
|
+
config = load_config(config_file)
|
|
98
|
+
service = _get_session_service(config)
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
session = asyncio.run(service.load(session_id))
|
|
102
|
+
except Exception as e:
|
|
103
|
+
console.print(f"[red]无法加载会话: {e}[/red]")
|
|
104
|
+
raise typer.Exit(1)
|
|
105
|
+
|
|
106
|
+
# 会话信息
|
|
107
|
+
info = [
|
|
108
|
+
f"会话 ID: {session.session_id}",
|
|
109
|
+
f"调用数: {len(session.invocations)}",
|
|
110
|
+
]
|
|
111
|
+
|
|
112
|
+
# 元数据
|
|
113
|
+
if session.metadata:
|
|
114
|
+
info.append("")
|
|
115
|
+
info.append("[bold]元数据:[/bold]")
|
|
116
|
+
for key, value in session.metadata.items():
|
|
117
|
+
info.append(f" {key}: {value}")
|
|
118
|
+
|
|
119
|
+
# 调用历史
|
|
120
|
+
if session.invocations:
|
|
121
|
+
info.append("")
|
|
122
|
+
info.append("[bold]最近调用:[/bold]")
|
|
123
|
+
for inv in session.invocations[-5:]:
|
|
124
|
+
info.append(f" - {inv.invocation_id}: {inv.status}")
|
|
125
|
+
|
|
126
|
+
console.print(Panel(
|
|
127
|
+
"\n".join(info),
|
|
128
|
+
title=f"会话: {session_id}",
|
|
129
|
+
))
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@session_app.command("delete")
|
|
133
|
+
def delete_session(
|
|
134
|
+
session_id: str = typer.Argument(
|
|
135
|
+
...,
|
|
136
|
+
help="会话 ID",
|
|
137
|
+
),
|
|
138
|
+
force: bool = typer.Option(
|
|
139
|
+
False,
|
|
140
|
+
"--force", "-f",
|
|
141
|
+
help="强制删除,不询问确认",
|
|
142
|
+
),
|
|
143
|
+
config_file: Optional[Path] = typer.Option(
|
|
144
|
+
None,
|
|
145
|
+
"--config", "-c",
|
|
146
|
+
help="配置文件路径",
|
|
147
|
+
),
|
|
148
|
+
):
|
|
149
|
+
"""删除会话。"""
|
|
150
|
+
config = load_config(config_file)
|
|
151
|
+
service = _get_session_service(config)
|
|
152
|
+
|
|
153
|
+
if not force:
|
|
154
|
+
confirm = typer.confirm(f"确定要删除会话 {session_id} 吗?")
|
|
155
|
+
if not confirm:
|
|
156
|
+
console.print("[dim]已取消[/dim]")
|
|
157
|
+
return
|
|
158
|
+
|
|
159
|
+
try:
|
|
160
|
+
asyncio.run(service.delete(session_id))
|
|
161
|
+
console.print(f"[green]会话已删除: {session_id}[/green]")
|
|
162
|
+
except Exception as e:
|
|
163
|
+
console.print(f"[red]删除失败: {e}[/red]")
|
|
164
|
+
raise typer.Exit(1)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
@session_app.command("clear")
|
|
168
|
+
def clear_sessions(
|
|
169
|
+
force: bool = typer.Option(
|
|
170
|
+
False,
|
|
171
|
+
"--force", "-f",
|
|
172
|
+
help="强制删除,不询问确认",
|
|
173
|
+
),
|
|
174
|
+
older_than: Optional[int] = typer.Option(
|
|
175
|
+
None,
|
|
176
|
+
"--older-than",
|
|
177
|
+
help="删除超过指定天数的会话",
|
|
178
|
+
),
|
|
179
|
+
config_file: Optional[Path] = typer.Option(
|
|
180
|
+
None,
|
|
181
|
+
"--config", "-c",
|
|
182
|
+
help="配置文件路径",
|
|
183
|
+
),
|
|
184
|
+
):
|
|
185
|
+
"""清理会话。"""
|
|
186
|
+
config = load_config(config_file)
|
|
187
|
+
service = _get_session_service(config)
|
|
188
|
+
|
|
189
|
+
sessions = asyncio.run(service.list())
|
|
190
|
+
|
|
191
|
+
if not sessions:
|
|
192
|
+
console.print("[dim]暂无会话[/dim]")
|
|
193
|
+
return
|
|
194
|
+
|
|
195
|
+
to_delete = []
|
|
196
|
+
|
|
197
|
+
if older_than:
|
|
198
|
+
# 筛选旧会话
|
|
199
|
+
cutoff = datetime.now().timestamp() - (older_than * 86400)
|
|
200
|
+
|
|
201
|
+
for session_id in sessions:
|
|
202
|
+
try:
|
|
203
|
+
session = asyncio.run(service.load(session_id))
|
|
204
|
+
created = session.metadata.get("created_at")
|
|
205
|
+
if created:
|
|
206
|
+
created_ts = datetime.fromisoformat(created).timestamp()
|
|
207
|
+
if created_ts < cutoff:
|
|
208
|
+
to_delete.append(session_id)
|
|
209
|
+
except Exception:
|
|
210
|
+
pass
|
|
211
|
+
else:
|
|
212
|
+
to_delete = sessions
|
|
213
|
+
|
|
214
|
+
if not to_delete:
|
|
215
|
+
console.print("[dim]没有符合条件的会话[/dim]")
|
|
216
|
+
return
|
|
217
|
+
|
|
218
|
+
if not force:
|
|
219
|
+
console.print(f"将删除 {len(to_delete)} 个会话:")
|
|
220
|
+
for sid in to_delete[:5]:
|
|
221
|
+
console.print(f" - {sid}")
|
|
222
|
+
if len(to_delete) > 5:
|
|
223
|
+
console.print(f" ... 还有 {len(to_delete) - 5} 个")
|
|
224
|
+
|
|
225
|
+
confirm = typer.confirm("确定要删除这些会话吗?")
|
|
226
|
+
if not confirm:
|
|
227
|
+
console.print("[dim]已取消[/dim]")
|
|
228
|
+
return
|
|
229
|
+
|
|
230
|
+
# 执行删除
|
|
231
|
+
deleted = 0
|
|
232
|
+
for session_id in to_delete:
|
|
233
|
+
try:
|
|
234
|
+
asyncio.run(service.delete(session_id))
|
|
235
|
+
deleted += 1
|
|
236
|
+
except Exception:
|
|
237
|
+
pass
|
|
238
|
+
|
|
239
|
+
console.print(f"[green]已删除 {deleted} 个会话[/green]")
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
@session_app.command("resume")
|
|
243
|
+
def resume_session(
|
|
244
|
+
session_id: str = typer.Argument(
|
|
245
|
+
...,
|
|
246
|
+
help="会话 ID",
|
|
247
|
+
),
|
|
248
|
+
config_file: Optional[Path] = typer.Option(
|
|
249
|
+
None,
|
|
250
|
+
"--config", "-c",
|
|
251
|
+
help="配置文件路径",
|
|
252
|
+
),
|
|
253
|
+
):
|
|
254
|
+
"""恢复会话并继续对话。"""
|
|
255
|
+
from aury.agents.cli.chat import chat_command
|
|
256
|
+
|
|
257
|
+
config = load_config(config_file)
|
|
258
|
+
|
|
259
|
+
# 验证会话存在
|
|
260
|
+
service = _get_session_service(config)
|
|
261
|
+
try:
|
|
262
|
+
asyncio.run(service.load(session_id))
|
|
263
|
+
except Exception as e:
|
|
264
|
+
console.print(f"[red]会话不存在: {e}[/red]")
|
|
265
|
+
raise typer.Exit(1)
|
|
266
|
+
|
|
267
|
+
console.print(f"[dim]恢复会话: {session_id}[/dim]")
|
|
268
|
+
|
|
269
|
+
# 启动对话
|
|
270
|
+
asyncio.run(chat_command(
|
|
271
|
+
session_id=session_id,
|
|
272
|
+
config=config,
|
|
273
|
+
verbose=False,
|
|
274
|
+
))
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
@session_app.command("export")
|
|
278
|
+
def export_session(
|
|
279
|
+
session_id: str = typer.Argument(
|
|
280
|
+
...,
|
|
281
|
+
help="会话 ID",
|
|
282
|
+
),
|
|
283
|
+
output: Path = typer.Option(
|
|
284
|
+
None,
|
|
285
|
+
"--output", "-o",
|
|
286
|
+
help="输出文件路径",
|
|
287
|
+
),
|
|
288
|
+
format: str = typer.Option(
|
|
289
|
+
"json",
|
|
290
|
+
"--format",
|
|
291
|
+
help="输出格式 (json/yaml)",
|
|
292
|
+
),
|
|
293
|
+
config_file: Optional[Path] = typer.Option(
|
|
294
|
+
None,
|
|
295
|
+
"--config", "-c",
|
|
296
|
+
help="配置文件路径",
|
|
297
|
+
),
|
|
298
|
+
):
|
|
299
|
+
"""导出会话数据。"""
|
|
300
|
+
import json
|
|
301
|
+
import yaml
|
|
302
|
+
|
|
303
|
+
config = load_config(config_file)
|
|
304
|
+
service = _get_session_service(config)
|
|
305
|
+
|
|
306
|
+
try:
|
|
307
|
+
session = asyncio.run(service.load(session_id))
|
|
308
|
+
except Exception as e:
|
|
309
|
+
console.print(f"[red]无法加载会话: {e}[/red]")
|
|
310
|
+
raise typer.Exit(1)
|
|
311
|
+
|
|
312
|
+
# 转换为字典
|
|
313
|
+
data = {
|
|
314
|
+
"session_id": session.session_id,
|
|
315
|
+
"metadata": session.metadata,
|
|
316
|
+
"invocations": [
|
|
317
|
+
{
|
|
318
|
+
"invocation_id": inv.invocation_id,
|
|
319
|
+
"status": inv.status,
|
|
320
|
+
}
|
|
321
|
+
for inv in session.invocations
|
|
322
|
+
],
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
# 格式化输出
|
|
326
|
+
if format == "yaml":
|
|
327
|
+
content = yaml.safe_dump(data, allow_unicode=True, default_flow_style=False)
|
|
328
|
+
else:
|
|
329
|
+
content = json.dumps(data, ensure_ascii=False, indent=2)
|
|
330
|
+
|
|
331
|
+
# 输出
|
|
332
|
+
if output:
|
|
333
|
+
with open(output, "w", encoding="utf-8") as f:
|
|
334
|
+
f.write(content)
|
|
335
|
+
console.print(f"[green]已导出到: {output}[/green]")
|
|
336
|
+
else:
|
|
337
|
+
console.print(content)
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Workflow 命令
|
|
3
|
+
=============
|
|
4
|
+
|
|
5
|
+
运行和管理 Workflow。
|
|
6
|
+
"""
|
|
7
|
+
import asyncio
|
|
8
|
+
import json
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Optional
|
|
11
|
+
|
|
12
|
+
import typer
|
|
13
|
+
from rich.console import Console
|
|
14
|
+
from rich.panel import Panel
|
|
15
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
16
|
+
from rich.table import Table
|
|
17
|
+
|
|
18
|
+
from aury.agents.cli.config import load_config
|
|
19
|
+
|
|
20
|
+
workflow_app = typer.Typer(help="Workflow 相关命令")
|
|
21
|
+
console = Console()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@workflow_app.command("run")
|
|
25
|
+
def run_workflow(
|
|
26
|
+
workflow_file: Path = typer.Argument(
|
|
27
|
+
...,
|
|
28
|
+
help="Workflow 定义文件路径(.py 或 .yaml)",
|
|
29
|
+
),
|
|
30
|
+
input_data: Optional[str] = typer.Option(
|
|
31
|
+
None,
|
|
32
|
+
"--input", "-i",
|
|
33
|
+
help="输入数据(JSON 格式)",
|
|
34
|
+
),
|
|
35
|
+
config_file: Optional[Path] = typer.Option(
|
|
36
|
+
None,
|
|
37
|
+
"--config", "-c",
|
|
38
|
+
help="配置文件路径",
|
|
39
|
+
),
|
|
40
|
+
verbose: bool = typer.Option(
|
|
41
|
+
False,
|
|
42
|
+
"--verbose", "-v",
|
|
43
|
+
help="显示详细输出",
|
|
44
|
+
),
|
|
45
|
+
dry_run: bool = typer.Option(
|
|
46
|
+
False,
|
|
47
|
+
"--dry-run",
|
|
48
|
+
help="模拟运行,不实际执行",
|
|
49
|
+
),
|
|
50
|
+
):
|
|
51
|
+
"""
|
|
52
|
+
运行 Workflow。
|
|
53
|
+
|
|
54
|
+
示例:
|
|
55
|
+
aury-agent workflow run pipeline.py
|
|
56
|
+
aury-agent workflow run pipeline.yaml --input '{"data": "test"}'
|
|
57
|
+
"""
|
|
58
|
+
# 加载配置
|
|
59
|
+
cfg = load_config(config_file)
|
|
60
|
+
|
|
61
|
+
if not workflow_file.exists():
|
|
62
|
+
console.print(f"[red]错误: Workflow 文件不存在: {workflow_file}[/red]")
|
|
63
|
+
raise typer.Exit(1)
|
|
64
|
+
|
|
65
|
+
# 解析输入
|
|
66
|
+
input_dict = {}
|
|
67
|
+
if input_data:
|
|
68
|
+
try:
|
|
69
|
+
input_dict = json.loads(input_data)
|
|
70
|
+
except json.JSONDecodeError as e:
|
|
71
|
+
console.print(f"[red]错误: 无效的 JSON 输入: {e}[/red]")
|
|
72
|
+
raise typer.Exit(1)
|
|
73
|
+
|
|
74
|
+
console.print(f"[bold]运行 Workflow: {workflow_file}[/bold]")
|
|
75
|
+
|
|
76
|
+
if dry_run:
|
|
77
|
+
console.print("[yellow]模拟运行模式[/yellow]")
|
|
78
|
+
_dry_run_workflow(workflow_file, input_dict, verbose)
|
|
79
|
+
return
|
|
80
|
+
|
|
81
|
+
# 实际运行
|
|
82
|
+
asyncio.run(_execute_workflow(workflow_file, input_dict, cfg, verbose))
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
async def _execute_workflow(
|
|
86
|
+
workflow_file: Path,
|
|
87
|
+
input_data: dict,
|
|
88
|
+
config: dict,
|
|
89
|
+
verbose: bool,
|
|
90
|
+
):
|
|
91
|
+
"""执行 Workflow。"""
|
|
92
|
+
from aury.agents.workflow import WorkflowExecutor
|
|
93
|
+
from aury.agents.core import Session, InvocationContext
|
|
94
|
+
|
|
95
|
+
# 加载 Workflow
|
|
96
|
+
workflow = await _load_workflow(workflow_file)
|
|
97
|
+
|
|
98
|
+
if workflow is None:
|
|
99
|
+
console.print("[red]无法加载 Workflow[/red]")
|
|
100
|
+
return
|
|
101
|
+
|
|
102
|
+
console.print(f"[dim]Workflow: {workflow.name}[/dim]")
|
|
103
|
+
console.print(f"[dim]步骤数: {len(workflow.step_map)}[/dim]")
|
|
104
|
+
|
|
105
|
+
# 创建上下文
|
|
106
|
+
session = Session(session_id=f"workflow-{workflow.name}")
|
|
107
|
+
context = InvocationContext(session=session)
|
|
108
|
+
|
|
109
|
+
# 创建执行器
|
|
110
|
+
executor = WorkflowExecutor(workflow)
|
|
111
|
+
|
|
112
|
+
# 执行
|
|
113
|
+
with Progress(
|
|
114
|
+
SpinnerColumn(),
|
|
115
|
+
TextColumn("[progress.description]{task.description}"),
|
|
116
|
+
console=console,
|
|
117
|
+
) as progress:
|
|
118
|
+
task = progress.add_task("执行中...", total=None)
|
|
119
|
+
|
|
120
|
+
try:
|
|
121
|
+
result = await executor.execute(context, input_data=input_data)
|
|
122
|
+
progress.update(task, description="完成")
|
|
123
|
+
except Exception as e:
|
|
124
|
+
progress.update(task, description=f"[red]失败: {e}[/red]")
|
|
125
|
+
raise
|
|
126
|
+
|
|
127
|
+
# 显示结果
|
|
128
|
+
console.print()
|
|
129
|
+
|
|
130
|
+
if result.success:
|
|
131
|
+
console.print(Panel(
|
|
132
|
+
f"[green]Workflow 执行成功[/green]\n\n"
|
|
133
|
+
f"完成步骤: {', '.join(result.state.get_completed_steps())}",
|
|
134
|
+
title="结果",
|
|
135
|
+
))
|
|
136
|
+
else:
|
|
137
|
+
console.print(Panel(
|
|
138
|
+
f"[red]Workflow 执行失败[/red]\n\n"
|
|
139
|
+
f"失败步骤: {', '.join([s for s in result.state.step_statuses if result.state.is_step_failed(s)])}",
|
|
140
|
+
title="结果",
|
|
141
|
+
))
|
|
142
|
+
|
|
143
|
+
# 详细输出
|
|
144
|
+
if verbose:
|
|
145
|
+
console.print("\n[bold]步骤详情:[/bold]")
|
|
146
|
+
for step_id, status in result.state.step_statuses.items():
|
|
147
|
+
step_result = result.state.get_step_result(step_id)
|
|
148
|
+
console.print(f" {step_id}: {status}")
|
|
149
|
+
if step_result:
|
|
150
|
+
console.print(f" 结果: {step_result}")
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
async def _load_workflow(workflow_file: Path):
|
|
154
|
+
"""加载 Workflow 定义。"""
|
|
155
|
+
if workflow_file.suffix == ".py":
|
|
156
|
+
# 从 Python 文件加载
|
|
157
|
+
import importlib.util
|
|
158
|
+
|
|
159
|
+
spec = importlib.util.spec_from_file_location("workflow_module", workflow_file)
|
|
160
|
+
module = importlib.util.module_from_spec(spec)
|
|
161
|
+
spec.loader.exec_module(module)
|
|
162
|
+
|
|
163
|
+
# 查找 Workflow 对象或构建函数
|
|
164
|
+
if hasattr(module, "workflow"):
|
|
165
|
+
return module.workflow
|
|
166
|
+
elif hasattr(module, "build_workflow"):
|
|
167
|
+
return module.build_workflow()
|
|
168
|
+
elif hasattr(module, "create_workflow"):
|
|
169
|
+
return module.create_workflow()
|
|
170
|
+
else:
|
|
171
|
+
console.print("[yellow]警告: 未找到 workflow 对象或构建函数[/yellow]")
|
|
172
|
+
return None
|
|
173
|
+
|
|
174
|
+
elif workflow_file.suffix in [".yaml", ".yml"]:
|
|
175
|
+
# 从 YAML 文件加载
|
|
176
|
+
# TODO: 实现 YAML 格式的 Workflow 定义
|
|
177
|
+
console.print("[yellow]YAML Workflow 格式暂未支持[/yellow]")
|
|
178
|
+
return None
|
|
179
|
+
|
|
180
|
+
else:
|
|
181
|
+
console.print(f"[red]不支持的文件格式: {workflow_file.suffix}[/red]")
|
|
182
|
+
return None
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _dry_run_workflow(workflow_file: Path, input_data: dict, verbose: bool):
|
|
186
|
+
"""模拟运行 Workflow。"""
|
|
187
|
+
console.print("\n[dim]模拟运行步骤:[/dim]")
|
|
188
|
+
console.print(f" 1. 加载 Workflow: {workflow_file}")
|
|
189
|
+
console.print(f" 2. 验证输入: {input_data or '(无输入)'}")
|
|
190
|
+
console.print(" 3. 执行各步骤 (模拟)")
|
|
191
|
+
console.print(" 4. 返回结果")
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
@workflow_app.command("list")
|
|
195
|
+
def list_workflows(
|
|
196
|
+
directory: Path = typer.Option(
|
|
197
|
+
Path("."),
|
|
198
|
+
"--dir", "-d",
|
|
199
|
+
help="搜索目录",
|
|
200
|
+
),
|
|
201
|
+
):
|
|
202
|
+
"""列出可用的 Workflow 文件。"""
|
|
203
|
+
workflows = []
|
|
204
|
+
|
|
205
|
+
# 搜索 Python 文件
|
|
206
|
+
for py_file in directory.glob("**/*.py"):
|
|
207
|
+
if "workflow" in py_file.name.lower():
|
|
208
|
+
workflows.append(py_file)
|
|
209
|
+
|
|
210
|
+
# 搜索 YAML 文件
|
|
211
|
+
for yaml_file in directory.glob("**/*.yaml"):
|
|
212
|
+
if "workflow" in yaml_file.name.lower():
|
|
213
|
+
workflows.append(yaml_file)
|
|
214
|
+
|
|
215
|
+
if not workflows:
|
|
216
|
+
console.print("[dim]未找到 Workflow 文件[/dim]")
|
|
217
|
+
return
|
|
218
|
+
|
|
219
|
+
table = Table(title="可用 Workflow")
|
|
220
|
+
table.add_column("文件", style="cyan")
|
|
221
|
+
table.add_column("类型")
|
|
222
|
+
|
|
223
|
+
for wf in workflows:
|
|
224
|
+
file_type = "Python" if wf.suffix == ".py" else "YAML"
|
|
225
|
+
table.add_row(str(wf.relative_to(directory)), file_type)
|
|
226
|
+
|
|
227
|
+
console.print(table)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
@workflow_app.command("validate")
|
|
231
|
+
def validate_workflow(
|
|
232
|
+
workflow_file: Path = typer.Argument(
|
|
233
|
+
...,
|
|
234
|
+
help="Workflow 定义文件路径",
|
|
235
|
+
),
|
|
236
|
+
):
|
|
237
|
+
"""验证 Workflow 定义。"""
|
|
238
|
+
if not workflow_file.exists():
|
|
239
|
+
console.print(f"[red]文件不存在: {workflow_file}[/red]")
|
|
240
|
+
raise typer.Exit(1)
|
|
241
|
+
|
|
242
|
+
console.print(f"验证 Workflow: {workflow_file}")
|
|
243
|
+
|
|
244
|
+
try:
|
|
245
|
+
workflow = asyncio.run(_load_workflow(workflow_file))
|
|
246
|
+
|
|
247
|
+
if workflow is None:
|
|
248
|
+
console.print("[red]验证失败: 无法加载 Workflow[/red]")
|
|
249
|
+
raise typer.Exit(1)
|
|
250
|
+
|
|
251
|
+
# 验证
|
|
252
|
+
errors = []
|
|
253
|
+
|
|
254
|
+
if not workflow.entry_step_id:
|
|
255
|
+
errors.append("未定义入口步骤")
|
|
256
|
+
|
|
257
|
+
if not workflow.step_map:
|
|
258
|
+
errors.append("没有定义任何步骤")
|
|
259
|
+
|
|
260
|
+
if workflow.entry_step_id and workflow.entry_step_id not in workflow.step_map:
|
|
261
|
+
errors.append(f"入口步骤不存在: {workflow.entry_step_id}")
|
|
262
|
+
|
|
263
|
+
if errors:
|
|
264
|
+
console.print("[red]验证失败:[/red]")
|
|
265
|
+
for err in errors:
|
|
266
|
+
console.print(f" - {err}")
|
|
267
|
+
raise typer.Exit(1)
|
|
268
|
+
|
|
269
|
+
console.print(f"[green]验证通过[/green]")
|
|
270
|
+
console.print(f" 名称: {workflow.name}")
|
|
271
|
+
console.print(f" 步骤数: {len(workflow.step_map)}")
|
|
272
|
+
console.print(f" 入口: {workflow.entry_step_id}")
|
|
273
|
+
|
|
274
|
+
except Exception as e:
|
|
275
|
+
console.print(f"[red]验证失败: {e}[/red]")
|
|
276
|
+
raise typer.Exit(1)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""ContextProvider system for context engineering.
|
|
2
|
+
|
|
3
|
+
ContextProviders provide a unified abstraction for fetching LLM context:
|
|
4
|
+
- Messages (conversation history)
|
|
5
|
+
- Memory (long-term memory summaries/recalls)
|
|
6
|
+
- Artifacts (with pluggable loaders for different types)
|
|
7
|
+
- SubAgents (sub-agent configurations)
|
|
8
|
+
- Skills (capability descriptions)
|
|
9
|
+
|
|
10
|
+
Core Design:
|
|
11
|
+
- Provider: ONLY responsible for FETCHING context (read)
|
|
12
|
+
- Middleware: Responsible for WRITING/SAVING (via hooks like on_message_save)
|
|
13
|
+
- Manager: Service layer providing APIs for both Provider and Middleware
|
|
14
|
+
|
|
15
|
+
- Each ContextProvider implements `fetch(ctx) -> AgentContext`
|
|
16
|
+
- AgentContext contains: system_content, user_content, messages, tools, subagents, skills
|
|
17
|
+
- Multiple AgentContexts are merged before LLM call
|
|
18
|
+
|
|
19
|
+
Usage:
|
|
20
|
+
agent = ReactAgent.create(
|
|
21
|
+
llm=llm,
|
|
22
|
+
tools=[my_tool],
|
|
23
|
+
providers=[
|
|
24
|
+
ArtifactContextProvider(backend=artifact_backend),
|
|
25
|
+
MemoryContextProvider(memory_manager=memory),
|
|
26
|
+
],
|
|
27
|
+
enable_history=True, # Auto-creates MessageContextProvider + MessageBackendMiddleware
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
Built-in ContextProviders:
|
|
31
|
+
- MessageContextProvider: Fetch conversation history
|
|
32
|
+
- MemoryContextProvider: Fetch memory summaries/recalls
|
|
33
|
+
- ArtifactContextProvider: Artifact index with ReadArtifactTool + pluggable loaders
|
|
34
|
+
- SubAgentContextProvider: Provides sub-agent configurations
|
|
35
|
+
- SkillContextProvider: Skill descriptions in system prompt
|
|
36
|
+
"""
|
|
37
|
+
from .base import ContextProvider, AgentContext, BaseContextProvider
|
|
38
|
+
from .message import MessageContextProvider
|
|
39
|
+
from .memory import MemoryContextProvider
|
|
40
|
+
from .artifact import (
|
|
41
|
+
ArtifactContextProvider,
|
|
42
|
+
ArtifactLoader,
|
|
43
|
+
ReadArtifactTool,
|
|
44
|
+
register_loader,
|
|
45
|
+
get_loader,
|
|
46
|
+
)
|
|
47
|
+
from .subagent import SubAgentContextProvider
|
|
48
|
+
from .skill import SkillContextProvider
|
|
49
|
+
|
|
50
|
+
__all__ = [
|
|
51
|
+
# Base protocol
|
|
52
|
+
"ContextProvider",
|
|
53
|
+
"AgentContext",
|
|
54
|
+
"BaseContextProvider",
|
|
55
|
+
# Built-in providers
|
|
56
|
+
"MessageContextProvider",
|
|
57
|
+
"MemoryContextProvider",
|
|
58
|
+
"ArtifactContextProvider",
|
|
59
|
+
"SubAgentContextProvider",
|
|
60
|
+
"SkillContextProvider",
|
|
61
|
+
# Artifact utilities
|
|
62
|
+
"ArtifactLoader",
|
|
63
|
+
"ReadArtifactTool",
|
|
64
|
+
"register_loader",
|
|
65
|
+
"get_loader",
|
|
66
|
+
]
|