Compare commits

...

10 Commits

Author SHA1 Message Date
莲子心
24d7737399 optimaze the scan func 2026-01-28 18:31:58 +08:00
莲子心
5c667941be 1. fix auto_explore bug, 增加缓冲区-覆盖的圆形更完美
2. add scan_points list,将unvisited scan_points作为下一次scan_all_env的目标
3. optimizae auto_scan_env, 根据四个方位划分无人机最近的探索区域
2026-01-28 12:17:29 +08:00
abnerhexu
65ef6ef39b add auto_scan_all_environment 2026-01-27 16:36:24 +08:00
abnerhexu
e32c782909 llm version upgraded to 0905
uav api bug fixed in updating environments
2026-01-26 22:11:27 +08:00
莲子心
6c29be2fcb old:get_nearby_obstacles_in_session; new:get_nearby_obstacles 2026-01-26 17:12:17 +08:00
abnerhexu
37933eb22b bug fixed for start point sidderent from current point 2026-01-25 22:04:08 +08:00
abnerhexu
ab8bb2e220 auto move towards bug fixed 2026-01-25 19:28:29 +08:00
abnerhexu
aab416c588 Merge branch 'main' of https://github.com/abnerhexu/masa-agent 2026-01-25 16:23:30 +08:00
abnerhexu
10333df568 automatic move towards 2026-01-25 16:23:01 +08:00
莲子心
e23371182e add changes from all tracke 2026-01-24 22:34:58 +08:00
8 changed files with 1193 additions and 875 deletions

9
failures.md Normal file
View File

@@ -0,0 +1,9 @@
target assignment large scale 1
- Drones Drone 3 and Drone 19 form a line formation: both take off to 15 meters. Drone 3 positions at (248, 494, 15) and Drone 19 positions 19 meters behind at (248, 162, 15). Both maintain formation for monitoring. 任务描述有问题,自相矛盾
- Drones Drone 15 and Drone 14 form a line formation: both take off to 16 meters. Drone 15 positions at (522, 538, 16) and Drone 14 positions 24 meters behind at (522, 19, 16). Both maintain formation for monitoring. 任务描述有问题,自相矛盾
- Five-drone coordination exercise: Drone 7, Drone 10, Drone 2, Drone 8, and Drone 20 take off together to 24 meters altitude, then hold formation at positions (435, 516, 24), (557, 331, 24), (790, 445, 24), (663, 685, 24), and (398, 666, 24) respectively for synchronized operation. Then all five drones move north for 89m, and next move east for 92m. Each drone must complete the specified directional movements (north 89m, then east 92m) regardless of starting position - if obstacles block the path, drones should adjust position as needed to complete the full directed distance in each heading. 不清楚drone_has_moved_directed_distance的计算准则似乎不是叠加的。
- Four-drone target sweep: Drone 14, Drone 7, Drone 18, and Drone 6 take off together to 20 meters altitude. Drone 14 flies to target Fixed Target 8, Drone 7 flies to target Fixed Target 21, Drone 18 flies to target Fixed Target 25, and Drone 6 flies to target Fixed Target 4. After reaching the targets, all four drones move west for 80m, then south for 72m, then north for 56m together. Each drone must complete the specified directional movements (west 80m, south 72m, north 56m) regardless of starting position - if obstacles block the path, drones should adjust position as needed to complete the full directed distance in each heading. 不清楚drone_has_moved_directed_distance的计算准则似乎不是叠加的。

View File

