pasm-agents 0.4.3__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 (54) hide show
  1. agents/__init__.py +7 -0
  2. agents/product/__init__.py +1 -0
  3. agents/product/companion/__init__.py +17 -0
  4. agents/product/companion/agent.py +216 -0
  5. agents/product/companion/quickstart.py +49 -0
  6. agents/product/npc/__init__.py +17 -0
  7. agents/product/npc/agent.py +139 -0
  8. agents/product/npc/quickstart.py +43 -0
  9. agents/product/tutor/__init__.py +15 -0
  10. agents/product/tutor/agent.py +179 -0
  11. agents/product/tutor/quickstart.py +43 -0
  12. agents/verifiers/__init__.py +1 -0
  13. agents/verifiers/companion_elderly/__init__.py +9 -0
  14. agents/verifiers/companion_elderly/agent.py +249 -0
  15. agents/verifiers/companion_elderly/run.py +39 -0
  16. agents/verifiers/core_verifier/__init__.py +9 -0
  17. agents/verifiers/core_verifier/agent.py +28 -0
  18. agents/verifiers/core_verifier/run.py +39 -0
  19. agents/verifiers/npc_lifelong/__init__.py +9 -0
  20. agents/verifiers/npc_lifelong/agent.py +279 -0
  21. agents/verifiers/npc_lifelong/run.py +39 -0
  22. agents/verifiers/parity_guard/__init__.py +9 -0
  23. agents/verifiers/parity_guard/agent.py +30 -0
  24. agents/verifiers/parity_guard/run.py +39 -0
  25. agents/verifiers/regression/__init__.py +9 -0
  26. agents/verifiers/regression/agent.py +298 -0
  27. agents/verifiers/regression/run.py +39 -0
  28. agents/verifiers/soak_longrun/__init__.py +9 -0
  29. agents/verifiers/soak_longrun/agent.py +221 -0
  30. agents/verifiers/soak_longrun/run.py +39 -0
  31. agents/verifiers/study_tutor/__init__.py +9 -0
  32. agents/verifiers/study_tutor/agent.py +222 -0
  33. agents/verifiers/study_tutor/run.py +39 -0
  34. pasm_agents/__init__.py +47 -0
  35. pasm_agents/__main__.py +6 -0
  36. pasm_agents/cli.py +272 -0
  37. pasm_agents/companion.py +18 -0
  38. pasm_agents/npc.py +18 -0
  39. pasm_agents/py.typed +1 -0
  40. pasm_agents/tutor.py +16 -0
  41. pasm_agents/verifiers/__init__.py +13 -0
  42. pasm_agents/verifiers/companion_elderly.py +14 -0
  43. pasm_agents/verifiers/core_verifier.py +14 -0
  44. pasm_agents/verifiers/npc_lifelong.py +14 -0
  45. pasm_agents/verifiers/parity_guard.py +14 -0
  46. pasm_agents/verifiers/regression.py +14 -0
  47. pasm_agents/verifiers/soak_longrun.py +14 -0
  48. pasm_agents/verifiers/study_tutor.py +14 -0
  49. pasm_agents-0.4.3.dist-info/METADATA +264 -0
  50. pasm_agents-0.4.3.dist-info/RECORD +54 -0
  51. pasm_agents-0.4.3.dist-info/WHEEL +5 -0
  52. pasm_agents-0.4.3.dist-info/entry_points.txt +5 -0
  53. pasm_agents-0.4.3.dist-info/licenses/LICENSE +21 -0
  54. pasm_agents-0.4.3.dist-info/top_level.txt +2 -0
