diff --git a/agent_loop.py b/agent_loop.py index e9c69db..6cb9d09 100644 --- a/agent_loop.py +++ b/agent_loop.py @@ -86,7 +86,7 @@ def agent_runner_loop(client, system_prompt, user_input, handler, tools_schema, if not outcome.next_prompt: should_exit = {'result': 'CURRENT_TASK_DONE', 'data': outcome.data}; break if outcome.next_prompt.startswith('未知工具'): client.last_tools = '' - if outcome.data is not None: + if outcome.data is not None and tool_name != 'no_tool': datastr = json.dumps(outcome.data, ensure_ascii=False, default=json_default) if type(outcome.data) in [dict, list] else str(outcome.data) tool_results.append({'tool_use_id': tid, 'content': datastr}) next_prompts.add(outcome.next_prompt) diff --git a/agentmain.py b/agentmain.py index 97c4132..7a853f2 100644 --- a/agentmain.py +++ b/agentmain.py @@ -188,7 +188,7 @@ if __name__ == '__main__': 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') - for _ in range(150): # 等reply.txt,5分钟超时 + for _ in range(300): # 等reply.txt,5分钟超时 time.sleep(2) if os.path.exists(rp): with open(rp, encoding='utf-8') as f: raw = f.read() diff --git a/frontends/stapp.py b/frontends/stapp.py index 17e0ffc..41f0a88 100644 --- a/frontends/stapp.py +++ b/frontends/stapp.py @@ -79,9 +79,10 @@ def fold_turns(text): turns.append((marker, content)) for idx, (marker, content) in enumerate(turns): if idx < len(turns) - 1: - matches = re.findall(r'\s*((?:(?!).)*?)\s*', content, re.DOTALL) + _c = re.sub(r'```.*?```|.*?', '', content, flags=re.DOTALL) + matches = re.findall(r'\s*((?:(?!).)*?)\s*', _c, re.DOTALL) if matches: - title = matches[-1].strip() + title = matches[0].strip() title = title.split('\n')[0] if len(title) > 50: title = title[:50] + '...' else: title = marker.strip('*') diff --git a/ga.py b/ga.py index 9cef05f..7e54e2b 100644 --- a/ga.py +++ b/ga.py @@ -264,7 +264,8 @@ class GenericAgentHandler(BaseHandler): def tool_after_callback(self, tool_name, args, response, ret): if args.get('_index', 0) > 0: return - rsumm = re.search(r"(.*?)", response.content, re.DOTALL) + _c = re.sub(r'```.*?```|.*?', '', response.content, flags=re.DOTALL) + rsumm = re.search(r"(.*?)", _c, re.DOTALL) if rsumm: summary = rsumm.group(1).strip()[:200] else: clean_args = {k: v for k, v in args.items() if not k.startswith('_')} diff --git a/llmcore.py b/llmcore.py index 8443df1..f41aa8b 100644 --- a/llmcore.py +++ b/llmcore.py @@ -492,6 +492,23 @@ class LLMSession(BaseSession): read_timeout=self.read_timeout, proxies=self.proxies)) def make_messages(self, raw_list): return _msgs_claude2oai(raw_list) +def _fix_messages(messages): + """修复 messages 符合 Claude API:交替、tool_use/tool_result 配对""" + if not messages: return messages + _wrap = lambda c: c if isinstance(c, list) else [{"type": "text", "text": str(c)}] + fixed = [] + for m in messages: + if fixed and m['role'] == fixed[-1]['role']: + fixed[-1] = {**fixed[-1], 'content': _wrap(fixed[-1]['content']) + _wrap(m['content'])}; continue + if fixed and fixed[-1]['role'] == 'assistant' and m['role'] == 'user': + uses = [b.get('id') for b in fixed[-1].get('content', []) if isinstance(b, dict) and b.get('type') == 'tool_use' and b.get('id')] + has = {b.get('tool_use_id') for b in _wrap(m['content']) if isinstance(b, dict) and b.get('type') == 'tool_result'} + miss = [uid for uid in uses if uid not in has] + if miss: m = {**m, 'content': [{"type": "tool_result", "tool_use_id": uid, "content": "(error)"} for uid in miss] + _wrap(m['content'])} + fixed.append(m) + while fixed and fixed[0]['role'] != 'user': fixed.pop(0) + return fixed + class NativeClaudeSession(BaseSession): def __init__(self, cfg): super().__init__(cfg) @@ -502,7 +519,8 @@ class NativeClaudeSession(BaseSession): self._device_id = uuid.uuid4().hex + uuid.uuid4().hex[:32] self.tools = None self.claude_tools_format = True - def raw_ask(self, messages, tools=None, system=None, model=None, temperature=0.5, max_tokens=6144): + def raw_ask(self, messages, model=None, temperature=0.5, max_tokens=6144): + messages = _fix_messages(messages) model = model or self.default_model headers = {"Content-Type": "application/json", "anthropic-version": "2023-06-01", "anthropic-beta": "prompt-caching-2024-07-31", "x-app": "cli", "user-agent": "claude-cli/2.1.80 (external, cli)"} @@ -510,14 +528,13 @@ class NativeClaudeSession(BaseSession): else: headers["x-api-key"] = self.api_key payload = {"model": model, "messages": messages, "temperature": temperature, "max_tokens": max_tokens, "stream": True} payload["metadata"] = {"user_id": json.dumps({"device_id": self._device_id, "account_uuid": self._account_uuid, "session_id": self._session_id}, separators=(',', ':'))} - if tools: - if self.claude_tools_format: tools = openai_tools_to_claude(tools) - tools = [dict(t) for t in tools]; tools[-1]["cache_control"] = {"type": "ephemeral"} + if self.tools: + tools = [dict(t) for t in self.tools]; tools[-1]["cache_control"] = {"type": "ephemeral"} payload["tools"] = tools payload['system'] = [{"type": "text", "text": "You are Claude Code, Anthropic's official CLI for Claude.", "cache_control": {"type": "ephemeral"}}] - if system: - if self.fake_cc_system_prompt: messages[0]["content"].insert(0, {"type": "text", "text": system}) - else: payload["system"] = [{"type": "text", "text": system}] + if self.system: + if self.fake_cc_system_prompt: messages[0]["content"].insert(0, {"type": "text", "text": self.system}) + else: payload["system"] = [{"type": "text", "text": self.system}] messages[-1] = {**messages[-1], "content": list(messages[-1]["content"])} messages[-1]["content"][-1] = dict(messages[-1]["content"][-1], cache_control={"type": "ephemeral"}) try: @@ -536,7 +553,7 @@ class NativeClaudeSession(BaseSession): messages = [{"role": m["role"], "content": list(m["content"])} for m in self.history] content_blocks = None - gen = self.raw_ask(messages, self.tools, self.system, model) + gen = self.raw_ask(messages, model=model) try: while True: yield next(gen) except StopIteration as e: content_blocks = e.value or [] @@ -564,12 +581,12 @@ class NativeOAISession(NativeClaudeSession): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.claude_tools_format = False - def raw_ask(self, messages, tools=None, system=None, model=None, temperature=0.5, max_tokens=6144, **kw): + def raw_ask(self, messages, model=None, temperature=0.5, max_tokens=6144, **kw): """OpenAI streaming. yields text chunks, generator return = list[content_block]""" model = model or self.default_model - msgs = ([{"role": "system", "content": system}] if system else []) + _msgs_claude2oai(messages) + msgs = ([{"role": "system", "content": self.system}] if self.system else []) + _msgs_claude2oai(messages) return (yield from _openai_stream(self.api_base, self.api_key, msgs, model, self.api_mode, - temperature=temperature, max_tokens=max_tokens, tools=tools, + temperature=temperature, max_tokens=max_tokens, tools=self.tools, reasoning_effort=self.reasoning_effort, max_retries=self.max_retries, connect_timeout=self.connect_timeout, read_timeout=self.read_timeout, proxies=self.proxies))