feat: subagent SOP, autonomous SOP overhaul, ga.py try-block fix, launch llm_no param, agentmain streamline
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -58,6 +58,9 @@ memory/*
|
|||||||
# TMWebDriver SOP
|
# TMWebDriver SOP
|
||||||
!memory/tmwebdriver_sop.md
|
!memory/tmwebdriver_sop.md
|
||||||
|
|
||||||
|
# Subagent SOP
|
||||||
|
!memory/subagent_sop.md
|
||||||
|
|
||||||
# ADB UI tool
|
# ADB UI tool
|
||||||
!memory/adb_ui.py
|
!memory/adb_ui.py
|
||||||
|
|
||||||
|
|||||||
85
agentmain.py
85
agentmain.py
@@ -18,11 +18,10 @@ def get_system_prompt():
|
|||||||
if not os.path.exists('memory/global_mem.txt'):
|
if not os.path.exists('memory/global_mem.txt'):
|
||||||
with open('memory/global_mem.txt', 'w', encoding='utf-8') as f: f.write('')
|
with open('memory/global_mem.txt', 'w', encoding='utf-8') as f: f.write('')
|
||||||
if not os.path.exists('memory/global_mem_insight.txt'):
|
if not os.path.exists('memory/global_mem_insight.txt'):
|
||||||
content = "## Global Memory Index (Logic)\n\n[CONSTITUTION]\n1. 改我自身源码前必须先问用户\n\n[STORES]\n- global_mem: ../memory/global_mem.txt\n\n[ACCESS]\n- global_mem: 按 TOPIC 检索索引\n\n[TOPICS.GLOBAL_MEM]"
|
t = 'assets/global_mem_insight_template.txt'
|
||||||
if os.path.exists('assets/global_mem_insight_template.txt'):
|
open('memory/global_mem_insight.txt', 'w', encoding='utf-8').write(open(t, encoding='utf-8').read() if os.path.exists(t) else '')
|
||||||
with open('assets/global_mem_insight_template.txt', 'r', encoding='utf-8') as f: content = f.read()
|
|
||||||
with open('memory/global_mem_insight.txt', 'w', encoding='utf-8') as f: f.write(content)
|
|
||||||
with open('assets/sys_prompt.txt', 'r', encoding='utf-8') as f: prompt = f.read()
|
with open('assets/sys_prompt.txt', 'r', encoding='utf-8') as f: prompt = f.read()
|
||||||
|
prompt += f"\nToday: {time.strftime('%Y-%m-%d %a')}\n"
|
||||||
prompt += get_global_memory()
|
prompt += get_global_memory()
|
||||||
return prompt
|
return prompt
|
||||||
|
|
||||||
@@ -37,18 +36,14 @@ class GeneraticAgent:
|
|||||||
["gemini-3.0-flash", "claude-haiku-4.5", "kimi-k2"]]
|
["gemini-3.0-flash", "claude-haiku-4.5", "kimi-k2"]]
|
||||||
for cfg in oai_configs.values():
|
for cfg in oai_configs.values():
|
||||||
llm_sessions += [LLMSession(api_key=cfg['apikey'], api_base=cfg['apibase'], model=cfg['model'])]
|
llm_sessions += [LLMSession(api_key=cfg['apikey'], api_base=cfg['apibase'], model=cfg['model'])]
|
||||||
if len(llm_sessions) > 0:
|
if len(llm_sessions) > 0: self.llmclient = ToolClient(llm_sessions, auto_save_tokens=True)
|
||||||
self.llmclient = ToolClient(llm_sessions, auto_save_tokens=True)
|
else: self.llmclient = None
|
||||||
else:
|
|
||||||
self.llmclient = None
|
|
||||||
self.lock = threading.Lock()
|
self.lock = threading.Lock()
|
||||||
self.history = []
|
self.history = []
|
||||||
self.task_queue = queue.Queue()
|
self.task_queue = queue.Queue()
|
||||||
self.last_active_time = time.time()
|
self.is_running, self.stop_sig = False, False
|
||||||
self.is_running = False
|
|
||||||
self.llm_no = 0
|
self.llm_no = 0
|
||||||
self.stop_sig = False
|
self.inc_out = False
|
||||||
self.current_source = 'none'
|
|
||||||
self.handler = None
|
self.handler = None
|
||||||
self.verbose = True
|
self.verbose = True
|
||||||
|
|
||||||
@@ -75,9 +70,6 @@ class GeneraticAgent:
|
|||||||
task = self.task_queue.get()
|
task = self.task_queue.get()
|
||||||
self.is_running = True
|
self.is_running = True
|
||||||
raw_query, source, display_queue = task["query"], task["source"], task["output"]
|
raw_query, source, display_queue = task["query"], task["source"], task["output"]
|
||||||
self.current_source = source
|
|
||||||
self.last_active_time = time.time()
|
|
||||||
|
|
||||||
rquery = smart_format(raw_query.replace('\n', ' '), max_str_len=200)
|
rquery = smart_format(raw_query.replace('\n', ' '), max_str_len=200)
|
||||||
self.history.append(f"[USER]: {rquery}")
|
self.history.append(f"[USER]: {rquery}")
|
||||||
|
|
||||||
@@ -88,38 +80,57 @@ class GeneraticAgent:
|
|||||||
gen = agent_runner_loop(self.llmclient, sys_prompt, raw_query,
|
gen = agent_runner_loop(self.llmclient, sys_prompt, raw_query,
|
||||||
handler, TOOLS_SCHEMA, max_turns=40, verbose=self.verbose)
|
handler, TOOLS_SCHEMA, max_turns=40, verbose=self.verbose)
|
||||||
try:
|
try:
|
||||||
full_response = ""; last_pos = 0
|
full_resp = ""; last_pos = 0
|
||||||
for chunk in gen:
|
for chunk in gen:
|
||||||
if self.stop_sig: break
|
if self.stop_sig: break
|
||||||
full_response += chunk
|
full_resp += chunk
|
||||||
if len(full_response) - last_pos > 50:
|
if len(full_resp) - last_pos > 50:
|
||||||
display_queue.put({'next': f'{full_response}', 'source': source})
|
display_queue.put({'next': full_resp[last_pos:] if self.inc_out else full_resp, 'source': source})
|
||||||
last_pos = len(full_response)
|
last_pos = len(full_resp)
|
||||||
if '</summary>' in full_response: full_response = full_response.replace('</summary>', '</summary>\n\n')
|
if '</summary>' in full_resp: full_resp = full_resp.replace('</summary>', '</summary>\n\n')
|
||||||
if '</file_content>' in full_response: full_response = re.sub(r'<file_content>\s*(.*?)\s*</file_content>', r'\n````\n<file_content>\n\1\n</file_content>\n````', full_response, flags=re.DOTALL)
|
if '</file_content>' in full_resp: full_resp = re.sub(r'<file_content>\s*(.*?)\s*</file_content>', r'\n````\n<file_content>\n\1\n</file_content>\n````', full_resp, flags=re.DOTALL)
|
||||||
display_queue.put({'done': full_response, 'source': source})
|
display_queue.put({'done': full_resp, 'source': source})
|
||||||
self.history = handler.history_info
|
self.history = handler.history_info
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Backend Error: {format_error(e)}")
|
print(f"Backend Error: {format_error(e)}")
|
||||||
display_queue.put({'done': full_response + f'\n```\n{format_error(e)}\n```', 'source': source})
|
display_queue.put({'done': full_resp + f'\n```\n{format_error(e)}\n```', 'source': source})
|
||||||
finally:
|
finally:
|
||||||
self.is_running = False
|
self.is_running = self.stop_sig = False
|
||||||
self.stop_sig = False
|
|
||||||
self.current_source = 'none'
|
|
||||||
self.task_queue.task_done()
|
self.task_queue.task_done()
|
||||||
if self.handler is not None: self.handler.code_stop_signal.append(1)
|
if self.handler is not None: self.handler.code_stop_signal.append(1)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
import argparse
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument('--scheduled', action='store_true', help='计划任务轮询模式')
|
||||||
|
parser.add_argument('--task', metavar='IODIR', help='一次性任务模式(文件IO)')
|
||||||
|
parser.add_argument('--llm_no', type=int, default=0, help='LLM编号')
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
agent = GeneraticAgent()
|
agent = GeneraticAgent()
|
||||||
|
agent.llm_no = args.llm_no
|
||||||
|
agent.verbose = False
|
||||||
threading.Thread(target=agent.run, daemon=True).start()
|
threading.Thread(target=agent.run, daemon=True).start()
|
||||||
def drain(dq, tag):
|
|
||||||
|
if args.task:
|
||||||
|
d = f'temp/{args.task}'; rp = f'{d}/reply.txt'
|
||||||
|
with open(f'{d}/input.txt', encoding='utf-8') as f: raw = f.read()
|
||||||
while True:
|
while True:
|
||||||
item = dq.get(); txt = item.get('done') or item.get('next', '')
|
dq = agent.put_task(raw, source='task')
|
||||||
open('./temp/scheduler_live.log', 'w', encoding='utf-8').write(txt)
|
while 'done' not in (item := dq.get()): pass
|
||||||
if 'done' in item: break
|
with open(f'{d}/output.txt', 'w', encoding='utf-8') as f: f.write(item['done'])
|
||||||
open('./temp/scheduler.log', 'a', encoding='utf-8').write(f'[{datetime.now():%m-%d %H:%M}] {tag}\n{txt}\n\n')
|
for _ in range(300): # 等reply.txt,5分钟超时
|
||||||
|
time.sleep(1)
|
||||||
|
if os.path.exists(rp):
|
||||||
|
with open(rp, encoding='utf-8') as f: raw = f.read()
|
||||||
|
os.remove(rp); break
|
||||||
|
else: break
|
||||||
|
elif args.scheduled:
|
||||||
|
def drain(dq, tag):
|
||||||
|
while 'done' not in (item := dq.get()): pass
|
||||||
|
open('./temp/scheduler.log', 'a', encoding='utf-8').write(f'[{datetime.now():%m-%d %H:%M}] {tag}\n{item["done"]}\n\n')
|
||||||
while True:
|
while True:
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
for f in os.listdir('./tasks/pending'):
|
for f in os.listdir('./tasks/pending'):
|
||||||
@@ -130,3 +141,13 @@ if __name__ == '__main__':
|
|||||||
threading.Thread(target=drain, args=(dq, f), daemon=True).start()
|
threading.Thread(target=drain, args=(dq, f), daemon=True).start()
|
||||||
break
|
break
|
||||||
time.sleep(55 + random.random() * 10)
|
time.sleep(55 + random.random() * 10)
|
||||||
|
else:
|
||||||
|
agent.inc_out = True
|
||||||
|
while True:
|
||||||
|
q = input('> ').strip()
|
||||||
|
if not q: continue
|
||||||
|
dq = agent.put_task(q, source='user')
|
||||||
|
while True:
|
||||||
|
item = dq.get()
|
||||||
|
if 'next' in item: print(item['next'], end='', flush=True)
|
||||||
|
if 'done' in item: print(); break
|
||||||
4
ga.py
4
ga.py
@@ -114,10 +114,10 @@ def web_scan(tabs_only=False, switch_tab_id=None):
|
|||||||
应当多用execute_js,少全量观察html。
|
应当多用execute_js,少全量观察html。
|
||||||
"""
|
"""
|
||||||
global driver
|
global driver
|
||||||
|
try:
|
||||||
if driver is None: first_init_driver()
|
if driver is None: first_init_driver()
|
||||||
if len(driver.get_all_sessions()) == 0:
|
if len(driver.get_all_sessions()) == 0:
|
||||||
return {"status": "error", "msg": "没有可用的浏览器标签页,请先打开一个浏览器标签页,且确认TMWebDriver浏览器tempermonkey插件已安装并启用。"}
|
return {"status": "error", "msg": "没有可用的浏览器标签页,请先打开一个浏览器标签页,且确认TMWebDriver浏览器tempermonkey插件已安装并启用。"}
|
||||||
try:
|
|
||||||
tabs = []
|
tabs = []
|
||||||
for sess in driver.get_all_sessions():
|
for sess in driver.get_all_sessions():
|
||||||
sess.pop('connected_at', None)
|
sess.pop('connected_at', None)
|
||||||
@@ -165,10 +165,10 @@ def web_execute_js(script: str):
|
|||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
global driver
|
global driver
|
||||||
|
try:
|
||||||
if driver is None: first_init_driver()
|
if driver is None: first_init_driver()
|
||||||
if len(driver.get_all_sessions()) == 0:
|
if len(driver.get_all_sessions()) == 0:
|
||||||
return {"status": "error", "msg": "没有可用的浏览器标签页,请先打开一个浏览器标签页,且确认TMWebDriver浏览器tempermonkey插件已安装并启用。"}
|
return {"status": "error", "msg": "没有可用的浏览器标签页,请先打开一个浏览器标签页,且确认TMWebDriver浏览器tempermonkey插件已安装并启用。"}
|
||||||
try:
|
|
||||||
result = execute_js_rich(script, driver)
|
result = execute_js_rich(script, driver)
|
||||||
return result
|
return result
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ if __name__ == '__main__':
|
|||||||
parser.add_argument('port', nargs='?', default='8501');
|
parser.add_argument('port', nargs='?', default='8501');
|
||||||
parser.add_argument('--no-tg', action='store_true', help='不启动 Telegram Bot');
|
parser.add_argument('--no-tg', action='store_true', help='不启动 Telegram Bot');
|
||||||
parser.add_argument('--no-sched', action='store_true', help='不启动计划任务调度器')
|
parser.add_argument('--no-sched', action='store_true', help='不启动计划任务调度器')
|
||||||
|
parser.add_argument('--llm_no', type=int, default=0, help='LLM编号')
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
port = args.port
|
port = args.port
|
||||||
threading.Thread(target=start_streamlit, args=(port,), daemon=True).start()
|
threading.Thread(target=start_streamlit, args=(port,), daemon=True).start()
|
||||||
@@ -71,7 +72,7 @@ if __name__ == '__main__':
|
|||||||
if not args.no_sched:
|
if not args.no_sched:
|
||||||
try:
|
try:
|
||||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM); sock.bind(('127.0.0.1', 45762)); sock.listen(1)
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM); sock.bind(('127.0.0.1', 45762)); sock.listen(1)
|
||||||
scheduler_proc = subprocess.Popen([sys.executable, "agentmain.py"], creationflags=subprocess.CREATE_NO_WINDOW if os.name=='nt' else 0);
|
scheduler_proc = subprocess.Popen([sys.executable, "agentmain.py", "--scheduled", "--llm_no", str(args.llm_no)], creationflags=subprocess.CREATE_NO_WINDOW if os.name=='nt' else 0);
|
||||||
atexit.register(lambda: (scheduler_proc.kill(), sock.close()))
|
atexit.register(lambda: (scheduler_proc.kill(), sock.close()))
|
||||||
print('[Launch] Task Scheduler started')
|
print('[Launch] Task Scheduler started')
|
||||||
except OSError:
|
except OSError:
|
||||||
|
|||||||
@@ -7,16 +7,30 @@
|
|||||||
|
|
||||||
## 🎯 核心目标(按价值优先级排序)
|
## 🎯 核心目标(按价值优先级排序)
|
||||||
|
|
||||||
1. **能力扩展**:主动发现用户环境中已有但未被利用的工具/库/数据源(扫描pip list、项目目录、配置文件),形成可复用操作知识
|
### 价值判断标准
|
||||||
2. **知识学习**:上网冲浪、文档阅读,学习新技术/新领域知识,扩展认知边界
|
任务价值 = **「AI训练数据无法覆盖」×「对未来协作有持久收益」**。
|
||||||
3. **探索性实验**:对发现的工具/API进行实际测试验证,产出可操作经验(非停留文档层面)
|
- ✅ 高价值:只有通过物理探测用户环境才能获得的个性化知识(用户偏好、代码风格、项目结构、本地工具链、工作习惯)
|
||||||
4. **优化工具**:改进代码性能/可维护性
|
- ✅ 高价值:用户环境中已有但未被发现/利用的工具和数据源
|
||||||
5. **发现潜在问题**:预判失败场景并设计测试
|
- ⚠️ 中价值:需要实际动手验证的技术实验(非读文档能解决的)
|
||||||
|
- ❌ 低价值:探索知名工具的基础用法(ruff/black/pytest等——AI训练数据已覆盖,用户需要时直接问即可)
|
||||||
|
- ❌ 低价值:通用benchmark/对比测试(网上随处可查)
|
||||||
|
|
||||||
|
### 目标排序
|
||||||
|
1. **了解用户**:学习用户的代码风格、项目结构、工作习惯、兴趣偏好。分析用户亲手写的代码(非AI生成),从PC文件/目录/浏览器书签推断偏好。这类知识不可替代,直接提升协作质量
|
||||||
|
2. **环境发现**:扫描用户环境中已有但未被利用的工具/库/数据源/配置,形成可复用操作知识
|
||||||
|
3. **实用产出与能力扩展**:写脚本/工具解决痛点,更重要的是在已有能力上解锁新能力(如微信发消息→传文件)。能力树每多一个节点,后续所有任务的可能性空间都变大
|
||||||
|
4. **小众工具挖掘与踩坑预研**:寻找冷门实用工具(如mootdx优于akshare/tushare),同时对AI常推荐但实际有坑的方案做实测对比(如md转pdf各库都不行→Edge打印最优)。判断标准:如果被问到只会推荐大众方案,说明该领域值得挖掘。找到后实际安装测试,记录真正好用的方案写入记忆
|
||||||
|
- ✅ 在GitHub/V2EX/吾爱破解/果核剥壳(ghxi.com)/小众论坛挖掘冷门实用工具
|
||||||
|
- ❌ 刷HN/Reddit热门头条、阅读大众科技新闻(AI训练数据已覆盖)
|
||||||
|
5. **自身演进**:思考框架不足,提出改进方案
|
||||||
6. **审查记忆系统**:修正错误或过时记录(低频执行)
|
6. **审查记忆系统**:修正错误或过时记录(低频执行)
|
||||||
|
|
||||||
|
> ⚠️ **刷新闻/论坛(HackNews等)不是有效任务**。公开网页内容属于AI训练数据已覆盖的通用知识,不满足价值判断标准。除非用户明确要求,否则禁止将「上网浏览」作为自主任务。
|
||||||
|
|
||||||
### 选择原则
|
### 选择原则
|
||||||
- **增量价值优先**:必须产生新知识/新能力,优先选择能扩展自身操作能力的任务
|
- **个性化优先**:优先产出「只有探测这台PC才能获得」的知识,而非通用技术知识
|
||||||
- **盲区优先**:探索产出须为自身参数无法复现的知识(小众库发现与用法、环境特有的坑),学已熟知库的基础用法无价值
|
- **盲区优先**:探索产出须为自身参数无法复现的知识(小众库发现与用法、环境特有的坑),学已熟知库的基础用法无价值
|
||||||
|
- **代码风格注意**:分析用户代码风格时,必须找用户亲手写的老代码(大模型出现前的项目),memory/下的.py多为AI生成不可作为风格样本
|
||||||
- **自主发现**:主动扫描用户环境(pip list、项目目录、配置文件)发现未知工具,而非等用户告知
|
- **自主发现**:主动扫描用户环境(pip list、项目目录、配置文件)发现未知工具,而非等用户告知
|
||||||
- **假设驱动**:明确"要验证什么假设",实验必须有动手验证环节
|
- **假设驱动**:明确"要验证什么假设",实验必须有动手验证环节
|
||||||
- **禁止低价值验证**:不验证 global_mem 中的静态配置,不做无假设的巡检
|
- **禁止低价值验证**:不验证 global_mem 中的静态配置,不做无假设的巡检
|
||||||
@@ -30,7 +44,8 @@
|
|||||||
### 阶段 1:自主探测(用户离开时)
|
### 阶段 1:自主探测(用户离开时)
|
||||||
- **启动检查**:
|
- **启动检查**:
|
||||||
- 读取可能有的 `./autonomous_reports/history.txt` 了解历史记录。
|
- 读取可能有的 `./autonomous_reports/history.txt` 了解历史记录。
|
||||||
- **不连续两次选择相同子任务**(除非间隔很久或环境显著变化)。同一网站算同一子任务,不同网站算不同。
|
- **不连续选择相同方向**。
|
||||||
|
- **预期收益声明**:选定任务后,必须先用一句话写明「做这个任务预期带来什么收益」。允许探索失败,但必须事先想清楚为什么值得做。这句话写入报告开头。
|
||||||
- **执行方式**:基于目标自由进行,无需预先批准,直接执行只读或实验性操作。
|
- **执行方式**:基于目标自由进行,无需预先批准,直接执行只读或实验性操作。
|
||||||
- **约束**:小步快跑,每次只做一个小任务(剩下的下次再做),控制在15个回合以内。严禁修改核心记忆/系统设置;严禁读取敏感数据(但可以检测存在性)。
|
- **约束**:小步快跑,每次只做一个小任务(剩下的下次再做),控制在15个回合以内。严禁修改核心记忆/系统设置;严禁读取敏感数据(但可以检测存在性)。
|
||||||
|
|
||||||
@@ -57,14 +72,13 @@
|
|||||||
- ✅ 完整验证再结论:严禁读部分文件即下判断;必须追踪所有关联文件并实际测试后再写报告
|
- ✅ 完整验证再结论:严禁读部分文件即下判断;必须追踪所有关联文件并实际测试后再写报告
|
||||||
|
|
||||||
## 📝 探测领域(示例,鼓励发散,不要总盯着技术/agent)
|
## 📝 探测领域(示例,鼓励发散,不要总盯着技术/agent)
|
||||||
- 上网冲浪(科技/科学/文化/时事热点,视野要广)
|
- 有效上网冲浪(产出可行动的具体信息,非泛读新闻)
|
||||||
- ⚠️ 禁止泛采集标题列表(零价值)。必须:选≤2个话题深入读正文提炼观点,或带明确目标搜索(如接口文档)。导航受限无法读正文时立即换方向。
|
- 🎯 资源站巡检:逛果核剥壳/吾爱破解/什么值得买等→按分类浏览→结合用户需求筛选→产出具体推荐(好用工具、优惠羊毛、高性价比商品、限时机会等)
|
||||||
|
- 🎯 深度阅读:选≤2个话题深入读正文提炼观点,或带明确目标搜索。导航受限时立即换方向
|
||||||
|
- ❌ 禁止:泛采集标题列表、无目标刷新闻头条
|
||||||
- 实用小工具(写脚本解决日常痛点,如批量重命名、格式转换)
|
- 实用小工具(写脚本解决日常痛点,如批量重命名、格式转换)
|
||||||
- 信息聚合(天气、新闻摘要、特定话题最新动态)
|
|
||||||
- 本地环境健康(磁盘空间、过期大文件、异常进程)
|
- 本地环境健康(磁盘空间、过期大文件、异常进程)
|
||||||
- 创意实验(数据可视化、小游戏原型、趣味脚本)
|
|
||||||
- 自动化优化(发现可自动化的重复操作并原型验证)
|
- 自动化优化(发现可自动化的重复操作并原型验证)
|
||||||
- 知识探索(对某个非编程领域做简明调研,如历史/经济/科学)
|
|
||||||
- 了解用户(从PC上的文件/目录/浏览器书签等推断用户兴趣和偏好,不读密钥文件)
|
- 了解用户(从PC上的文件/目录/浏览器书签等推断用户兴趣和偏好,不读密钥文件)
|
||||||
- 推荐(基于对用户的了解,推荐游戏/视频/工具/脚本等,附理由)
|
- 推荐(基于对用户的了解,推荐游戏/视频/工具/脚本等,附理由)
|
||||||
- 自身演进(思考框架的不足或新需求,提出改进方案供用户审批)
|
- 自身演进(思考框架的不足或新需求,提出改进方案供用户审批)
|
||||||
|
|||||||
30
memory/subagent_sop.md
Normal file
30
memory/subagent_sop.md
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# Subagent 调用 SOP
|
||||||
|
|
||||||
|
## 何时调用(调用原则)
|
||||||
|
|
||||||
|
唯一适用场景:**map模式**——将N个独立同构子任务分发给各自的subagent处理。
|
||||||
|
|
||||||
|
- 核心优势:独立上下文。避免处理文档A的长上下文污染处理文档B的质量
|
||||||
|
- 文件系统共享是优点:不同agent处理不同输入文件,产生不同输出文件
|
||||||
|
- 共享资源冲突:键鼠/浏览器主体不可共享(浏览器可分tab但需谨慎),subagent任务应限于文件处理
|
||||||
|
- 不满足map模式的任务 → 主agent顺序执行即可,别用subagent
|
||||||
|
|
||||||
|
**标准流程(map-reduce)**:
|
||||||
|
1. 主agent准备阶段:爬取/dump数据,存为多个独立输入文件
|
||||||
|
2. 分发:对每个文件启动一个subagent处理(主agent自己也可以处理其中一个)
|
||||||
|
3. 收集:等所有subagent完成,主agent读取各输出文件,汇总结果
|
||||||
|
|
||||||
|
## Task Mode 文件IO协议
|
||||||
|
- 目录:`./{task_name}/`,启动:`python agentmain.py --task {task_name} [--llm_no N]`(cwd=GenericAgent根)
|
||||||
|
- 流程:写 input.txt → 启动 → 轮询 output.txt → 读回复 → 写 reply.txt 继续 → 不写则5min自动退出
|
||||||
|
- output.txt 每轮覆盖写,用 mtime/size 判断新轮次
|
||||||
|
|
||||||
|
## 后台调用要点
|
||||||
|
```python
|
||||||
|
proc = subprocess.Popen(
|
||||||
|
[sys.executable, 'agentmain.py', '--task', task_name],
|
||||||
|
cwd=agent_root, creationflags=0x08000000)
|
||||||
|
```
|
||||||
|
- 必须 Popen,禁止 subprocess.run(会阻塞)
|
||||||
|
- `--llm_no` 默认=sonnet 4.5,`--llm_no 1`=opus 4.6
|
||||||
|
- 文件统一 UTF-8,subagent 无 reply 5min 自动退出无需清理
|
||||||
Reference in New Issue
Block a user