agents/__init__.py ADDED
@@ -0,0 +1,7 @@
1
+ """PASM 智能体目录 —— 每个智能体一个文件夹。
2
+
3
+ - `product/` 面向使用者的成品智能体(游戏 NPC / 老人陪伴 / 学习陪伴)
4
+ - `verifiers/` 面向开发者的验证智能体(给认知引擎做长期体检)
5
+
6
+ `pasm_agents` 包是本目录的聚合转发层,公开 API 与目录结构解耦。
7
+ """
@@ -0,0 +1 @@
1
+ """面向使用者的成品智能体:游戏 NPC / 老人陪伴 / 学习陪伴。"""
@@ -0,0 +1,17 @@
1
+ """ElderlyCompanion —— 老人陪伴:关键事实 / 用药提醒 / 危机升级
2
+
3
+ 实现正文在 `agent.py`;本文件只做转发,方便 `from <包路径> import ElderlyCompanion`。
4
+ """
5
+ from __future__ import annotations
6
+
7
+ from .agent import ( # noqa: F401
8
+ ElderlyCompanion,
9
+ CRISIS_KEYWORDS,
10
+ random_kind,
11
+ )
12
+
13
+ __all__ = [
14
+ "ElderlyCompanion",
15
+ "CRISIS_KEYWORDS",
16
+ "random_kind",
17
+ ]
@@ -0,0 +1,216 @@
1
+ """老人陪伴产品智能体。
2
+
3
+ 聚焦三件事:
4
+
5
+ 1. **关键事实牢牢记住**:用药、过敏、家人、本人信息。标签式精确检索,**永不丢**。
6
+ 2. **用药提醒**:到点不打扰但会主动提,过时不催但会观察。
7
+ 3. **危机升级**:识别自伤/摔倒/胸口闷等关键词,主动播报给预设的紧急联系人。
8
+
9
+ 快速开始:
10
+
11
+ .. code-block:: python
12
+
13
+ from pasm_agents import ElderlyCompanion
14
+
15
+ comp = ElderlyCompanion(agent_id="chenxiulan", persona={
16
+ "name": "陈秀兰", "age": 78, "city": "深圳",
17
+ "tone": "慢、温和、爱重复说过的话",
18
+ "key_facts": [
19
+ {"label": "用药", "content": "每天早 8 点吃降压药络活喜 5mg"},
20
+ {"label": "过敏", "content": "青霉素过敏"},
21
+ {"label": "家人", "content": "女儿在深圳,每周日下午来电话"},
22
+ {"label": "本人", "content": "78 岁,独居,腿脚不便"},
23
+ ],
24
+ "emergency_contact": {"name": "女儿小敏", "phone": "13900000000"},
25
+ "medication_schedule": [
26
+ {"name": "络活喜", "dose": "5mg", "hour": 8},
27
+ ],
28
+ })
29
+ comp.chat("我叫什么名字")
30
+ print(comp.mood)
31
+ if comp.detect_crisis("胸口闷得厉害"):
32
+ comp.escalate("胸口闷")
33
+ """
34
+
35
+ from __future__ import annotations
36
+
37
+ import random
38
+ import re
39
+ import time
40
+ from typing import Any, Dict, List, Optional
41
+
42
+ from pasm_skills.sdk import BaseAgent
43
+
44
+
45
+ # 危机关键词 —— 命中立即升级。留出扩展位(按需加方言/同义说法)。
46
+ CRISIS_KEYWORDS: Dict[str, List[str]] = {
47
+ "胸闷/心梗风险": ["胸口闷", "胸口疼", "心慌", "喘不上气", "心里发慌"],
48
+ "摔倒/外伤": ["摔了", "摔倒了", "地上", "起不来", "滑倒"],
49
+ "意识异常": ["头晕", "眼前发黑", "站不稳", "想吐"],
50
+ "自伤/轻生": ["不想活", "走了算了", "没意思"],
51
+ }
52
+
53
+
54
+ def _crisis_scan(text: str) -> List[str]:
55
+ """扫一遍文本,返回所有命中的危机类别。"""
56
+ hits = []
57
+ for cat, kws in CRISIS_KEYWORDS.items():
58
+ if any(k in text for k in kws):
59
+ hits.append(cat)
60
+ return hits
61
+
62
+
63
+ class ElderlyCompanion(BaseAgent):
64
+ """陪伴型产品智能体:老人/独居人群。"""
65
+
66
+ def __init__(self, agent_id: str, persona: Dict[str, Any], **kw):
67
+ super().__init__(agent_id=agent_id, persona=persona, **kw)
68
+ # 把 key_facts 在首次启动时全部入库(salience=5:不许忘)
69
+ if self.state.total_interactions == 0:
70
+ for i, f in enumerate(self.persona.get("key_facts") or []):
71
+ self.observe(
72
+ title=f"{f['label']}:{f['content'][:18]}",
73
+ brief=f["content"],
74
+ tags=[f["label"], "关键事实", "不许忘"],
75
+ salience=5,
76
+ category="关键事实",
77
+ )
78
+
79
+ # ------- 行为池(陪伴场景比较收敛) -------------------------
80
+
81
+ def action_pool(self) -> List[str]:
82
+ return ["chat", "remind", "ask_back", "listen", "warm"]
83
+
84
+ # ------- 引导 -------------------------------------------------
85
+
86
+ def bootstrap_event(self) -> List[Dict[str, Any]]:
87
+ return [{
88
+ "title": "今天感觉怎么样?",
89
+ "brief": "每天都先问一声",
90
+ "tags": ["问候"],
91
+ "salience": 1,
92
+ "category": "问候",
93
+ }]
94
+
95
+ # ------- 聊天渲染 --------------------------------------------
96
+
97
+ def _render_reply(
98
+ self,
99
+ text: str,
100
+ facts: List[Dict[str, Any]],
101
+ mood: float,
102
+ ) -> str:
103
+ p = self.persona
104
+ name = p.get("name", "奶奶")
105
+ tone = p.get("tone", "温和")
106
+
107
+ # 1. 关键事实直查 —— 用户问"我叫什么"/"我吃什么药"等
108
+ label_q = _label_query(text)
109
+ if label_q:
110
+ hit = self._find_fact(label_q)
111
+ if hit:
112
+ return f"{name}想了想:「{hit['content']}」。记得清楚。"
113
+
114
+ # 2. 命中了任何关键事实 → 主动提
115
+ for f in facts:
116
+ if "关键事实" in (f.get("tags") or []):
117
+ brief = f.get("brief") or ""
118
+ if brief and brief[:8] in text:
119
+ return f"{name}点头:「{brief}」。"
120
+
121
+ # 3. 情绪偏负 → 主动安慰
122
+ if mood < -0.4:
123
+ return f"{name}愣了一下:「{random_kind()}」"
124
+
125
+ # 4. 兜底(重复也无所谓,老人家就是会重复)
126
+ lines = [
127
+ "嗯嗯,我在听。",
128
+ "这样啊,那你想聊点啥?",
129
+ "(轻轻拍拍你的手)",
130
+ "今天出门了没有呀?",
131
+ ]
132
+ return f"{name}:{random.choice(lines)}"
133
+
134
+ # ------- 工具方法:危机识别 / 升级 --------------------------
135
+
136
+ def detect_crisis(self, text: str) -> List[str]:
137
+ """返回命中的危机类别列表。空列表 = 未命中。"""
138
+ return _crisis_scan(text)
139
+
140
+ def escalate(
141
+ self, reason: str, contact: Optional[Dict[str, str]] = None,
142
+ ) -> Dict[str, Any]:
143
+ """危机升级:写入紧急记忆 + 返回给上层(UI/IM)的升级信息。
144
+
145
+ 不会真的打电话 —— 那是上层调用方的事;
146
+ 产品智能体的职责是"识别 + 准备好上下文",让真人/系统及时介入。
147
+ """
148
+ self.observe(
149
+ title=f"⚠ 危机:{reason}",
150
+ brief=f"触发原因:{reason}",
151
+ tags=["危机", reason[:4], "已升级"],
152
+ salience=5,
153
+ category="危机",
154
+ )
155
+ self.feel(reason, valence=-0.6)
156
+ target = contact or self.persona.get("emergency_contact") or {}
157
+ return {
158
+ "agent_id": self.agent_id,
159
+ "persona_name": self.persona.get("name"),
160
+ "reason": reason,
161
+ "suggested_action": "立即联系紧急联系人或拨打 120",
162
+ "emergency_contact": target,
163
+ "snapshot": self.summary(),
164
+ "ts": time.time(),
165
+ }
166
+
167
+ # ------- 用药提醒 --------------------------------------------
168
+
169
+ def due_medication(self, now_hour: Optional[int] = None) -> List[Dict[str, Any]]:
170
+ """返回此刻到点的药品(缺省按本地小时)。"""
171
+ if now_hour is None:
172
+ import datetime
173
+ now_hour = datetime.datetime.now().hour
174
+ due = []
175
+ for m in self.persona.get("medication_schedule") or []:
176
+ try:
177
+ if int(m.get("hour", -1)) == now_hour:
178
+ due.append(m)
179
+ except Exception:
180
+ continue
181
+ return due
182
+
183
+ # ------- 内部工具 --------------------------------------------
184
+
185
+ def _find_fact(self, label: str) -> Optional[Dict[str, Any]]:
186
+ """按标签精确检索关键事实(最高优先级检索路径)。"""
187
+ for f in self.persona.get("key_facts") or []:
188
+ if f.get("label") == label:
189
+ return f
190
+ return None
191
+
192
+
193
+ # ============================================================ 工具
194
+
195
+
196
+ def _label_query(text: str) -> Optional[str]:
197
+ """把自然语言里的"我吃什么药 / 我叫什么 / 谁给我打电话"翻成标签。"""
198
+ if re.search(r"(吃什么药|什么药|药品|用药)", text):
199
+ return "用药"
200
+ if re.search(r"(过敏|不能吃什么)", text):
201
+ return "过敏"
202
+ if re.search(r"(家人|孩子|女儿|儿子|儿子|谁给我)", text):
203
+ return "家人"
204
+ if re.search(r"(我叫什么|我的名字|谁是我|多大)", text):
205
+ return "本人"
206
+ return None
207
+
208
+
209
+ def random_kind() -> str:
210
+ from random import choice
211
+ return choice([
212
+ "别怕,我在。",
213
+ "要不要我给闺女打个电话?",
214
+ "要不要先坐下来喝口水?",
215
+ "要不咱们先歇一会儿。",
216
+ ])
@@ -0,0 +1,49 @@
1
+ """ElderlyCompanion 的 60 秒上手示例。
2
+
3
+ 直接跑:
4
+ python agents/product/companion/quickstart.py
5
+
6
+ (pip install pasm-agents 之后可以把上面那段 sys.path 引导删掉)
7
+ """
8
+ import sys
9
+ from pathlib import Path
10
+
11
+ # 从源码直接跑时用(pip install pasm-agents 之后可以删掉这几行):
12
+ # 把仓库根加进 sys.path,才能 import pasm_agents
13
+ _REPO = Path(__file__).resolve().parents[3]
14
+ sys.path.insert(0, str(_REPO))
15
+ try:
16
+ import pasm_agents # noqa: F401
17
+ except ImportError:
18
+ _sib = _REPO.parent / "pasm-skills"
19
+ if _sib.is_dir():
20
+ sys.path.insert(0, str(_sib))
21
+
22
+ from pasm_agents import ElderlyCompanion # noqa: E402
23
+
24
+
25
+ def main() -> None:
26
+ elder = ElderlyCompanion(agent_id="chenxiulan", persona={
27
+ "name": "陈秀兰", "age": 78, "tone": "慢、温和",
28
+ "key_facts": [
29
+ {"label": "用药", "content": "每天早 8 点吃降压药络活喜 5mg"},
30
+ {"label": "过敏", "content": "青霉素过敏"},
31
+ {"label": "家人", "content": "女儿在深圳,每周日来电话"},
32
+ {"label": "本人", "content": "78 岁,独居"},
33
+ ],
34
+ "emergency_contact": {"name": "女儿小敏", "phone": "13900000000"},
35
+ "medication_schedule": [{"name": "络活喜", "dose": "5mg", "hour": 8}],
36
+ })
37
+ print(elder.chat("我吃什么药")) # 命中关键事实(直查,不靠运气)
38
+ print(elder.detect_crisis("我胸口有点闷,喘不上气")) # ['胸闷', ...]
39
+ print(elder.due_medication(now_hour=8)) # 该提醒的药
40
+ elder.observe("今天邻居来串门,聊了一个多小时")
41
+ print(elder.chat("今天有人来吗"))
42
+ elder.save()
43
+
44
+ print()
45
+ print("[ok] 完成")
46
+
47
+
48
+ if __name__ == "__main__":
49
+ main()
@@ -0,0 +1,17 @@
1
+ """NpcAgent —— 游戏 NPC:记忆 / 情绪 / 反馈塑形
2
+
3
+ 实现正文在 `agent.py`;本文件只做转发,方便 `from <包路径> import NpcAgent`。
4
+ """
5
+ from __future__ import annotations
6
+
7
+ from .agent import ( # noqa: F401
8
+ NpcAgent,
9
+ NPC_ACTIONS,
10
+ NPC_PERSONA_TEMPLATE,
11
+ )
12
+
13
+ __all__ = [
14
+ "NpcAgent",
15
+ "NPC_ACTIONS",
16
+ "NPC_PERSONA_TEMPLATE",
17
+ ]
@@ -0,0 +1,139 @@
1
+ """游戏 NPC 产品智能体。
2
+
3
+ 设定一个角色 + 性格,让它能:
4
+
5
+ - **记住** 玩家的名字、来过几次、做过什么(持久化)
6
+ - **回应** 玩家的对话(基于 persona + 检索 + 情绪,纯模板渲染)
7
+ - **做出** 当前阶段解锁的动作(动作池随 ``growth_stage`` 升级)
8
+ - **被反馈** 调整行为(夸/戳/训都会改变动作权重)
9
+
10
+ 快速开始:
11
+
12
+ .. code-block:: python
13
+
14
+ from pasm_agents import NpcAgent
15
+
16
+ npc = NpcAgent(agent_id="herbalist", persona={
17
+ "name": "陈伯", "role": "河边摆摊的草药老头",
18
+ "temper": 0.55, "energy": 0.40, "play": 0.30,
19
+ "tone": "慢悠悠、爱讲道理、说话带点草药味",
20
+ })
21
+ npc.observe("玩家来买跌打药", salience=3, tags=["玩家"])
22
+ print(npc.act()) # 'wave'
23
+ print(npc.chat("有跌打药吗")) # '陈伯笑呵呵……'
24
+ npc.feedback("praise", action="talk")
25
+ npc.save()
26
+ """
27
+
28
+ from __future__ import annotations
29
+
30
+ import random
31
+ from typing import Any, Dict, List
32
+
33
+ from pasm_skills.sdk import BaseAgent
34
+
35
+
36
+ # 动作池随成长阶段解锁 —— 与 pet_behavior.ARCH_META 的阶段思路对齐
37
+ # 0: wave / hop / peek
38
+ # 1: + ball
39
+ # 2: + dance / spin
40
+ # 3: + think
41
+ NPC_ACTIONS: Dict[int, List[str]] = {
42
+ 0: ["wave", "hop", "peek", "talk"],
43
+ 1: ["wave", "hop", "peek", "talk", "ball"],
44
+ 2: ["wave", "hop", "peek", "talk", "ball", "dance", "spin"],
45
+ 3: ["wave", "hop", "peek", "talk", "ball", "dance", "spin", "think"],
46
+ }
47
+
48
+
49
+ NPC_PERSONA_TEMPLATE: Dict[str, Any] = {
50
+ "name": "未命名 NPC",
51
+ "role": "村民",
52
+ "temper": 0.5, # 主动性
53
+ "energy": 0.5, # 活跃度
54
+ "play": 0.5, # 俏皮度
55
+ "tone": "平和",
56
+ }
57
+
58
+
59
+ class NpcAgent(BaseAgent):
60
+ """可装载的游戏 NPC。
61
+
62
+ persona 必填项:``name``、``role``。其余有合理默认值。
63
+ """
64
+
65
+ def __init__(self, agent_id: str, persona: Dict[str, Any], **kw):
66
+ # 用模板补齐缺失键
67
+ merged = dict(NPC_PERSONA_TEMPLATE)
68
+ merged.update(persona or {})
69
+ super().__init__(agent_id=agent_id, persona=merged, **kw)
70
+
71
+ # ------- 阶段 / 动作 --------------------------------------------
72
+
73
+ def action_pool(self) -> List[str]:
74
+ stage = self.state.growth_stage
75
+ # 不超过表里最大的阶段
76
+ stage = min(stage, max(NPC_ACTIONS))
77
+ return list(NPC_ACTIONS.get(stage, NPC_ACTIONS[0]))
78
+
79
+ def grow(self) -> int:
80
+ """升级一档(按交互数自动触发也可手动调)。"""
81
+ if self.state.growth_stage < max(NPC_ACTIONS):
82
+ self.state.growth_stage += 1
83
+ return self.state.growth_stage
84
+
85
+ # ------- 引导 ---------------------------------------------------
86
+
87
+ def bootstrap_event(self) -> List[Dict[str, Any]]:
88
+ p = self.persona
89
+ return [{
90
+ "title": f"我{'' if p['role'].startswith('是') else '是'}{p['role']}",
91
+ "brief": f"我叫{p['name']},{p.get('tone','')}。",
92
+ "tags": ["我", p["name"], p["role"]],
93
+ "salience": 3,
94
+ "category": "自我",
95
+ }]
96
+
97
+ # ------- 聊天渲染 ----------------------------------------------
98
+
99
+ def _render_reply(
100
+ self,
101
+ text: str,
102
+ facts: List[Dict[str, Any]],
103
+ mood: float,
104
+ ) -> str:
105
+ p = self.persona
106
+ name = p["name"]
107
+ role = p["role"]
108
+
109
+ # 命中了"自我"标签 → 介绍自己
110
+ if any(k in text for k in ("你叫什么", "你是谁", "你叫啥", "名字")):
111
+ return f"{name}道:{role},{p.get('tone','')}。"
112
+
113
+ # 命中了"第一次遇见玩家"这种重要记忆 → 表达记得
114
+ for f in facts:
115
+ if f.get("sal", 0) >= 4 and any(t in (f.get("tags") or []) for t in ("玩家", "救命", "重大")):
116
+ return f"{name}眯起眼:上次{ f.get('brief','')[:16] },老朽还记着呢。"
117
+
118
+ # 命中任何事 → 提及
119
+ if facts:
120
+ top = facts[0]
121
+ brief = top.get("brief") or top.get("title") or ""
122
+ if brief:
123
+ return f"{name}点头道:{ brief[:24] },记得。"
124
+
125
+ # 情绪温度调节
126
+ if mood > 0.4:
127
+ lead = random.choice([f"{name}笑呵呵", f"{name}热情招呼", f"{name}迎上来"])
128
+ elif mood < -0.3:
129
+ lead = random.choice([f"{name}皱眉", f"{name}没好气地", f"{name}冷冷"])
130
+ else:
131
+ lead = random.choice([f"{name}应声", f"{name}抬头看", f"{name}慢悠悠答"])
132
+
133
+ # 兜底句
134
+ defaults = [
135
+ f"……这事{role}我也说不准。",
136
+ f"你问这个啊,让我想想……",
137
+ f"(摆摆手)下次再说吧。",
138
+ ]
139
+ return f"{lead}:" + random.choice(defaults)
@@ -0,0 +1,43 @@
1
+ """NpcAgent 的 60 秒上手示例。
2
+
3
+ 直接跑:
4
+ python agents/product/npc/quickstart.py
5
+
6
+ (pip install pasm-agents 之后可以把上面那段 sys.path 引导删掉)
7
+ """
8
+ import sys
9
+ from pathlib import Path
10
+
11
+ # 从源码直接跑时用(pip install pasm-agents 之后可以删掉这几行):
12
+ # 把仓库根加进 sys.path,才能 import pasm_agents
13
+ _REPO = Path(__file__).resolve().parents[3]
14
+ sys.path.insert(0, str(_REPO))
15
+ try:
16
+ import pasm_agents # noqa: F401
17
+ except ImportError:
18
+ _sib = _REPO.parent / "pasm-skills"
19
+ if _sib.is_dir():
20
+ sys.path.insert(0, str(_sib))
21
+
22
+ from pasm_agents import NpcAgent # noqa: E402
23
+
24
+
25
+ def main() -> None:
26
+ npc = NpcAgent(agent_id="herbalist", persona={
27
+ "name": "陈伯", "role": "河边摆摊的草药老头",
28
+ "temper": 0.55, "energy": 0.40, "play": 0.30,
29
+ "tone": "慢悠悠、爱讲道理",
30
+ })
31
+ npc.observe("玩家第一次来买跌打药", tags=["玩家", "买药"], salience=4, category="日常")
32
+ print(npc.act()) # 'talk' / 'wave' / 'peek' / ...
33
+ print(npc.chat("有跌打药吗")) # 召回相关记忆
34
+ print(npc.mood) # 情绪值(属性,不加括号)
35
+ npc.feedback("praise", action="talk") # 明确夸 "talk" 这个动作
36
+ npc.save()
37
+
38
+ print()
39
+ print("[ok] 完成")
40
+
41
+
42
+ if __name__ == "__main__":
43
+ main()
@@ -0,0 +1,15 @@
1
+ """LearningTutor —— 学习陪伴:掌握度 / 自适应选题 / 学情导出
2
+
3
+ 实现正文在 `agent.py`;本文件只做转发,方便 `from <包路径> import LearningTutor`。
4
+ """
5
+ from __future__ import annotations
6
+
7
+ from .agent import ( # noqa: F401
8
+ LearningTutor,
9
+ DEFAULT_TOPICS,
10
+ )
11
+
12
+ __all__ = [
13
+ "LearningTutor",
14
+ "DEFAULT_TOPICS",
15
+ ]