feat: --func pure function mode for subagent; merge bg launch; refine SOP
This commit is contained in:
1 parent
66b964bdf0
commit
5bc4945dcb
4 files changed
+49
-44
No files matched your search
+22
-16
@@ -1,4 +1,4 @@
|
||||
import os, sys, threading, queue, time, json, re, random, locale
|
||||
import os, sys, threading, queue, time, json, re, random, locale, glob
|
||||
os.environ.setdefault('GA_LANG', 'zh' if any(k in (locale.getlocale()[0] or '').lower() for k in ('zh', 'chinese')) else 'en')
|
||||
if sys.stdout is None: sys.stdout = open(os.devnull, "w")
|
||||
elif hasattr(sys.stdout, 'reconfigure'): sys.stdout.reconfigure(errors='replace')
|
||||
@@ -178,8 +178,6 @@ class GenericAgent:
|
||||
if self.inc_out and last_pos < len(full_resp):
|
||||
display_queue.put({'next': full_resp[last_pos:], 'source': source,
|
||||
'turn': curr_turn, 'outputs': turn_resps[-2:]})
|
||||
#if '</summary>' in full_resp: full_resp = full_resp.replace('</summary>', '</summary>\n\n')
|
||||
#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_resp, 'source': source, 'turn': curr_turn, 'outputs': turn_resps.copy()})
|
||||
self.history = handler.history_info
|
||||
except Exception as e:
|
||||
@@ -198,6 +196,7 @@ if __name__ == '__main__':
|
||||
from datetime import datetime
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--task', metavar='IODIR', help='一次性任务模式,先看subagent.md')
|
||||
parser.add_argument('--func', metavar='PROMPT_FILE', help='纯函数模式:读prompt文件→结果写prompt.out.txt→退出')
|
||||
parser.add_argument('--reflect', metavar='SCRIPT', help='反射模式:加载监控脚本,check()触发时发任务')
|
||||
parser.add_argument('--input', help='prompt')
|
||||
parser.add_argument('--llm_no', type=int, default=0)
|
||||
@@ -206,14 +205,17 @@ if __name__ == '__main__':
|
||||
args, _unknown = parser.parse_known_args()
|
||||
_reflect_args = dict(zip([k.lstrip('-') for k in _unknown[::2]], _unknown[1::2])) if _unknown else {}
|
||||
|
||||
if args.task and not args.nobg:
|
||||
if (args.func or args.task) and not args.nobg:
|
||||
import subprocess, platform
|
||||
cmd = [sys.executable, os.path.abspath(__file__)] + [a for a in sys.argv[1:]] + ['--nobg']
|
||||
d = os.path.join(script_dir, f'temp/{args.task}'); os.makedirs(d, exist_ok=True)
|
||||
if args.task:
|
||||
d = os.path.join(script_dir, f'temp/{args.task}'); os.makedirs(d, exist_ok=True)
|
||||
out = open(os.path.join(d, 'stdout.log'), 'w', encoding='utf-8')
|
||||
err = open(os.path.join(d, 'stderr.log'), 'w', encoding='utf-8')
|
||||
else: out, err = subprocess.DEVNULL, subprocess.DEVNULL
|
||||
p = subprocess.Popen(cmd, cwd=script_dir,
|
||||
creationflags=0x08000000 if platform.system() == 'Windows' else 0,
|
||||
stdout=open(os.path.join(d, 'stdout.log'), 'w', encoding='utf-8'),
|
||||
stderr=open(os.path.join(d, 'stderr.log'), 'w', encoding='utf-8'))
|
||||
stdout=out, stderr=err)
|
||||
print('PID:', p.pid); sys.exit(0)
|
||||
|
||||
agent = GeneraticAgent()
|
||||
@@ -222,31 +224,35 @@ if __name__ == '__main__':
|
||||
threading.Thread(target=agent.run, daemon=True).start()
|
||||
|
||||
if args.task:
|
||||
agent.peer_hint = False
|
||||
agent.force_non_stream = True
|
||||
agent.task_dir = d = os.path.join(script_dir, f'temp/{args.task}'); nround = ''
|
||||
infile = os.path.join(d, 'input.txt')
|
||||
infile = os.path.join(d, 'input.txt'); outfile = f'{d}/output{nround}.txt'
|
||||
if args.input:
|
||||
os.makedirs(d, exist_ok=True)
|
||||
import glob; [os.remove(f) for f in glob.glob(os.path.join(d, 'output*.txt'))]
|
||||
[os.remove(f) for f in glob.glob(os.path.join(d, 'output*.txt'))]
|
||||
with open(infile, 'w', encoding='utf-8') as f: f.write(args.input)
|
||||
if (fh := consume_file(d, '_history.json')): agent.llmclient.backend.history = json.loads(fh)
|
||||
elif args.func:
|
||||
infile = args.func; outfile = os.path.splitext(args.func)[0] + '.out.txt'
|
||||
|
||||
if args.func or args.task:
|
||||
agent.peer_hint = False
|
||||
with open(infile, encoding='utf-8') as f: raw = f.read()
|
||||
while True:
|
||||
dq = agent.put_task(raw, source='task')
|
||||
dq = agent.put_task(raw, source='func' if args.func else 'task')
|
||||
while 'done' not in (item := dq.get(timeout=1200)):
|
||||
if 'next' in item and random.random() < 0.95: # 概率写一次中间结果
|
||||
with open(f'{d}/output{nround}.txt', 'w', encoding='utf-8') as f: f.write(item.get('next', ''))
|
||||
with open(f'{d}/output{nround}.txt', 'w', encoding='utf-8') as f: f.write(item['done'] + '\n\n[ROUND END]\n')
|
||||
if 'next' in item:
|
||||
with open(outfile, 'w', encoding='utf-8') as f: f.write(item.get('next', ''))
|
||||
with open(outfile, 'w', encoding='utf-8') as f: f.write(item['done'] + '\n\n[ROUND END]\n')
|
||||
if not args.task: break
|
||||
consume_file(d, '_stop') # 已经成功停下来了,避免打断下次reply
|
||||
for _ in range(300): # 等reply.txt,10分钟超时
|
||||
time.sleep(2)
|
||||
if (raw := consume_file(d, 'reply.txt')): break
|
||||
else: break
|
||||
nround = nround + 1 if isinstance(nround, int) else 1
|
||||
outfile = f'{d}/output{nround}.txt'
|
||||
elif args.reflect:
|
||||
agent.peer_hint = False
|
||||
agent.force_non_stream = True
|
||||
import importlib.util
|
||||
spec = importlib.util.spec_from_file_location('reflect_script', args.reflect)
|
||||
mod = importlib.util.module_from_spec(spec); spec.loader.exec_module(mod)
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
"width": 1280,
|
||||
"height": 800,
|
||||
"resizable": true,
|
||||
"maximized": true,
|
||||
"maximized": false,
|
||||
"visible": false,
|
||||
"url": "loading.html"
|
||||
},
|
||||
|
||||
@@ -12,7 +12,8 @@
|
||||
| 3 vision(VLM) | 2仍不足时才用 | 仅语义理解、确认界面状态、辅助判断目标 | 不可信其坐标 |
|
||||
|
||||
Windows 下窗口截图和操作使用 ljqCtrl:严禁 pyautogui;记得先 Activate 到前台;
|
||||
坑1-DPI:一律物理坐标;坑2-遮盖/失焦:混乱先枚举窗口确认前台;
|
||||
ui_detect附送OCR,不要单独使用OCR
|
||||
坑1-DPI:必须先import ljqCtrl,之后一律使用物理坐标;坑2-遮盖/失焦:混乱先枚举窗口确认前台;
|
||||
ui_detect 的 bbox 是截图内坐标,点击前必须用 `ClientToScreen(hwnd,(0,0))/dpi_scale + bbox中心` 转屏幕物理坐标
|
||||
坐标转换禁用 `GetWindowRect` 或 DWM 窗口矩形直接加截图坐标(含标题栏/边框/阴影会错位)
|
||||
ljqCtrl.Click 后会返回像素/前台变化,0% 或近 0% 变化立即停下诊断,禁止盲目重试。
|
||||
|
||||
+24
-26
@@ -1,40 +1,38 @@
|
||||
# Subagent 调用 SOP
|
||||
|
||||
## 文件IO协议
|
||||
## 两种模式
|
||||
|
||||
- 目录:`temp/{task_name}/`(cwd在temp/时即`./{task_name}/`)
|
||||
- 启动:`python agentmain.py --task {name} [--input "短文本"] [--llm_no N]`(cwd=代码根)
|
||||
### --func 纯函数模式
|
||||
- `python agentmain.py --func prompt.txt [--llm_no N]`(cwd=代码根)
|
||||
- 读prompt文件→执行→结果写`prompt.out.txt`→退出,主agent读完可删
|
||||
- 后台启动(print PID),加`--nobg`前台同步等结果
|
||||
- 适用:单次任务、并行map、不需要追问的场景
|
||||
|
||||
### --task 持续协作模式
|
||||
- `python agentmain.py --task {name} [--input "短文本"] [--llm_no N]`(cwd=代码根)
|
||||
- `--input`自动建目录+清旧output+写input.txt;长文本先手动写input.txt再启动(不带--input)
|
||||
- 自动后台启动,print PID then exit
|
||||
- 统一设定:所有agent的cwd都是temp,方便文件共享,不是task目录
|
||||
- input:目标+约束即可,subagent同等智能。**禁写步骤/过度描述**,大量数据给路径
|
||||
- 通信:output.txt(append,`[ROUND END]`=轮完成) → 写reply.txt继续 → 不写10min退出。reply后输出为output1/2/3.txt(同格式)
|
||||
- **不要--nobg**(会卡在等reply循环),只能后台启动
|
||||
- 通信:output.txt(`[ROUND END]`=轮完成) → 写reply.txt继续 → 不写10min退出。reply后输出为output1/2/3.txt
|
||||
- 干预文件:`_stop`(当轮结束) | `_keyinfo`(注入working memory) | `_intervene`(追加指令)
|
||||
- [[可选fork功能]](继承对话上下文): 事先code_run(inline_eval=True),将变量history(自动注入,str)写入task目录下_history.json
|
||||
- [[可选监察者模式]]:**主agent空闲时应读output观察进度,必要时用干预文件纠偏,禁止无脑长时间sleep**
|
||||
若加`--verbose`,output将包含工具执行结果,主agent可直接审查原始数据而非仅信任摘要
|
||||
- [[可选fork]]:将变量history(str)写入task目录下`_history.json`继承对话上下文
|
||||
- [[可选监察者]]:主agent空闲时读output观察进度,必要时干预文件纠偏。加`--verbose`可审查原始数据
|
||||
|
||||
## 共通规则
|
||||
- 所有agent的cwd=temp,方便文件共享
|
||||
- input:目标+约束即可,subagent同等智能。**禁写步骤/过度描述**,大量数据给路径
|
||||
|
||||
## 场景1:测试模式 - 行为验证
|
||||
**用途**:观察agent真实行为,修正RULES/L2/L3/SOP
|
||||
**流程**:创建test_path/写input.txt→启动subagent→轮询output.txt(2秒间隔)→验证→清理重复
|
||||
**测试原则**:只给目标,不提示位置/不诱导做法,观察自主选择
|
||||
**修正闭环**:发现问题→设计测试→定位根源(RULES/L2/L3/SOP)→patch修正→验证
|
||||
**技术要点**:Insight优先级>SOP;subagent的cwd=temp/
|
||||
**流程**:写prompt→启动subagent→轮询结果→验证→清理
|
||||
**原则**:只给目标,不提示位置/不诱导做法;Insight优先级>SOP;subagent的cwd=temp/
|
||||
**两种测试**:
|
||||
- 测SOP质量:input指定SOP名(如"用ezgmail_sop查看最近3封未读邮件"),排除导航干扰,失败即SOP问题
|
||||
- 测导航能力:input只写目标,验证subagent能自主从insight找到正确SOP。禁止内联SOP内容
|
||||
- 测SOP质量:input指定SOP名,排除导航干扰,失败即SOP问题
|
||||
- 测导航能力:input只写目标,验证能自主从insight找到正确SOP
|
||||
|
||||
## 场景2:Map模式 - 并行处理
|
||||
**用途**:将N个独立同构子任务分发给各自的subagent处理
|
||||
**核心优势**:独立上下文。避免处理文档A的长上下文污染处理文档B的质量
|
||||
**约束**:
|
||||
- 文件系统共享是优点:不同agent处理不同输入文件,产生不同输出文件
|
||||
- 共享资源冲突:键鼠不可共享;浏览器避免操作同一tab
|
||||
- 不满足map模式的任务 → 主agent顺序执行即可,别用subagent
|
||||
**标准流程(map-reduce)**:
|
||||
1. 主agent准备阶段:爬取/dump数据,存为多个独立输入文件
|
||||
2. 分发:对每个文件启动一个subagent处理(主agent自己也可以处理其中一个)
|
||||
3. 收集:等所有subagent完成,主agent读取各输出文件,汇总结果
|
||||
**用途**:N个独立同构子任务分发,独立上下文避免交叉污染
|
||||
**约束**:文件系统共享(优点);键鼠不可共享;浏览器避免同tab
|
||||
**流程**:准备独立输入文件→每个启动subagent(--func优先)→收集输出汇总
|
||||
|
||||
## subagent内部plan_mode使用
|
||||
**原则**:subagent本身是完整agent,接收多步骤任务时应在内部创建plan管理执行
|
||||
|
||||
Reference in new issue
Block a user