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,109 @@
1
+ from __future__ import annotations
2
+
3
+ import shutil
4
+ from pathlib import Path
5
+
6
+ _INCOMING_DIR = "incoming"
7
+
8
+
9
+ def reset_harness_workspace(harness_root: Path) -> None:
10
+ """
11
+ 清空 Harness 工作区并重建空目录。
12
+
13
+ Args:
14
+ harness_root: Harness 沙箱根
15
+ """
16
+ root = harness_root.resolve()
17
+ if root.is_dir():
18
+ shutil.rmtree(root)
19
+ root.mkdir(parents=True, exist_ok=True)
20
+
21
+
22
+ def stage_input_files(
23
+ source_paths: tuple[str, ...],
24
+ harness_root: Path,
25
+ ) -> tuple[str, ...]:
26
+ """
27
+ 将用户文件复制到 Harness 的 incoming/ 子目录。
28
+
29
+ Args:
30
+ source_paths: 源文件路径列表
31
+ harness_root: Harness 沙箱根
32
+
33
+ Returns:
34
+ 复制后相对于 Harness 根的路径(incoming/文件名)
35
+
36
+ Raises:
37
+ ValueError: 源路径不存在或不是文件
38
+ """
39
+ if not source_paths:
40
+ return ()
41
+ incoming = harness_root / _INCOMING_DIR
42
+ incoming.mkdir(parents=True, exist_ok=True)
43
+ staged: list[str] = []
44
+ used_names: set[str] = set()
45
+ for raw in source_paths:
46
+ source = Path(raw).expanduser().resolve()
47
+ if not source.is_file():
48
+ raise ValueError(f"输入文件不存在或不是文件: {raw}")
49
+ name = source.name
50
+ if name in used_names:
51
+ stem = source.stem
52
+ suffix = source.suffix
53
+ index = 2
54
+ while name in used_names:
55
+ name = f"{stem}_{index}{suffix}"
56
+ index += 1
57
+ used_names.add(name)
58
+ target = incoming / name
59
+ shutil.copy2(source, target)
60
+ rel = f"{_INCOMING_DIR}/{name}"
61
+ staged.append(rel)
62
+ return tuple(staged)
63
+
64
+
65
+ def resolve_output_files(
66
+ relative_paths: tuple[str, ...],
67
+ harness_root: Path,
68
+ ) -> tuple[str, ...]:
69
+ """
70
+ 将模型给出的相对路径解析为存在的宿主机绝对路径。
71
+
72
+ Args:
73
+ relative_paths: 相对 Harness 根的路径
74
+ harness_root: Harness 沙箱根
75
+
76
+ Returns:
77
+ 仅包含已存在常规文件的路径
78
+ """
79
+ root = harness_root.resolve()
80
+ resolved: list[str] = []
81
+ for raw in relative_paths:
82
+ cleaned = raw.strip().replace("\\", "/")
83
+ if not cleaned:
84
+ continue
85
+ if Path(cleaned).is_absolute():
86
+ candidate = Path(cleaned).resolve()
87
+ try:
88
+ candidate.relative_to(root)
89
+ except ValueError:
90
+ continue
91
+ else:
92
+ candidate = (root / cleaned).resolve()
93
+ try:
94
+ candidate.relative_to(root)
95
+ except ValueError:
96
+ continue
97
+ if candidate.is_file():
98
+ resolved.append(str(candidate))
99
+ return tuple(resolved)
100
+
101
+
102
+ def format_input_files_context(staged_paths: tuple[str, ...]) -> str:
103
+ """生成拼入规划或用户消息的附件说明。"""
104
+ if not staged_paths:
105
+ return ""
106
+ lines = ["## 用户附件(位于 Harness 工作区内)"]
107
+ for rel in staged_paths:
108
+ lines.append(f"- {rel}")
109
+ return "\n".join(lines)
@@ -0,0 +1,77 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from pydantic import BaseModel, Field, ValidationError
6
+
7
+ from ai_agent.json_extract import extract_first_json_object
8
+
9
+ _RUN_OUTPUT_INSTRUCTION = """
10
+ ## 最终交付格式(最后一步必须遵守)
11
+ 完成全部计划步骤后,你的**最终可见回复**必须是**仅含一个 JSON 对象**的文本,不要 Markdown 围栏,不要其它说明。字段:
12
+ - ``answer``:面向用户的完整回答(字符串)
13
+ - ``output_files``:需要交还给用户的文件路径数组,路径**相对 Harness 工作区根**(如 ``incoming/foo.xlsx``、``out/result.csv``);无文件则 ``[]``
14
+
15
+ 格式硬性要求:
16
+ - 整段输出必须是**合法 JSON**(可用单行,或仅在字符串内使用 ``\\n`` 表示换行);``answer`` 内勿写入未转义的真实换行。
17
+ - ``answer`` 字符串值内勿使用未转义的 ASCII 双引号(``"``);须转义为 ``\\"`` 或改写为不含英文双引号的表述。
18
+ - 除 JSON 外不要任何前后缀文字。
19
+ """.strip()
20
+
21
+
22
+ def run_output_instruction() -> str:
23
+ """返回须拼入计划最后一步用户消息末尾的交付格式说明。"""
24
+ return _RUN_OUTPUT_INSTRUCTION
25
+
26
+
27
+ _STEP_INTERMEDIATE_INSTRUCTION = """
28
+ ## 本步产出要求
29
+ 本步不是计划的最后一步:勿输出最终交付用的 ``answer`` / ``output_files`` JSON。
30
+ 首行须写:``会话当前日期:YYYY-MM-DD(来源:builtin__current_time)``(本步已调用该工具时;日期取自工具返回);首行前勿加 ``---`` 或其它前缀。
31
+ 其后用简洁中文记录本步**结论要点**(检索时间窗口、事实、条目),供后续步骤使用;条数贴近用户所要的条数,勿堆砌窗口外月份背景。
32
+ 本步各回合可见回复均用中文;勿写搜索重试过程、搜前搜后寒暄、「再搜一次」「进入下一步」等过程叙述;需要再搜时直接调用工具,勿先发文说明。
33
+ 同回合**禁止**与 ``builtin__current_time``(或 ``harness__current_time``)一并发起 ``bocha_search`` 等其它工具:须先单独一轮取时,待返回后再搜;并行发起时运行层只执行取时,搜索须下回合重写 query。
34
+ 若本步无可写产出可只回复「本步已完成」(已取时时仍保留首行日期行)。
35
+ """.strip()
36
+
37
+
38
+ def step_intermediate_instruction() -> str:
39
+ """返回拼入非最后计划步用户消息的产出说明。"""
40
+ return _STEP_INTERMEDIATE_INSTRUCTION
41
+
42
+
43
+ class _StructuredRunBody(BaseModel):
44
+ answer: str = Field(description="面向用户的回答")
45
+ output_files: list[str] = Field(default_factory=list)
46
+
47
+
48
+ def parse_structured_run_output(text: str) -> tuple[str, tuple[str, ...]]:
49
+ """
50
+ 从模型最终文本解析结构化回答与输出文件列表。
51
+
52
+ Args:
53
+ text: 模型原始输出
54
+
55
+ Returns:
56
+ 回答正文与相对 Harness 的文件路径元组;无法解析 JSON 时整段作为 answer
57
+ """
58
+ stripped = text.strip()
59
+ if not stripped:
60
+ return "", ()
61
+ payload = _extract_json_object(stripped)
62
+ if payload is None:
63
+ return stripped, ()
64
+ try:
65
+ body = _StructuredRunBody.model_validate(payload)
66
+ except ValidationError:
67
+ return stripped, ()
68
+ files = tuple(
69
+ p.strip()
70
+ for p in body.output_files
71
+ if isinstance(p, str) and p.strip()
72
+ )
73
+ return body.answer.strip() or stripped, files
74
+
75
+
76
+ def _extract_json_object(text: str) -> dict[str, Any] | None:
77
+ return extract_first_json_object(text)
ai_agent/app/packet.py ADDED
@@ -0,0 +1,39 @@
1
+ from __future__ import annotations
2
+
3
+ from pydantic import BaseModel, ConfigDict, Field
4
+
5
+
6
+ class RunInputPacket(BaseModel):
7
+ """
8
+ 应用运行入口的输入数据包。
9
+
10
+ 由调用方构造并传入 ``AgentApp.run``;会话状态落在磁盘,单次调用结束后不保留会话实例。
11
+ """
12
+
13
+ model_config = ConfigDict(extra="forbid")
14
+
15
+ user_name: str = Field(description="用户显示名,写入对话与记忆讲述者字段")
16
+ session_id: str = Field(description="会话标识,对应沙箱下 sessions/<id>/ 目录")
17
+ request: str = Field(description="本轮用户要求或任务说明")
18
+ input_files: tuple[str, ...] = Field(
19
+ default_factory=tuple,
20
+ description="用户传入的本地文件绝对或相对路径,将复制到本会话 Harness 的 incoming/ 下",
21
+ )
22
+ clear: bool = Field(
23
+ default=False,
24
+ description="为 True 时清空本会话 Harness 工作区、分层记忆存储与无 Memory 时的对话持久化",
25
+ )
26
+
27
+
28
+ class RunOutputPacket(BaseModel):
29
+ """应用运行入口的输出数据包。"""
30
+
31
+ model_config = ConfigDict(extra="forbid")
32
+
33
+ user_name: str = Field(description="与输入包一致的用户名")
34
+ session_id: str = Field(description="与输入包一致的会话 id")
35
+ answer: str = Field(description="助手面向用户的最终文本")
36
+ output_files: tuple[str, ...] = Field(
37
+ default_factory=tuple,
38
+ description="模型声明须返回的文件路径(已解析为宿主机绝对路径且文件存在)",
39
+ )