feat: task模式干预机制 _stop/_keyinfo/_intervene + consume_file
This commit is contained in:
15
agentmain.py
15
agentmain.py
@@ -7,7 +7,7 @@ sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
|||||||
|
|
||||||
from llmcore import SiderLLMSession, LLMSession, ToolClient, ClaudeSession, MixinSession, NativeToolClient, NativeClaudeSession, build_multimodal_content, NativeOAISession
|
from llmcore import SiderLLMSession, LLMSession, ToolClient, ClaudeSession, MixinSession, NativeToolClient, NativeClaudeSession, build_multimodal_content, NativeOAISession
|
||||||
from agent_loop import agent_runner_loop
|
from agent_loop import agent_runner_loop
|
||||||
from ga import GenericAgentHandler, smart_format, get_global_memory, format_error
|
from ga import GenericAgentHandler, smart_format, get_global_memory, format_error, consume_file
|
||||||
|
|
||||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
def load_tool_schema(suffix=''):
|
def load_tool_schema(suffix=''):
|
||||||
@@ -63,7 +63,8 @@ class GeneraticAgent:
|
|||||||
except Exception as e: print(f'[WARN] Failed to init MixinSession with cfg {s["mixin_cfg"]}: {e}')
|
except Exception as e: print(f'[WARN] Failed to init MixinSession with cfg {s["mixin_cfg"]}: {e}')
|
||||||
self.llmclients = llm_sessions
|
self.llmclients = llm_sessions
|
||||||
self.lock = threading.Lock()
|
self.lock = threading.Lock()
|
||||||
self.history = []
|
self.task_dir = None
|
||||||
|
self.history = []
|
||||||
self.task_queue = queue.Queue()
|
self.task_queue = queue.Queue()
|
||||||
self.is_running = False; self.stop_sig = False
|
self.is_running = False; self.stop_sig = False
|
||||||
self.llm_no = 0; self.inc_out = False
|
self.llm_no = 0; self.inc_out = False
|
||||||
@@ -125,6 +126,7 @@ class GeneraticAgent:
|
|||||||
try:
|
try:
|
||||||
full_resp = ""; last_pos = 0
|
full_resp = ""; last_pos = 0
|
||||||
for chunk in gen:
|
for chunk in gen:
|
||||||
|
if consume_file(self.task_dir, '_stop'): self.abort()
|
||||||
if self.stop_sig: break
|
if self.stop_sig: break
|
||||||
full_resp += chunk
|
full_resp += chunk
|
||||||
if len(full_resp) - last_pos > 50 or 'LLM Running' in chunk:
|
if len(full_resp) - last_pos > 50 or 'LLM Running' in chunk:
|
||||||
@@ -175,8 +177,8 @@ if __name__ == '__main__':
|
|||||||
threading.Thread(target=agent.run, daemon=True).start()
|
threading.Thread(target=agent.run, daemon=True).start()
|
||||||
|
|
||||||
if args.task:
|
if args.task:
|
||||||
d = os.path.join(script_dir, f'temp/{args.task}'); nround = ''
|
agent.task_dir = d = os.path.join(script_dir, f'temp/{args.task}'); nround = ''
|
||||||
rp = os.path.join(d, 'reply.txt'); infile = os.path.join(d, 'input.txt')
|
infile = os.path.join(d, 'input.txt')
|
||||||
if args.input:
|
if args.input:
|
||||||
os.makedirs(d, exist_ok=True)
|
os.makedirs(d, exist_ok=True)
|
||||||
import glob; [os.remove(f) for f in glob.glob(os.path.join(d, 'output*.txt'))]
|
import glob; [os.remove(f) for f in glob.glob(os.path.join(d, 'output*.txt'))]
|
||||||
@@ -188,11 +190,10 @@ if __name__ == '__main__':
|
|||||||
if 'next' in item and random.random() < 0.95: # 概率写一次中间结果
|
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.get('next', ''))
|
||||||
with open(f'{d}/output{nround}.txt', 'w', encoding='utf-8') as f: f.write(item['done'] + '\n\n[ROUND END]\n')
|
with open(f'{d}/output{nround}.txt', 'w', encoding='utf-8') as f: f.write(item['done'] + '\n\n[ROUND END]\n')
|
||||||
|
consume_file(d, '_stop') # 已经成功停下来了,避免打断下次reply
|
||||||
for _ in range(300): # 等reply.txt,10分钟超时
|
for _ in range(300): # 等reply.txt,10分钟超时
|
||||||
time.sleep(2)
|
time.sleep(2)
|
||||||
if os.path.exists(rp):
|
if (raw := consume_file(d, 'reply.txt')): break
|
||||||
with open(rp, encoding='utf-8') as f: raw = f.read()
|
|
||||||
os.remove(rp); break
|
|
||||||
else: break
|
else: break
|
||||||
nround = nround + 1 if isinstance(nround, int) else 1
|
nround = nround + 1 if isinstance(nround, int) else 1
|
||||||
elif args.reflect:
|
elif args.reflect:
|
||||||
|
|||||||
10
ga.py
10
ga.py
@@ -249,6 +249,12 @@ def smart_format(data, max_str_len=100, omit_str=' ... '):
|
|||||||
if len(data) < max_str_len + len(omit_str)*2: return data
|
if len(data) < max_str_len + len(omit_str)*2: return data
|
||||||
return f"{data[:max_str_len//2]}{omit_str}{data[-max_str_len//2:]}"
|
return f"{data[:max_str_len//2]}{omit_str}{data[-max_str_len//2:]}"
|
||||||
|
|
||||||
|
def consume_file(dr, file):
|
||||||
|
if dr and os.path.exists(os.path.join(dr, file)):
|
||||||
|
with open(os.path.join(dr, file), encoding='utf-8', errors='replace') as f: content = f.read()
|
||||||
|
os.remove(os.path.join(dr, file))
|
||||||
|
return content
|
||||||
|
|
||||||
class GenericAgentHandler(BaseHandler):
|
class GenericAgentHandler(BaseHandler):
|
||||||
'''Generic Agent 工具库,包含多种工具的实现。工具函数自动加上了 do_ 前缀。实际工具名没有前缀。'''
|
'''Generic Agent 工具库,包含多种工具的实现。工具函数自动加上了 do_ 前缀。实际工具名没有前缀。'''
|
||||||
def __init__(self, parent, last_history=None, cwd='./temp'):
|
def __init__(self, parent, last_history=None, cwd='./temp'):
|
||||||
@@ -505,6 +511,10 @@ class GenericAgentHandler(BaseHandler):
|
|||||||
elif turn % 7 == 0:
|
elif turn % 7 == 0:
|
||||||
next_prompt += f"\n\n[DANGER] 已连续执行第 {turn} 轮。禁止无效重试。若无有效进展,必须切换策略:1. 探测物理边界 2. 请求用户协助。如有需要,可调用 update_working_checkpoint 保存关键上下文。"
|
next_prompt += f"\n\n[DANGER] 已连续执行第 {turn} 轮。禁止无效重试。若无有效进展,必须切换策略:1. 探测物理边界 2. 请求用户协助。如有需要,可调用 update_working_checkpoint 保存关键上下文。"
|
||||||
elif turn % 10 == 0: next_prompt += get_global_memory()
|
elif turn % 10 == 0: next_prompt += get_global_memory()
|
||||||
|
injkeyinfo = consume_file(self.parent.task_dir, '_keyinfo')
|
||||||
|
injprompt = consume_file(self.parent.task_dir, '_intervene')
|
||||||
|
if injkeyinfo: self.working['key_info'] = self.working.get('key_info', '') + f"\n[MASTER] {injkeyinfo}"
|
||||||
|
if injprompt: next_prompt += f"\n\n[MASTER] {injprompt}\n"
|
||||||
return next_prompt
|
return next_prompt
|
||||||
|
|
||||||
def get_global_memory():
|
def get_global_memory():
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
- 启动:`python agentmain.py --task {name} [--input "内容"] [--bg] [--llm_no N]`(cwd=代码根)
|
- 启动:`python agentmain.py --task {name} [--input "内容"] [--bg] [--llm_no N]`(cwd=代码根)
|
||||||
- `--input`:自动建目录+清旧output+写input.txt
|
- `--input`:自动建目录+清旧output+写input.txt
|
||||||
- `--bg`:自举后台(Popen自身去掉--bg → print PID → exit)
|
- `--bg`:自举后台(Popen自身去掉--bg → print PID → exit)
|
||||||
- 流程:启动 → 轮询 output.txt(`[ROUND END]`=该轮完成)→ 写 reply.txt 继续 → 不写则5min退出
|
- 流程:启动 → 轮询 output.txt(`[ROUND END]`=该轮完成)→ 写 reply.txt 继续 → 不写则10min退出
|
||||||
|
- 干预文件:`_stop`(当轮结束退出) | `_keyinfo`(写入即注入下轮prompt的key_info) | `_intervene`(写入内容作为下轮追加指令)
|
||||||
- output1/2/3.txt:reply后各轮输出(递增编号),同样append+`[ROUND END]`
|
- output1/2/3.txt:reply后各轮输出(递增编号),同样append+`[ROUND END]`
|
||||||
- input.txt:目标+约束,可指定SOP名。禁写具体步骤(除非已读SOP确认)。大量数据给路径禁塞入
|
- input.txt:目标+约束,可指定SOP名。禁写具体步骤(除非已读SOP确认)。大量数据给路径禁塞入
|
||||||
- ⚠ `--input`走命令行,长文本/含引号会超时。超过一句话时:先写input.txt,启动时不带`--input`
|
- ⚠ `--input`走命令行,长文本/含引号会超时。超过一句话时:先写input.txt,启动时不带`--input`
|
||||||
|
|||||||
Reference in New Issue
Block a user