python-library-ai-agent 0.1.0__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 (62) hide show
  1. ai_agent/__init__.py +66 -0
  2. ai_agent/agent.py +122 -0
  3. ai_agent/app/__init__.py +10 -0
  4. ai_agent/app/_workspace.py +127 -0
  5. ai_agent/app/app.py +321 -0
  6. ai_agent/app/harness_io.py +109 -0
  7. ai_agent/app/output_format.py +77 -0
  8. ai_agent/app/packet.py +39 -0
  9. ai_agent/app/session.py +742 -0
  10. ai_agent/app/session_store.py +85 -0
  11. ai_agent/builtin_tools/__init__.py +18 -0
  12. ai_agent/builtin_tools/current_time.py +39 -0
  13. ai_agent/builtin_tools/pack.py +20 -0
  14. ai_agent/builtin_tools/prefix.py +11 -0
  15. ai_agent/context.py +151 -0
  16. ai_agent/harness/__init__.py +3 -0
  17. ai_agent/harness/current_time.py +25 -0
  18. ai_agent/harness/harness.py +324 -0
  19. ai_agent/harness/process.py +115 -0
  20. ai_agent/harness/prompts.py +38 -0
  21. ai_agent/harness/sandbox.py +139 -0
  22. ai_agent/json_extract.py +70 -0
  23. ai_agent/listener.py +172 -0
  24. ai_agent/llm.py +39 -0
  25. ai_agent/llm_openai.py +117 -0
  26. ai_agent/loop.py +124 -0
  27. ai_agent/mcp_config.py +54 -0
  28. ai_agent/mcp_loader.py +110 -0
  29. ai_agent/memory/__init__.py +9 -0
  30. ai_agent/memory/compression_work.py +71 -0
  31. ai_agent/memory/compressor.py +339 -0
  32. ai_agent/memory/config.py +40 -0
  33. ai_agent/memory/context_builder.py +57 -0
  34. ai_agent/memory/memory_system.py +561 -0
  35. ai_agent/memory/models.py +76 -0
  36. ai_agent/memory/snapshot_merge.py +158 -0
  37. ai_agent/memory/store.py +107 -0
  38. ai_agent/memory/worker.py +227 -0
  39. ai_agent/plan/__init__.py +15 -0
  40. ai_agent/plan/complete.py +64 -0
  41. ai_agent/plan/delivery.py +41 -0
  42. ai_agent/plan/display.py +46 -0
  43. ai_agent/plan/models.py +44 -0
  44. ai_agent/plan/parse.py +39 -0
  45. ai_agent/plan/planner.py +204 -0
  46. ai_agent/plan/runner.py +281 -0
  47. ai_agent/react_tool_turn.py +39 -0
  48. ai_agent/rule/__init__.py +3 -0
  49. ai_agent/rule/rules.py +36 -0
  50. ai_agent/skill/__init__.py +5 -0
  51. ai_agent/skill/builtin_registry.py +56 -0
  52. ai_agent/skill/catalog.py +104 -0
  53. ai_agent/skill/frontmatter.py +83 -0
  54. ai_agent/skill/manager.py +486 -0
  55. ai_agent/skill/models.py +31 -0
  56. ai_agent/skill/roots.py +150 -0
  57. ai_agent/skill/skill_kit.py +80 -0
  58. ai_agent/skill/tool_declarations.py +68 -0
  59. ai_agent/tools.py +123 -0
  60. python_library_ai_agent-0.1.0.dist-info/METADATA +10 -0
  61. python_library_ai_agent-0.1.0.dist-info/RECORD +62 -0
  62. python_library_ai_agent-0.1.0.dist-info/WHEEL +4 -0
