From 37933eb22bd2f1846c46d46b8df06706d760586b Mon Sep 17 00:00:00 2001 From: abnerhexu <20591243+abnerhexu@users.noreply.github.com> Date: Sun, 25 Jan 2026 22:04:08 +0800 Subject: [PATCH] bug fixed for start point sidderent from current point --- uav_agent.py | 87 +++++++++++++++++++++++++++++++----------- uav_langchain_tools.py | 8 +++- 2 files changed, 70 insertions(+), 25 deletions(-) diff --git a/uav_agent.py b/uav_agent.py index cc3854b..ad167bd 100644 --- a/uav_agent.py +++ b/uav_agent.py @@ -20,26 +20,56 @@ import json import os from pathlib import Path +# from langchain.agents import create_tool_calling_agent +# from langchain.agents import create_react_agent as create_react_agent_anthropic +# from langchain.agents import AgentExecutor as AgentExecutorAnthropic -class EnhancedChatOpenAI(ChatOpenAI): - """ChatOpenAI subclass that captures reasoning_content if provided by the API""" +class EnhancedChatAnthropic(ChatAnthropic): + """ + 针对 Anthropic 的增强类: + 1. 提取 'thinking' 块并将其转换为 ReAct 格式的 'Thought: ...' + 2. 防止因为只有工具调用而导致的 content 为空 + """ - def _create_chat_result(self, response: Any) -> ChatResult: - result = super()._create_chat_result(response) + def generate(self, response: Any) -> ChatResult: + result = super().generate(response) - if hasattr(response, "choices") and response.choices: - for i, choice in enumerate(response.choices): - # Handle MiniMax reasoning_details - if hasattr(choice.message, "reasoning_details") and choice.message.reasoning_details: - reasoning = choice.message.reasoning_details[0].get('text', '') - if reasoning and i < len(result.generations): - gen = result.generations[i] - if isinstance(gen.message, AIMessage): - # Store in additional_kwargs - gen.message.additional_kwargs["reasoning_content"] = reasoning - # Prepend to content for ReAct agent visibility - if "Thought:" not in gen.message.content: - gen.message.content = f"Thought: {reasoning}\n" + gen.message.content + for generation in result.generations: + message = generation.message + raw_content = message.content + + # --- 逻辑 A: 处理思维链 (Thinking Blocks) --- + # 如果 content 是列表 (Anthropic 标准格式),提取 thinking + if isinstance(raw_content, list): + text_parts = [] + thought_parts = [] + + for block in raw_content: + if isinstance(block, dict): + if block.get("type") == "thinking": + # 获取思考内容 + thinking = block.get("thinking", "") + # 存入 additional_kwargs (保持与其他代码一致) + message.additional_kwargs["reasoning_content"] = thinking + thought_parts.append(f"Thought: {thinking}\n") + elif block.get("type") == "text": + text_parts.append(block.get("text", "")) + + # 重组 Content,把思考放在最前面,欺骗 ReAct 解析器 + final_text = "".join(text_parts) + if thought_parts and "Thought:" not in final_text: + message.content = "".join(thought_parts) + final_text + else: + message.content = final_text + + # --- 逻辑 B: 处理纯工具调用导致的空字符串 --- + # 如果 content 为空,但有工具调用,我们手动补一个 Thought + # 这样 ReAct 解析器就不会报错说 "No action found" + if not message.content and message.tool_calls: + tool_name = message.tool_calls[0]['name'] + # 伪造一个 Thought,让 Log 好看,也让解析器通过 + message.content = f"Thought: I should use the {tool_name} tool to proceed.\n" + return result @@ -289,7 +319,7 @@ class UAVControlAgent: "api_key": llm_api_key, "base_url": final_base_url } - self.llm = ChatAnthropic(**kwargs) + self.llm = EnhancedChatAnthropic(**kwargs) else: kwargs = { "model": llm_model, @@ -328,11 +358,22 @@ class UAVControlAgent: # Create ReAct agent if self.debug: print("🤖 Creating ReAct agent...") - self.agent = create_react_agent( - llm=self.llm, - tools=self.tools, - prompt=self.prompt - ) + if llm_provider in ["anthropic", "anthropic-compatible"]: + if self.debug: + print("🤖 Using Tool Calling Agent (Better for Claude)") + self.agent = create_react_agent( + llm=self.llm, + tools=self.tools, + prompt=self.prompt + ) + else: + if self.debug: + print("🤖 Using React Agent (GPT-3 and older)") + self.agent = create_react_agent( + llm=self.llm, + tools=self.tools, + prompt=self.prompt + ) if self.debug: print("✅ ReAct agent created") diff --git a/uav_langchain_tools.py b/uav_langchain_tools.py index d61a02a..4479cac 100644 --- a/uav_langchain_tools.py +++ b/uav_langchain_tools.py @@ -460,6 +460,8 @@ def create_uav_tools(client: UAVAPIClient) -> list: return "Error: drone_id is required" result = client.take_off(drone_id, altitude) + if result["status"] == "success": + result["message"] += f" Using `get_drone_status` to check if the drone\'s current position is start point. If not, fly to the start point first." return json.dumps(result, indent=2) except json.JSONDecodeError as e: return f"Error parsing JSON input: {str(e)}. Expected format: {{\"drone_id\": \"drone-001\", \"altitude\": 15.0}}" @@ -1248,8 +1250,10 @@ def create_uav_tools(client: UAVAPIClient) -> list: if current_coverage >= required_coverage: tool_states.explored_count = 0 return f"Success: Target explored with coverage {current_coverage:.2%} (Visited {tool_states.explored_count}/{total_points} grid points)" - - return f"Finished path. Final coverage: {current_coverage:.2%}" + if math.isclose(current_coverage, 0.0): + return f"Finished path. Final coverage: {current_coverage:.2%}. Please try call this tool again to continue exploring." + else: + return f"Finished path. Final coverage: {current_coverage:.2%}. Wait for a while and continue calling this function! return [TASK DONE] this time" # Return all tools