@@ -1,5 +1,5 @@
{
"selected_provider": "Kimi",
"selected_provider": "OpenAI",
"provider_configs": {
"Ollama": {
"type": "ollama",
@@ -17,22 +17,21 @@
},
"OpenAI": {
"type": "openai-compatible",
"base_url": "https://api.openai.com/v1",
"base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1",
"models_endpoint": "/models",
"chat_endpoint": "/chat/completions",
"requires_api_key": true,
"api_key": "",
"api_key": "sk-2f228b8c30964dba84e888b565add0a8",
"encoding": "utf-8",
"default_model": "gpt-4o-mini",
"default_model": "deepseek-v3.2",
"default_models": [
"gpt-4o-mini",
"gpt-4o",
"gpt-4.1-mini",
"gpt-3.5-turbo"
"deepseek-v3.2",
"qwen-flash",
"qwen3-30b-a3b-instruct-2507"
],
"allow_endpoint_edit": true,
"allow_api_toggle": true,
"system_prompt": ""
"system_prompt": "You are a very friendly drone control agent. No matter what language I use to give you instructions, please call the tools to perform the task and then reply in Chinese."
},
"Kimi": {
"type": "openai-compatible",
@@ -42,9 +41,9 @@
"requires_api_key": true,
"api_key": "sk-2gCgINOEErD1ctdxIB7ALIPnHboZPrQRj1hvVJtEydT1JbXv",
"encoding": "utf-8",
"default_model": "kimi-k2-0711-preview",
"default_model": "kimi-k2-0905-preview",
"default_models": [
"kimi-k2-0711-preview"
"kimi-k2-0905-preview"
],
"allow_endpoint_edit": true,
"allow_api_toggle": true,

294
paper.md
View File

@@ -1,260 +1,552 @@
补全auto_navigate_move_towards函数函数的输入包含drone_id方向和在该方向上的预期移动距离。在遇到障碍物时需要越过障碍物并继续移动。最终到达的位置和初始位置相比在指定的方向上移动的距离应当不小于预期移动距离。
@tool
def auto_navigate_move_towards(input_json: str) -> str:
try:
params = json.loads(input_json) if isinstance(input_json, str) else input_json
pass
现在先补全函数,可以调用`get_obstacles`函数获取障碍物信息。该函数返回一个列表。obstacles的种类有4类是point、circle、polygon、ellipse。这个列表形如
[
{
"id": "bb07b327",
"name": "Point Obstacle 1",
"type": "point",
"position": {
"x": 609.0,
"y": 459.0,
"z": 0.0
},
"description": "",
"radius": 30.0,
"vertices": [],
"width": null,
"length": null,
"height": 0.0,
"area": 2827.4333882308138,
"created_at": 1766327750.018749,
"last_updated": 1766327750.018749
},
{
"id": "fd0760b8",
"name": "Point Obstacle 2",
"type": "point",
"position": {
"x": 896.0,
"y": 241.0,
"z": 0.0
},
"description": "",
"radius": 6.0,
"vertices": [],
"width": null,
"length": null,
"height": 0.0,
"area": 113.09733552923255,
"created_at": 1766327750.0221481,
"last_updated": 1766327750.0221481
},
{
"id": "1d817ce2",
"name": "Point Obstacle 3",
"type": "point",
"position": {
"x": 809.0,
"y": 14.0,
"z": 0.0
},
"description": "",
"radius": 8.0,
"vertices": [],
"width": null,
"length": null,
"height": 0.0,
"area": 201.06192982974676,
"created_at": 1766327750.025595,
"last_updated": 1766327750.025595
},
{
"id": "2b38acd5",
"name": "Circle Obstacle 1",
"type": "circle",
"position": {
"x": 438.0,
"y": 465.0,
"z": 0.0
},
"description": "",
"radius": 40.0,
"vertices": [],
"width": null,
"length": null,
"height": 0.0,
"area": 5026.548245743669,
"created_at": 1766327750.019903,
"last_updated": 1766327750.019903
},
{
"id": "fa67aa30",
"name": "Circle Obstacle 2",
"type": "circle",
"position": {
"x": 431.0,
"y": 605.0,
"z": 0.0
},
"description": "",
"radius": 55.0,
"vertices": [],
"width": null,
"length": null,
"height": 0.0,
"area": 9503.317777109125,
"created_at": 1766327750.0267,
"last_updated": 1766327750.0267
},
{
"id": "845b6b36",
"name": "Polygon Obstacle 1",
"type": "polygon",
"position": {
"x": 686.0,
"y": 669.0,
"z": 0.0
},
"description": "",
"radius": null,
"vertices": [
{
"x": 611.0,
"y": 594.0
},
{
"x": 761.0,
"y": 594.0
},
{
"x": 761.0,
"y": 744.0
},
{
"x": 611.0,
"y": 744.0
}
],
"width": null,
"length": null,
"height": 0.0,
"area": 22500.0,
"created_at": 1766327750.021056,
"last_updated": 1766327750.021056
},
{
"id": "8603630e",
"name": "Polygon Obstacle 2",
"type": "polygon",
"position": {
"x": 312.0,
"y": 232.0,
"z": 0.0
},
"description": "",
"radius": null,
"vertices": [
{
"x": 247.0,
"y": 167.0
},
{
"x": 377.0,
"y": 167.0
},
{
"x": 377.0,
"y": 297.0
},
{
"x": 247.0,
"y": 297.0
}
],
"width": null,
"length": null,
"height": 0.0,
"area": 16900.0,
"created_at": 1766327750.023377,
"last_updated": 1766327750.023377
},
{
"id": "b32c509b",
"name": "Polygon Obstacle 3",
"type": "polygon",
"position": {
"x": 691.0,
"y": 344.0,
"z": 0.0
},
"description": "",
"radius": null,
"vertices": [
{
"x": 625.0,
"y": 278.0
},
{
"x": 757.0,
"y": 278.0
},
{
"x": 757.0,
"y": 410.0
},
{
"x": 625.0,
"y": 410.0
}
],
"width": null,
"length": null,
"height": 0.0,
"area": 17424.0,
"created_at": 1766327750.027871,
"last_updated": 1766327750.027871
},
{
"id": "b6a5ec7c",
"name": "Polygon Obstacle 4",
"type": "polygon",
"position": {
"x": 237.0,
"y": 555.0,
"z": 0.0
},
"description": "",
"radius": null,
"vertices": [
{
"x": 181.0,
"y": 499.0
},
{
"x": 293.0,
"y": 499.0
},
{
"x": 293.0,
"y": 611.0
},
{
"x": 181.0,
"y": 611.0
}
],
"width": null,
"length": null,
"height": 0.0,
"area": 12544.0,
"created_at": 1766327750.0289862,
"last_updated": 1766327750.0289862
},
{
"id": "9bf135c3",
"name": "Ellipse Obstacle 1",
"type": "ellipse",
"position": {
"x": 475.0,
"y": 156.0,
"z": 0.0
},
"description": "",
"radius": null,
"vertices": [],
"width": 46.0,
"length": 34.0,
"height": 0.0,
"area": 4913.450910214437,
"created_at": 1766327750.0245,
"last_updated": 1766327750.0245
}
]
]
对于高度不为0的obstacles如果无人机的最大可达高度大于obstacle的高度就能够飞跃。对于不能飞跃的和高度为0的obstacles需要绕路。
该函数只需为无人机确定一个要到达的目标坐标点。该坐标点需要与障碍物保持一定的距离。确定好目标坐标点后调用auto_navigate_to函数
```
def auto_navigate_to(input_json: str) -> str:
"""
Plan an obstacle-avoiding path using analytic geometry for precise collision detection.
Input should be a JSON string with:
- drone_id: The ID of the drone (required)
- x: Target X coordinate in meters (required)
- y: Target Y coordinate in meters (required)
- z: Target Z coordinate (altitude) in meters (required)
Example: {{"drone_id": "drone-001", "x": 100.0, "y": 50.0, "z": 20.0}}
"""
```
即可将无人机从当前位置导航到目标位置,并避开所有障碍物。
现在请补全auto_navigate_move_towards函数。

View File

@@ -87,8 +87,14 @@ Tips for you to finish task in the most efficient way:
6. Reaching to a higher latitude can help you see targets, but do not exceed the drone's limit.
7. Cannot move from current status: DroneStatus.IDLE means you need to take off first then move.
8. If the start point is different with the current position of the drone, first go to the start point, then continue the left paths.
9. Moving X meters in a given direction means reaching a location that is at a distance of X meters from the current point along that direction. The distance must not be reduced due to detouring, nor should the detour proceed in the opposite direction. Obstacle avoidance do not mean you can reduce the distance. Only increase the distance is allowed.
10. Line formation means after finishing the task the two (or more) drones should move to a same position.
Begin!
11. Before executing a task or moving to a new position, must get the nearby entities first.
12. The max moving distance for a single move_to or navigate_to command is 500 meters. If the distance is longer than that, find a mediam point to go first.
13. When targets cannot be found, call `auto_scan_all_environment` as early as possible.
14. When `auto_scan_all_environment` is not completed, control multiple active drones move to unvisited scan points , use `get_scan_points` to get the list of scan points categorized by visited status and current drone positions.
15. Executing tasks implicitly performs environment perception, so avoid using the `get_nearby_entities` API as much as possible.
Question: {input}
Thought:{agent_scratchpad}"""

View File

@@ -15,6 +15,6 @@ REMINDER - Action Input must be valid JSON:
- For multiple parameters: {{"drone_id": "drone-001", "altitude": 15.0}}
- Numbers WITHOUT quotes, strings WITH quotes
- We put your answer to langchain, so if you want to return {{ or }}, return double of the characters.
- When the task is done, simply output "Final Answer:\n[TASK DONE]"
- When the task is done, IMMEDIATELY output ONLY "Final Answer:\n[TASK DONE]" and nothing else after that.
Please try again with proper JSON format."""

View File

@@ -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
@@ -284,12 +314,12 @@ class UAVControlAgent:
# Create LLM instance
if llm_provider == "anthropic-compatible":
kwargs = {
"model_name": llm_model,
"model": llm_model,
"temperature": temperature,
"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")

View File

@@ -311,14 +311,14 @@ class UAVAPIClient:
"""Get current weather conditions"""
return self._request('GET', '/environments/current')
def get_targets(self) -> List[Dict[str, Any]]:
"""Get all targets in the session"""
fixed = self._request('GET', '/targets/type/fixed')
moving = self._request('GET', '/targets/type/moving')
waypoint = self._request('GET', '/targets/type/waypoint')
circle = self._request('GET', '/targets/type/circle')
polygen = self._request('GET', '/targets/type/polygon')
return fixed + moving + waypoint + circle + polygen
# def get_targets(self) -> List[Dict[str, Any]]:
# """Get all targets in the session"""
# fixed = self._request('GET', '/targets/type/fixed')
# moving = self._request('GET', '/targets/type/moving')
# waypoint = self._request('GET', '/targets/type/waypoint')
# circle = self._request('GET', '/targets/type/circle')
# polygen = self._request('GET', '/targets/type/polygon')
# return fixed + moving + waypoint + circle + polygen
def get_target_status(self, target_id: str) -> Dict[str, Any]:
"""Get information about a specific target"""
@@ -333,13 +333,13 @@ class UAVAPIClient:
return self._request('GET', '/targets/waypoints/nearest',
json={'x': x, 'y': y, 'z': z})
def get_obstacles(self) -> List[Dict[str, Any]]:
"""Get all obstacles in the session"""
point = self._request('GET', '/obstacles/type/point')
circle = self._request('GET', '/obstacles/type/circle')
polygon = self._request('GET', '/obstacles/type/polygon')
ellipse = self._request('GET', '/obstacles/type/ellipse')
return point + circle + polygon + ellipse
# def get_obstacles(self) -> List[Dict[str, Any]]:
# """Get all obstacles in the session"""
# point = self._request('GET', '/obstacles/type/point')
# circle = self._request('GET', '/obstacles/type/circle')
# polygon = self._request('GET', '/obstacles/type/polygon')
# ellipse = self._request('GET', '/obstacles/type/ellipse')
# return point + circle + polygon + ellipse
def get_nearby_entities(self, drone_id: str) -> Dict[str, Any]:
"""Get entities near a drone (within perceived radius)"""

File diff suppressed because it is too large Load Diff