@@ -0,0 +1,742 @@
1
+ from __future__ import annotations
2
+
3
+
4
+
5
+ from collections.abc import Iterable, Mapping, Sequence
6
+
7
+ from pathlib import Path
8
+
9
+ from typing import Any
10
+
11
+
12
+
13
+ from ai_agent.agent import Agent
14
+
15
+ from ai_agent.context import ChatMessage, RunContext, RunPhase
16
+
17
+ from ai_agent.harness import Harness
18
+
19
+ from ai_agent.listener import AgentListener, normalize_listeners
20
+
21
+ from ai_agent.memory import MemorySystem
22
+
23
+ from ai_agent.skill import SkillKit
24
+
25
+ from ai_agent.skill.manager import SkillManager
26
+
27
+ from ai_agent.plan.models import PlanRunResult
28
+ from ai_agent.tools import Tool, ToolRegistry
29
+
30
+
31
+
32
+
33
+
34
+ class AgentSession:
35
+ """
36
+ 单会话入口:独立工作区、沙箱工具、对话代理与可选分层记忆。
37
+
38
+ 由 AgentApp.open_session 组装;应用代码勿直接构造。
39
+
40
+ Args:
41
+ session_id: 会话标识,对应总沙箱下该会话子目录名
42
+ workspace: 会话根目录(含 harness、memory 等固定子目录)
43
+ harness: 已绑定工作区的沙箱与可选技能套件
44
+ agent: 已注册工具表与规则的语言模型代理
45
+ skill_manager: 技能动态管理器;未配置技能根时为 None
46
+ memory: 分层记忆;未配置记忆模型时为 None
47
+ """
48
+
49
+ def __init__(
50
+ self,
51
+ *,
52
+ session_id: str,
53
+ workspace: Path,
54
+ harness: Harness,
55
+ agent: Agent,
56
+ skill_manager: SkillManager | None = None,
57
+ memory: MemorySystem | None = None,
58
+ ) -> None:
59
+
60
+ self._session_id = session_id
61
+
62
+ self._workspace = workspace
63
+
64
+ self._harness = harness
65
+
66
+ self._agent = agent
67
+
68
+ self._skill_manager = skill_manager
69
+
70
+ self._memory = memory
71
+
72
+ self._messages: list[ChatMessage] = []
73
+
74
+
75
+
76
+ @property
77
+
78
+ def memory(self) -> MemorySystem | None:
79
+
80
+ """绑定的分层记忆;未配置时为 None。"""
81
+
82
+ return self._memory
83
+
84
+
85
+
86
+ @property
87
+
88
+ def session_id(self) -> str:
89
+
90
+ """会话标识。"""
91
+
92
+ return self._session_id
93
+
94
+
95
+
96
+ @property
97
+
98
+ def workspace(self) -> Path:
99
+
100
+ """该会话在总沙箱下的根目录(含 harness、memory 等子目录)。"""
101
+
102
+ return self._workspace
103
+
104
+
105
+
106
+ @property
107
+
108
+ def harness(self) -> Harness:
109
+
110
+ """已安装隔离工作区与 skill 的 Harness。"""
111
+
112
+ return self._harness
113
+
114
+
115
+
116
+ @property
117
+
118
+ def skill_manager(self) -> SkillManager | None:
119
+
120
+ """本会话的 skill 能力管理器;未配置 skill_roots 时为 None。"""
121
+
122
+ return self._skill_manager
123
+
124
+
125
+
126
+ @property
127
+
128
+ def agent(self) -> Agent:
129
+
130
+ """绑定了本会话分层工具表的 ReAct 代理。"""
131
+
132
+ return self._agent
133
+
134
+
135
+
136
+ @property
137
+
138
+ def messages(self) -> tuple[ChatMessage, ...]:
139
+
140
+ """当前对话历史(只读视图)。"""
141
+
142
+ return tuple(self._messages)
143
+
144
+
145
+
146
+ def clear_messages(self) -> None:
147
+
148
+ """清空本会话对话历史。"""
149
+
150
+ self._messages.clear()
151
+
152
+ def replace_messages(self, messages: Sequence[ChatMessage]) -> None:
153
+ """
154
+ 用给定列表替换内存中的对话历史(不写入磁盘)。
155
+
156
+ Args:
157
+ messages: 新的消息序列
158
+ """
159
+ self._messages.clear()
160
+ self._messages.extend(messages)
161
+
162
+
163
+
164
+ def enable_skill(self, skill_ref: str) -> str:
165
+
166
+ """
167
+
168
+ 启用 skill 并刷新工具表。
169
+
170
+
171
+
172
+ Args:
173
+
174
+ skill_ref: ``{root_key}/{skill_id}``
175
+
176
+ """
177
+
178
+ manager = self._require_skill_manager()
179
+
180
+ message = manager.enable_skill(skill_ref)
181
+
182
+ manager.sync_to_registry()
183
+
184
+ return message
185
+
186
+
187
+
188
+ def disable_skill(self, skill_ref: str) -> str:
189
+
190
+ """
191
+
192
+ 停用 skill 并刷新工具表。
193
+
194
+
195
+
196
+ Args:
197
+
198
+ skill_ref: ``{root_key}/{skill_id}``
199
+
200
+ """
201
+
202
+ manager = self._require_skill_manager()
203
+
204
+ message = manager.disable_skill(skill_ref)
205
+
206
+ manager.sync_to_registry()
207
+
208
+ return message
209
+
210
+
211
+
212
+ def refresh_skills(self) -> str:
213
+
214
+ """
215
+
216
+ 重新扫描 skill 根并刷新工具表。
217
+
218
+
219
+
220
+ Returns:
221
+
222
+ 扫描结果摘要
223
+
224
+ """
225
+
226
+ manager = self._require_skill_manager()
227
+
228
+ message = manager.refresh()
229
+
230
+ manager.sync_to_registry()
231
+
232
+ return message
233
+
234
+
235
+
236
+ def effective_tools(self) -> list[Tool]:
237
+
238
+ """当前传入模型的全部工具实例。"""
239
+
240
+ return self._agent.context.tools.effective_tools()
241
+
242
+
243
+
244
+ async def run(
245
+
246
+ self,
247
+
248
+ *,
249
+
250
+ user_message: str,
251
+
252
+ speaker: str = "user",
253
+
254
+ ) -> str:
255
+
256
+ """
257
+
258
+ 追加用户消息后运行一轮 ReAct,并将助手回复写入历史。
259
+
260
+
261
+
262
+ Args:
263
+
264
+ user_message: 本轮用户输入
265
+
266
+ speaker: 用户讲述者显示名,多用户场景下区分身份
267
+
268
+
269
+
270
+ Returns:
271
+
272
+ 助手最终文本
273
+
274
+ """
275
+
276
+ full_system = self.build_system_prompt()
277
+
278
+ if self._memory is not None:
279
+
280
+ self._memory.append(
281
+
282
+ speaker=speaker,
283
+
284
+ role="user",
285
+
286
+ content=user_message,
287
+
288
+ )
289
+
290
+ merged_system, messages = self._memory.context_for_agent(
291
+
292
+ system_prompt=full_system,
293
+
294
+ )
295
+
296
+ else:
297
+
298
+ name = speaker if speaker != "user" else None
299
+
300
+ self._messages.append(
301
+
302
+ ChatMessage(role="user", content=user_message, name=name),
303
+
304
+ )
305
+
306
+ merged_system = full_system
307
+
308
+ messages = list(self._messages)
309
+
310
+
311
+
312
+ output = await self._agent.run_with_system(
313
+
314
+ system_prompt=merged_system,
315
+
316
+ messages=messages,
317
+
318
+ )
319
+
320
+
321
+
322
+ if self._memory is not None:
323
+
324
+ self._memory.append(
325
+
326
+ speaker="assistant",
327
+
328
+ role="assistant",
329
+
330
+ content=output,
331
+
332
+ )
333
+
334
+ else:
335
+
336
+ self._messages.append(ChatMessage(role="assistant", content=output))
337
+
338
+ return output
339
+
340
+
341
+
342
+ async def run_with_messages(
343
+ self,
344
+ *,
345
+ messages: list[ChatMessage],
346
+ system_prompt: str | None = None,
347
+ phase: RunPhase | None = None,
348
+ ) -> str:
349
+
350
+ """
351
+
352
+ 用给定消息列表运行一轮(不自动改写 ``messages`` 属性)。
353
+
354
+
355
+
356
+ Args:
357
+
358
+ messages: 完整消息列表(通常含历史)
359
+
360
+ system_prompt: 覆盖默认规则拼装的完整系统提示;规划步执行时传入
361
+
362
+
363
+
364
+ Returns:
365
+
366
+ 助手最终文本
367
+
368
+ """
369
+
370
+ prompt = (
371
+
372
+ system_prompt
373
+
374
+ if system_prompt is not None
375
+
376
+ else self.build_system_prompt()
377
+
378
+ )
379
+
380
+ return await self._agent.run_with_system(
381
+ system_prompt=prompt,
382
+ messages=messages,
383
+ phase=phase,
384
+ )
385
+
386
+
387
+
388
+ async def run_with_plan(
389
+
390
+ self,
391
+
392
+ *,
393
+
394
+ user_message: str,
395
+
396
+ speaker: str = "user",
397
+
398
+ extra_planning_context: str = "",
399
+
400
+ ) -> PlanRunResult:
401
+
402
+ """
403
+
404
+ 先规划串行步骤,再逐步 ReAct 执行,返回计划与各步输出。
405
+
406
+
407
+
408
+ Args:
409
+
410
+ user_message: 本轮用户输入
411
+
412
+ speaker: 用户讲述者显示名
413
+
414
+ extra_planning_context: 拼入规划阶段的附加说明
415
+
416
+ Returns:
417
+
418
+ 计划、各步完整输出与最终回答
419
+
420
+ """
421
+
422
+ from ai_agent.plan.runner import PlanRunner
423
+
424
+ runner = PlanRunner(
425
+ self._agent.context.llm,
426
+ listeners=self._agent.context.listeners,
427
+ )
428
+
429
+ return await runner.run(
430
+
431
+ self,
432
+
433
+ user_message=user_message,
434
+
435
+ speaker=speaker,
436
+
437
+ extra_planning_context=extra_planning_context,
438
+
439
+ )
440
+
441
+
442
+
443
+ def _prepare_plan_run(
444
+
445
+ self,
446
+
447
+ *,
448
+
449
+ user_message: str,
450
+
451
+ system_prompt: str,
452
+
453
+ speaker: str,
454
+
455
+ ) -> tuple[list[ChatMessage], str]:
456
+
457
+ if self._memory is not None:
458
+
459
+ self._memory.append(
460
+
461
+ speaker=speaker,
462
+
463
+ role="user",
464
+
465
+ content=user_message,
466
+
467
+ )
468
+
469
+ merged_system, messages = self._memory.context_for_agent(
470
+
471
+ system_prompt=system_prompt,
472
+
473
+ )
474
+
475
+ return list(messages), merged_system
476
+
477
+ name = speaker if speaker != "user" else None
478
+
479
+ self._messages.append(
480
+
481
+ ChatMessage(role="user", content=user_message, name=name),
482
+
483
+ )
484
+
485
+ return list(self._messages), system_prompt
486
+
487
+
488
+
489
+ def _finish_plan_run(
490
+
491
+ self,
492
+
493
+ *,
494
+
495
+ user_message: str,
496
+
497
+ final_output: str,
498
+
499
+ speaker: str,
500
+
501
+ ) -> None:
502
+
503
+ del user_message, speaker
504
+
505
+ from ai_agent.app.output_format import parse_structured_run_output
506
+
507
+ answer, _files = parse_structured_run_output(final_output)
508
+
509
+ assistant_content = answer if answer else final_output.strip()
510
+
511
+ if self._memory is not None:
512
+
513
+ self._memory.append(
514
+
515
+ speaker="assistant",
516
+
517
+ role="assistant",
518
+
519
+ content=assistant_content,
520
+
521
+ )
522
+
523
+ return
524
+
525
+ self._messages.append(
526
+ ChatMessage(role="assistant", content=assistant_content),
527
+ )
528
+
529
+
530
+
531
+ def build_system_prompt(self) -> str:
532
+
533
+ """
534
+
535
+ 由规则文件拼成的系统提示(skill 正文在启用后注入单次运行上下文)。
536
+
537
+ Returns:
538
+
539
+ 无内容时为空字符串
540
+
541
+ """
542
+
543
+ parts: list[str] = []
544
+
545
+ rules_block = self._agent.rules.build_system_prompt()
546
+
547
+ if rules_block:
548
+
549
+ parts.append(rules_block)
550
+
551
+ return "\n\n".join(parts)
552
+
553
+
554
+
555
+ def _require_skill_manager(self) -> SkillManager:
556
+
557
+ if self._skill_manager is None:
558
+
559
+ raise ValueError("本会话未配置 skill_roots")
560
+
561
+ return self._skill_manager
562
+
563
+
564
+
565
+
566
+
567
+ def build_session(
568
+
569
+ *,
570
+
571
+ session_id: str,
572
+
573
+ workspace: Path,
574
+
575
+ harness_workspace: Path,
576
+
577
+ skill_roots: (
578
+
579
+ Mapping[str, Path | str] | Sequence[Path | str] | Path | str | None
580
+
581
+ ),
582
+
583
+ rule_paths: Sequence[Path | str] | None,
584
+
585
+ api_key: str,
586
+
587
+ model: str,
588
+
589
+ base_url: str,
590
+
591
+ temperature: float | None,
592
+
593
+ max_tokens: int | None,
594
+
595
+ thinking_enabled: bool = False,
596
+
597
+ max_steps: int,
598
+
599
+ extra_tools: list[Tool],
600
+
601
+ listeners: list[AgentListener],
602
+
603
+ memory: MemorySystem | None = None,
604
+
605
+ harness_enabled: bool = False,
606
+
607
+ current_time_tool_enabled: bool = True,
608
+
609
+ ) -> AgentSession:
610
+ """
611
+ 组装单会话的沙箱、分层工具表与对话代理。
612
+
613
+ Args:
614
+ session_id: 会话标识
615
+ workspace: 会话根目录
616
+ harness_workspace: 沙箱读写工作区目录(一般为会话下 harness 子目录)
617
+ skill_roots: 技能仓库根;未配置则不挂载技能
618
+ rule_paths: 系统规则文件路径列表
619
+ api_key: 语言模型 API 密钥
620
+ model: 模型名
621
+ base_url: API 地址
622
+ temperature: 采样温度
623
+ max_tokens: 单次补全 token 上限
624
+ thinking_enabled: 是否在请求中开启思考模式
625
+ max_steps: ReAct 最大步数
626
+ extra_tools: 额外工具列表(如 MCP)
627
+ listeners: 运行时监听列表
628
+ memory: 已构造的分层记忆;未传则为 None
629
+ harness_enabled: 为 False 时不向模型注册 Harness 沙箱工具
630
+ current_time_tool_enabled: 为 True 时注册 ``builtin__current_time``(与 Harness 无关)
631
+
632
+ Returns:
633
+ 可运行的 AgentSession
634
+ """
635
+
636
+ from ai_agent.builtin_tools import (
637
+ build_app_builtin_tools,
638
+ harness_current_time_tool_name,
639
+ )
640
+
641
+ skill_manager: SkillManager | None = None
642
+
643
+ skill_kit: SkillKit | None = None
644
+
645
+ if skill_roots is not None:
646
+
647
+ skill_kit = SkillKit(skill_roots)
648
+
649
+ skill_manager = skill_kit.manager
650
+
651
+ harness_kwargs: dict[str, Any] = {}
652
+
653
+ if skill_kit is not None:
654
+
655
+ harness_kwargs["skill_kit"] = skill_kit
656
+
657
+ harness = Harness(harness_workspace, **harness_kwargs)
658
+
659
+ registry = ToolRegistry()
660
+
661
+ base_tools = build_app_builtin_tools(current_time=current_time_tool_enabled)
662
+ if harness_enabled:
663
+ harness_tools = harness.build_tools()
664
+ if current_time_tool_enabled:
665
+ drop = harness_current_time_tool_name()
666
+ harness_tools = [tool for tool in harness_tools if tool.name != drop]
667
+ base_tools = base_tools + harness_tools
668
+ registry.set_base_tools(base_tools)
669
+
670
+ if skill_manager is not None:
671
+
672
+ skill_manager.bind_registry(registry)
673
+
674
+ skill_manager.sync_to_registry()
675
+
676
+ if extra_tools:
677
+
678
+ registry.set_extra_tools(extra_tools)
679
+
680
+ agent = Agent(
681
+
682
+ api_key=api_key,
683
+
684
+ model=model,
685
+
686
+ base_url=base_url,
687
+
688
+ temperature=temperature,
689
+
690
+ max_tokens=max_tokens,
691
+
692
+ thinking_enabled=thinking_enabled,
693
+
694
+ tools=registry,
695
+
696
+ rule_paths=rule_paths,
697
+
698
+ max_steps=max_steps,
699
+
700
+ listeners=listeners,
701
+
702
+ )
703
+
704
+ if skill_manager is not None:
705
+
706
+ def on_run_begin(run: RunContext) -> None:
707
+ skill_manager.begin_run(run)
708
+
709
+ def on_run_end(_run: RunContext) -> None:
710
+ skill_manager.end_run()
711
+
712
+ agent.context.on_run_begin = on_run_begin
713
+ agent.context.on_run_end = on_run_end
714
+
715
+ return AgentSession(
716
+
717
+ session_id=session_id,
718
+
719
+ workspace=workspace,
720
+
721
+ harness=harness,
722
+
723
+ agent=agent,
724
+
725
+ skill_manager=skill_manager,
726
+
727
+ memory=memory,
728
+
729
+ )
730
+
731
+
732
+
733
+
734
+
735
+ def normalize_session_listeners(
736
+
737
+ listeners: AgentListener | Iterable[AgentListener] | None,
738
+
739
+ ) -> list[AgentListener]:
740
+
741
+ return normalize_listeners(listeners)
742
+