From b37b56a780769562e4b5cfd7b1a84cceca9cc6a0 Mon Sep 17 00:00:00 2001 From: Jiaqing Liang Date: Thu, 5 Feb 2026 17:36:30 +0800 Subject: [PATCH] chore: sync core logic updates --- agent_loop.py | 2 +- agentmain.py | 9 +++++++-- ga.py | 16 ++++++++++------ sidercall.py | 48 +++++++++++++++++++++++++++++------------------- 4 files changed, 47 insertions(+), 28 deletions(-) diff --git a/agent_loop.py b/agent_loop.py index bc5ba77..49335c9 100644 --- a/agent_loop.py +++ b/agent_loop.py @@ -26,7 +26,7 @@ class BaseHandler: return ret else: yield f"❌ 未知工具: {tool_name}\n" - return StepOutcome(None, "未知工具", "ERROR") + return StepOutcome(None, next_prompt=f"未知工具 {tool_name}", should_exit=False) def json_default(o): if isinstance(o, set): return list(o) diff --git a/agentmain.py b/agentmain.py index 3023df3..d1bb8d0 100644 --- a/agentmain.py +++ b/agentmain.py @@ -47,14 +47,18 @@ class GeneraticAgent: self.llm_no = 0 self.stop_sig = False self.current_source = 'none' + self.handler = None def next_llm(self): self.llm_no = (self.llm_no + 1) % len(self.llmclient.raw_apis) self.llmclient.last_tools = '' def abort(self): + print('About to abort current task...') if not self.is_running: return self.stop_sig = True + if self.handler is not None: + self.handler.code_stop_signal.append(1) def put_task(self, query, source="user"): self.display_queue.queue.clear() @@ -73,7 +77,7 @@ class GeneraticAgent: sys_prompt = get_system_prompt() handler = GenericAgentHandler(None, self.history, './temp') - + self.handler = handler self.llmclient.raw_api = self.llmclient.raw_apis[self.llm_no] gen = agent_runner_loop(self.llmclient, sys_prompt, raw_query, handler, TOOLS_SCHEMA, max_turns=25) @@ -81,7 +85,8 @@ class GeneraticAgent: try: full_response = ""; last_pos = 0 for chunk in gen: - if self.stop_sig: break + if self.stop_sig: + self.abort(); break full_response += chunk if len(full_response) - last_pos > 50: self.display_queue.put({'next': f'{full_response}', 'source': source}) diff --git a/ga.py b/ga.py index 944b866..20a8ce7 100644 --- a/ga.py +++ b/ga.py @@ -7,7 +7,7 @@ sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) from agent_loop import BaseHandler, StepOutcome, try_call_generator -def code_run(code, code_type="python", timeout=60, cwd=None, code_cwd=None): +def code_run(code, code_type="python", timeout=60, cwd=None, code_cwd=None, stop_signal=[]): """代码执行器 python: 运行复杂的 .py 脚本(文件模式) powershell/bash: 运行单行指令(命令模式) @@ -52,11 +52,14 @@ def code_run(code, code_type="python", timeout=60, cwd=None, code_cwd=None): t.start() while t.is_alive(): - if time.time() - start_t > timeout: + istimeout = time.time() - start_t > timeout + if istimeout or len(stop_signal) > 0: process.kill() - full_stdout.append("\n[Timeout Error] 超时强制终止") + print("[Debug] Process killed due to timeout or stop signal.") + if istimeout: full_stdout.append("\n[Timeout Error] 超时强制终止") + else: full_stdout.append("\n[Stopped] 用户强制终止") break - time.sleep(0.2) + time.sleep(1) t.join(timeout=1) exit_code = process.poll() @@ -242,6 +245,7 @@ class GenericAgentHandler(BaseHandler): self.focus = "" self.cwd = cwd self.history_info = last_history if last_history else [] + self.code_stop_signal = [] def _get_abs_path(self, path): if not path: return "" @@ -274,7 +278,7 @@ class GenericAgentHandler(BaseHandler): raw_path = os.path.join(self.cwd, args.get("cwd", './')) cwd = os.path.normpath(os.path.abspath(raw_path)) code_cwd = os.path.normpath(self.cwd) - result = yield from code_run(code, code_type, timeout, cwd, code_cwd=code_cwd) + result = yield from code_run(code, code_type, timeout, cwd, code_cwd=code_cwd, stop_signal=self.code_stop_signal) next_prompt = self._get_anchor_prompt() + warning return StepOutcome(result, next_prompt=next_prompt) @@ -315,7 +319,7 @@ class GenericAgentHandler(BaseHandler): print("Web Execute JS Result:", smart_format(result)) yield f"JS 执行结果:\n{smart_format(result)}\n" next_prompt = self._get_anchor_prompt() - return StepOutcome(result, next_prompt=next_prompt) + return StepOutcome(smart_format(result, max_str_len=5000), next_prompt=next_prompt) def do_file_patch(self, args, response): path = self._get_abs_path(args.get("path", "")) diff --git a/sidercall.py b/sidercall.py index 501493d..afe860a 100644 --- a/sidercall.py +++ b/sidercall.py @@ -12,12 +12,15 @@ class SiderLLMSession: self.default_model = default_model def ask(self, prompt, model=None, stream=False): if model is None: model = self.default_model - if len(prompt) > 29000: + if len(prompt) > 28000: print(f"[Warn] Prompt too long ({len(prompt)} chars), truncating.") - prompt = prompt[-29000:] + prompt = prompt[-28000:] gen = self._core.chat(prompt, model) - if stream: return gen - return ''.join(list(gen)) + full_text = ''.join(list(gen)) + if stream: + def wrap_as_stream(): yield full_text + return wrap_as_stream() # gen有奇怪的死循环行为,sider足够快 + return full_text class LLMSession: def __init__(self, api_key=oai_apikey, api_base=oai_apibase, model=oai_model, context_win=12000): @@ -74,7 +77,7 @@ class LLMSession: keep = 0; tok = 0 for m in reversed(self.raw_msgs): l = len(str(m))//4 - if tok + l > self.context_win//3: break + if tok + l > self.context_win*0.2: break tok += l; keep += 1 keep = max(2, keep) old, self.raw_msgs = self.raw_msgs[:-keep], self.raw_msgs[-keep:] @@ -84,28 +87,29 @@ class LLMSession: messages += [{"role":"user", "content":p}] msg_lens = [1000 if isinstance(m["content"], list) else len(str(m["content"]))//4 for m in messages] summary = ''.join(list(self.raw_ask(messages, model, temperature=0.1))) - print('[Debug] Summary length:', len(summary)//4, '; Context lengths:', str(msg_lens)) + print('[Debug] Summary length:', len(summary)//4, '; Orig context lengths:', str(msg_lens)) if not summary.startswith("Error:"): self.raw_msgs.insert(0, {"role":"assistant", "prompt":"Prev summary:\n"+summary, "image":None}) else: self.raw_msgs = old + self.raw_msgs # 不做了,下次再做 def ask(self, prompt, model=None, image_base64=None, stream=False): if model is None: model = self.model - self.raw_msgs.append({"role": "user", "prompt": prompt, "image": image_base64}) - messages = self.make_messages(self.raw_msgs[:-1], omit_images=True) - messages += self.make_messages([self.raw_msgs[-1]], omit_images=False) - msg_lens = [1000 if isinstance(m["content"], list) else len(str(m["content"]))//4 for m in messages] - total_len = sum(msg_lens) # estimate token count def _ask_gen(): content = '' with self.lock: - gen = self.raw_ask(messages, model) - for chunk in gen: - content += chunk; yield chunk + self.raw_msgs.append({"role": "user", "prompt": prompt, "image": image_base64}) + messages = self.make_messages(self.raw_msgs[:-1], omit_images=True) + messages += self.make_messages([self.raw_msgs[-1]], omit_images=False) + msg_lens = [1000 if isinstance(m["content"], list) else len(str(m["content"]))//4 for m in messages] + total_len = sum(msg_lens) # estimate token count + gen = self.raw_ask(messages, model) + for chunk in gen: + content += chunk; yield chunk if not content.startswith("Error:"): self.raw_msgs.append({"role": "assistant", "prompt": content, "image": None}) if total_len > 5000: print(f"[Debug] Whole context length {total_len} {str(msg_lens)}.") if total_len > self.context_win: + yield '[NextWillSummary]' threading.Thread(target=self.summary_history, daemon=True).start() if stream: return _ask_gen() return ''.join(list(_ask_gen())) @@ -142,12 +146,18 @@ class ToolClient: def chat(self, messages, tools=None): full_prompt = self._build_protocol_prompt(messages, tools) print("Full prompt length:", len(full_prompt), 'chars') - gen = self.raw_api(full_prompt, stream=True) - raw_text = '' - for chunk in gen: - raw_text += chunk; yield chunk with open('model_responses.txt', 'a', encoding='utf-8', errors="replace") as f: - f.write(f"=== Prompt ===\n{full_prompt}\n=== Response ===\n{raw_text}\n\n") + f.write(f"=== Prompt ===\n{full_prompt}\n") + gen = self.raw_api(full_prompt, stream=True) + raw_text = ''; summarytag = '[NextWillSummary]' + for chunk in gen: + raw_text += chunk; + if chunk != summarytag: yield chunk + print('Complete response received.') + if raw_text.endswith(summarytag): + self.last_tools = ''; raw_text = raw_text[:-len(summarytag)] + with open('model_responses.txt', 'a', encoding='utf-8', errors="replace") as f: + f.write(f"=== Response ===\n{raw_text}\n\n") return self._parse_mixed_response(raw_text) def _build_protocol_prompt(self, messages, tools):