diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..b6b1ecf --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,10 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 已忽略包含查询文件的默认文件夹 +/queries/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ diff --git a/.idea/base_agent.iml b/.idea/base_agent.iml new file mode 100644 index 0000000..cbfcb6b --- /dev/null +++ b/.idea/base_agent.iml @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..317bc4d --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,25 @@ + + + + \ No newline at end of file diff --git a/config.yaml b/config.yaml new file mode 100644 index 0000000..260abe9 --- /dev/null +++ b/config.yaml @@ -0,0 +1,72 @@ +# ════════════════════════════════════════════════════════════════ +# config/config.yaml — Agent 系统全局配置文件 +# ════════════════════════════════════════════════════════════════ + +# ── LLM 模型配置 ─────────────────────────────────────────────── +llm: + provider: "openai" + model_name: "gpt-4o" + api_key: "sk-AUmOuFI731Ty5Nob38jY26d8lydfDT-QkE2giqb0sCuPCAE2JH6zjLM4lZLpvL5WMYPOocaMe2FwVDmqM_9KimmKACjR" # 优先读取环境变量 LLM_API_KEY + api_base_url: "https://openapi.monica.im/v1" # 自定义代理地址,留空使用官方 + max_tokens: 4096 + temperature: 0.7 + timeout: 60 + max_retries: 3 + function_calling: true + stream: false + model_path: "" + ollama_host: "http://localhost:11434" +database: + type: "sqlite" + url: "sqlite:///skills.db" +skills_directory: "./" # 新增:SKILL.md 文件所在目录 +# ── 本地 MCP Server 配置 ─────────────────────────────────────── +mcp: + server_name: "MCPServer" + transport: "stdio" + host: "localhost" + port: 3000 + tools_directory: "./agent/tools" + # 本地注册的工具列表 + enabled_tools: + - uav_self_check + - uav_control + - get_uav_state + + +# ── 在线 MCP Skill 配置 ──────────────────────────────────────── +# 每一项代表一个远端 MCP Server,其暴露的所有工具将作为 skill 注册到 Agent +mcp_skills: +# ── 工具配置 ─────────────────────────────────────────────────── +tools: + web_search: + max_results: 5 + timeout: 10 + api_key: "7917bef5e46044af5209fdb78518be98be394f3fe763bbce3fbb503280408bd9" + uav_control: +# ── 记忆配置 ─────────────────────────────────────────────────── +memory: + max_history: 20 + enable_long_term: false + vector_db_url: "" + +# ── 日志配置 ─────────────────────────────────────────────────── +logging: + level: "DEBUG" + enable_file: true + log_dir: "./logs" + log_file: "agent.log" + +# ── Agent 行为配置 ───────────────────────────────────────────── +agent: + max_chain_steps: 10 + enable_multi_step: true + session_timeout: 3600 + fallback_to_rules: true + +device: + type: "uav" + device_id: "UAV-001" + protocol: "mavlink" + params: + connection_string: "udp:127.0.0.1:14550" diff --git a/skills.db b/skills.db new file mode 100644 index 0000000..6771bce Binary files /dev/null and b/skills.db differ diff --git a/skills/hexstrike-ai b/skills/hexstrike-ai index 8333779..6fdbd74 160000 --- a/skills/hexstrike-ai +++ b/skills/hexstrike-ai @@ -1 +1 @@ -Subproject commit 83337796dcfb8cfbf733bd24d0b2c7e4f0732790 +Subproject commit 6fdbd74e2eea693d03d8b737fc494b9eeb135635 diff --git a/tools/web_search.py b/tools/web_search.py deleted file mode 100644 index 190753c..0000000 --- a/tools/web_search.py +++ /dev/null @@ -1,89 +0,0 @@ -""" -tools/web_search.py -网络搜索工具 —— google搜索 -配置通过 settings.tools['web_search'] 读取 -""" - -from dataclasses import dataclass - -from config.settings import settings -from tools.base_tool import BaseTool -from utils.logger import get_logger -from serpapi import SerpApiClient - -logger = get_logger("TOOL.WebSearch") - - -def _cfg(key: str, fallback=None): - return settings.tools['web_search'].get(key, fallback) - - -@dataclass -class SearchResult: - title: str - url: str - snippet: str - rank: int = 0 - - def __str__(self) -> str: - return f"[{self.rank}] {self.title}\n {self.url}\n {self.snippet}" - - -class Tool(BaseTool): - name = "web_search" - description = ( - "在互联网上搜索信息,返回相关网页的标题、链接和摘要。" - "适用于需要实时信息、最新资讯或不确定的知识查询。" - ) - parameters = { - "type": "object", - "properties": { - "query": { - "type": "string", - "description": "搜索关键词或问题,例如: 'Python 3.12 新特性'", - } - }, - "required": ["query"], - } - - def execute(self, query: str = "", **_) -> str: - """ - 一个基于SerpApi的实战网页搜索引擎工具。 - 它会智能地解析搜索结果,优先返回直接答案或知识图谱信息。 - """ - print(f"🔍 正在执行 [SerpApi] 网页搜索: {query}") - try: - api_key = _cfg("api_key") - if not api_key: - return "错误:ApiKey未配置。" - - params = { - "engine": "google", - "q": query, - "api_key": api_key, - "gl": "cn", # 国家代码 - "hl": "zh-cn", # 语言代码 - } - - client = SerpApiClient(params) - results = client.get_dict() - - # 智能解析:优先寻找最直接的答案 - if "answer_box_list" in results: - return "\n".join(results["answer_box_list"]) - if "answer_box" in results and "answer" in results["answer_box"]: - return results["answer_box"]["answer"] - if "knowledge_graph" in results and "description" in results["knowledge_graph"]: - return results["knowledge_graph"]["description"] - if "organic_results" in results and results["organic_results"]: - # 如果没有直接答案,则返回前三个有机结果的摘要 - snippets = [ - f"[{i + 1}] {res.get('title', '')}\n{res.get('snippet', '')}" - for i, res in enumerate(results["organic_results"][:3]) - ] - return "\n\n".join(snippets) - - return f"对不起,没有找到关于 '{query}' 的信息。" - - except Exception as e: - return f"搜索时发生错误: {e}" \ No newline at end of file diff --git a/uas/agent.py b/uas/agent.py deleted file mode 100644 index fff7704..0000000 --- a/uas/agent.py +++ /dev/null @@ -1,434 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import annotations - -import logging -import queue -import threading -import traceback -from enum import Enum -from time import sleep -from typing import Optional, Tuple - -from PyQt5.QtCore import QObject, pyqtSlot, pyqtSignal, QThread - -from uas.action.action_executor import action_executor -from uas.event.event_bus import event_bus -from uas.event.handlers.device_handlers import DeviceErrorHandler, DeviceTimeoutHandler, EquipmentFaultHandler -from uas.task.task_executor import task_executor -from uas.task.task_model import Task, TaskState -from uas.uas_control.base_controller import BaseUASController - -logger = logging.getLogger(__name__) - -class AgentState(Enum): - IDLE = "IDLE" - READY = "READY" - RUNNING = "RUNNING" - PAUSED = "PAUSED" - STOPPED = "STOPPED" - SWITCHING = "SWITCHING" - -# ───────────────────────────────────────────────────────────────── -# 装备模型抽象基类 -# ───────────────────────────────────────────────────────────────── - -class AgentTaskWorker(QObject): - # 信号:用于向主线程发送结果 - finished = pyqtSignal() - result = pyqtSignal(object) # 可传数据 - - def __init__(self, agent:"Agent", dt:int): - super().__init__() - self.agent = agent - self.dt = dt - - @pyqtSlot() - def do_work(self): - """真正的耗时任务""" - try: - # 执行你的任务 - print("线程开始执行") - res = self._start_task() - # 发送结果 - self.result.emit(res) - finally: - self.finished.emit() - - def _start_task(self): - self.agent._model.start() - self.agent.state = AgentState.RUNNING - while self.agent.state != AgentState.STOPPED and not self.agent._task_queue.empty(): - self.agent.current_task = self.agent._task_queue.get() - priority, task = self.agent.current_task - assert isinstance(task, Task) - self.agent._planning_task(priority, task) - task_executor.submit(task) - - while task.state in(TaskState.RUNNING, TaskState.PLANNED, TaskState.READY): - # 如果需要切换任务 - if self.agent.from_to(AgentState.RUNNING, AgentState.SWITCHING): - self.agent._save_task(priority, task) - task_executor.cancel(task.task_id) - break - # 如果需要停止任务 - elif self.agent.from_to(AgentState.RUNNING, AgentState.STOPPED): - task_executor.cancel(task.task_id) - break - elif self.agent.from_to(AgentState.RUNNING, AgentState.PAUSED): - task_executor.pause(task.task_id) - elif self.agent.from_to(AgentState.PAUSED, AgentState.RUNNING): - task_executor.resume(task.task_id) - try: - if task.state == TaskState.RUNNING and self.agent.state == AgentState.RUNNING: - result = self.agent._model.step(self.dt) - # 控制仿真速率(实时:sleep = dt;加速:sleep < dt) - sleep(max(.050, (self.dt/1000) * 0.200)) - except Exception as e: - traceback.print_exc() - print(f"[SimThread] 仿真异常: {e}") - break - self.agent.state = AgentState.IDLE - self.agent.current_task = None - self.agent._model.stop() - - - def _start_task2(self): - self.agent.state = AgentState.RUNNING - while self.agent.state != AgentState.STOPPED and not self.agent._task_queue.empty(): - self.agent.current_task = self.agent._task_queue.get() - priority, task = self.agent.current_task - assert isinstance(task, Task) - self.agent._planning_task(priority, task) - task_executor.submit(task) - - route = task.route - for seg_index, segment in enumerate(task.route): - pass - - while task.state in(TaskState.RUNNING, TaskState.PLANNED, TaskState.READY): - - # 如果需要切换任务 - if self.agent.from_to(AgentState.RUNNING, AgentState.SWITCHING): - self.agent._save_task(priority, task) - task_executor.cancel(task.task_id) - break - # 如果需要停止任务 - elif self.agent.from_to(AgentState.RUNNING, AgentState.STOPPED): - task_executor.cancel(task.task_id) - break - elif self.agent.from_to(AgentState.RUNNING, AgentState.PAUSED): - task_executor.pause(task.task_id) - elif self.agent.from_to(AgentState.PAUSED, AgentState.RUNNING): - task_executor.resume(task.task_id) - try: - if task.state == TaskState.RUNNING and self.agent.state == AgentState.RUNNING: - result = self.agent._model.step(self.dt) - # 控制仿真速率(实时:sleep = dt;加速:sleep < dt) - sleep(max(.050, (self.dt/1000) * 0.200)) - except Exception as e: - traceback.print_exc() - print(f"[SimThread] 仿真异常: {e}") - break - self.agent.state = AgentState.IDLE - self.agent.current_task = None - self.agent._model.stop() - -class Agent(QObject): - state_updated = pyqtSignal(object) - - def __init__( - self, - controller: BaseUASController, - parent: QObject = None, - ): - super().__init__(parent) - self._controller = controller - # self._model = model - # 日志 - self.logger = logging.getLogger( - f"{self.__class__.__name__}[{self._model.equipment_id}]" - ) - - self._prev_state: object = AgentState.IDLE - self._state: object = AgentState.IDLE - - self._current_task: Optional[Tuple[int, Task]] = None - self._task_lock = threading.Lock() - self._task_queue = queue.PriorityQueue() - self._task_thread = None - - self._setup() - - def _setup(self): - # 注册事件类处理器 - event_bus.register_handler(DeviceErrorHandler()) - event_bus.register_handler(DeviceTimeoutHandler()) - event_bus.register_handler(EquipmentFaultHandler()) - # 扫描 @on_event 装饰器处理器 - count = event_bus.scan_decorators() - event_bus.event_submitted.connect(self._on_event_submitted) - event_bus.event_handled.connect(self._on_event_handled) - event_bus.event_failed.connect(self._on_event_failed) - event_bus.event_ignored.connect(self._on_event_ignored) - event_bus.stats_updated.connect(self._on_stats_updated) - - # ── 连接 ActionExecutor 信号 ────────────────────────────── - action_executor.action_completed.connect(self._on_action_completed) - action_executor.action_progress.connect(self._on_action_progress) - action_executor.events_produced.connect(self._on_events_produced) - - # ── 连接 TaskExecutor 信号 ──────────────────────────────── - task_executor.task_submitted.connect(self._on_task_submitted) - task_executor.task_started.connect(self._on_task_started) - task_executor.task_completed.connect(self._on_task_completed) - task_executor.task_failed.connect(self._on_task_failed) - task_executor.task_cancelled.connect(self._on_task_cancelled) - task_executor.task_paused.connect(self._on_task_paused) - task_executor.task_resumed.connect(self._on_task_resumed) - task_executor.segment_entered.connect(self._on_segment_entered) - task_executor.segment_exited.connect(self._on_segment_exited) - action_executor.set_event_bus(event_bus) - task_executor.set_action_executor(action_executor) - - def _state_trans_handler(self): - pass - - - - def put_task(self, priority: int, task: Task): - with self._task_lock: - self._task_queue.put((priority, task)) - if self._state == AgentState.IDLE: - self._state = AgentState.READY - self.state_updated.emit(self._state) - - def remove_task(self, task: Optional[Task|str]): - with self._task_lock: - temp_queue = queue.PriorityQueue() - while not self._task_queue.empty(): - priorty, _task = self._task_queue.get() - assert isinstance(_task, Task) - if isinstance(task, Task): - if _task != task: - temp_queue.put((priorty, task)) - elif isinstance(task, str): - if _task.task_id != task: - temp_queue.put((priorty, _task)) - self._task_queue = temp_queue - if self._task_queue.empty() and self._state == AgentState.READY: - self._state = AgentState.IDLE - - @property - def state(self): - with self._task_lock: - return self._state - - @state.setter - def state(self, state: AgentState): - with self._task_lock: - self._prev_state = self._state - self._state = state - self.state_updated.emit(state) - - @property - def current_task(self): - with self._task_lock: - return self._current_task - - @current_task.setter - def current_task(self, task): - with self._task_lock: - self._current_task = task - - def schedule_task(self): - self.state = AgentState.SWITCHING - - def stop(self): - self.state = AgentState.STOPPED - - def pause(self): - self.state = AgentState.PAUSED - - def resume(self): - self.state = AgentState.RUNNING - - def from_to(self, from_state, to_state): - with self._task_lock: - if self._prev_state == from_state and self._state == to_state: - return True - else: - return False - - def _process_task_queue(self, dt: float): - if self.state == AgentState.RUNNING: - return - self._model.start() - self.state = AgentState.RUNNING - while self.state != AgentState.STOPPED and not self._task_queue.empty(): - self.current_task = self._task_queue.get() - priority, task = self.current_task - assert isinstance(task, Task) - self._planning_task(priority, task) - task_executor.submit(task) - - while task.state in(TaskState.RUNNING, TaskState.PLANNED, TaskState.READY): - # 如果需要切换任务 - if self.from_to(AgentState.RUNNING, AgentState.SWITCHING): - self._save_task(priority, task) - task_executor.cancel(task.task_id) - break - # 如果需要停止任务 - elif self.from_to(AgentState.RUNNING, AgentState.STOPPED): - task_executor.cancel(task.task_id) - break - elif self.from_to(AgentState.RUNNING, AgentState.PAUSED): - task_executor.pause(task.task_id) - elif self.from_to(AgentState.PAUSED, AgentState.RUNNING): - task_executor.resume(task.task_id) - try: - if task.state == TaskState.RUNNING and self.state == AgentState.RUNNING: - result = self._model.step(dt) - # 控制仿真速率(实时:sleep = dt;加速:sleep < dt) - sleep(max(.050, (dt/1000) * 0.200)) - except Exception as e: - traceback.print_exc() - print(f"[SimThread] 仿真异常: {e}") - break - self.state = AgentState.IDLE - self.current_task = None - self._model.stop() - - def start(self, dt): - if self.state == AgentState.RUNNING: - return - self._task_thread = QThread() - self._worker = AgentTaskWorker(self, dt) - self._worker.moveToThread(self._task_thread) - self._task_thread.started.connect(self._worker.do_work) - self._worker.finished.connect(self._task_thread.quit) - self._worker.finished.connect(self._task_thread.quit) # 任务结束 → 退出线程 - self._worker.finished.connect(self._worker.deleteLater) # 自动释放 - self._task_thread.finished.connect(self._task_thread.deleteLater) # 自动释放 - self._task_thread.finished.connect(self._on_worker_finished) # 任务结束后恢复按钮 - self._task_thread.start() - - @pyqtSlot() - def _on_worker_finished(self): - self._task_thread = None - - # ── 属性(只读)────────────────────────────────────────────── - def _planning_task(self, priority, task): - pass - - def _log(self, message, style): - print(message) - - def _save_task(self, priority, task): - pass - - @pyqtSlot(object) - def _on_event_submitted(self, event): - self._log(f"📥 提交 | {event.type_key} | src={event.source}", "#569cd6") - - @pyqtSlot(object) - def _on_event_handled(self, event): - results_str = " | ".join(str(r) for r in event.results) - self._log( - f"✅ 完成 | {event.type_key} | {event.elapsed_ms:.1f}ms | {results_str}", - "#4ec9b0" - ) - - @pyqtSlot(object, str) - def _on_event_failed(self, event, error): - self._log(f"❌ 失败 | {event.type_key} | {error}", "#f44747") - - @pyqtSlot(object) - def _on_event_ignored(self, event): - self._log(f"⚪ 忽略 | {event.type_key} | 无处理器", "#808080") - - @pyqtSlot(int, int, int) - def _on_stats_updated(self, total: int, handled: int, failed: int): - pass - - - # ── TaskExecutor 信号槽 ─────────────────────────────────────── - @pyqtSlot(object) - def _on_task_submitted(self, task): - self._log( - f"📥 任务提交: {task.name} [{task.task_id[:8]}]", "#569cd6" - ) - - @pyqtSlot(object) - def _on_task_started(self, task): - self._log(f"▶ 任务开始执行: {task.name}", "#4ec9b0") - - @pyqtSlot(object) - def _on_task_completed(self, task): - self._log( - f"✅ 任务完成: {task.name} 耗时={task.elapsed_s:.1f}s", - "#4CAF50" - ) - - @pyqtSlot(object, str) - def _on_task_failed(self, task, error): - self._log(f"❌ 任务失败: {task.name} {error}", "#f44747") - - @pyqtSlot(object) - def _on_task_cancelled(self, task): - self._log(f"🚫 任务取消: {task.name}", "#FF9800") - - @pyqtSlot(object) - def _on_task_paused(self, task): - self._log(f"⏸ 任务暂停: {task.name}", "#9E9E9E") - # self._add_rec_row("state_change", "任务已暂停", "") - - @pyqtSlot(object) - def _on_task_resumed(self, task): - self._log(f"▶ 任务恢复: {task.name}", "#4ec9b0") - # self._add_rec_row("state_change", "任务已恢复", "") - - @pyqtSlot(object, int) - def _on_segment_entered(self, task, seg_index): - if task.has_route and seg_index < len(task.route.segments): - seg = task.route.segments[seg_index] - msg = (f"进入航段[{seg_index}] " - f"{seg.wp_from.identifier}→{seg.wp_to.identifier} " - f"({seg.distance / 1000:.1f}km)") - else: - msg = f"进入航段[{seg_index}]" - self._log(f"✈️ {msg}", "#dcdcaa") - # self._add_rec_row("segment", msg, f"seg_index={seg_index}") - - @pyqtSlot(object, int) - def _on_segment_exited(self, task, seg_index): - self._log(f"🏁 离开航段[{seg_index}]", "#808080") - - # ── ActionExecutor 信号槽 ───────────────────────────────────── - - @pyqtSlot(object) - def _on_action_completed(self, action): - self._log( - f" ✅ Action完成: {action.type_key} " - f"src={action.source} {action.elapsed_ms:.0f}ms", - "#6a9955", - ) - - @pyqtSlot(str, int, str) - def _on_action_progress(self, action_id: str, percent: int, message: str): - pass - - @pyqtSlot(object, list) - def _on_events_produced(self, action, events): - # self._event_count += len(events) - for ev in events: - self._log( - f" ⚡ Event产生: {ev.type_key} src={ev.source}", - "#c586c0", - ) - - def __repr__(self) -> str: - return (f"{self.__class__.__name__}(" - f"id={self.equipment_id}, " - f"name={self.name}, " - f"lc={self.lifecycle.value})") - diff --git a/uas/route/routes/ground_route.py b/uas/route/routes/ground_route.py index e27e1e5..fa530f1 100644 --- a/uas/route/routes/ground_route.py +++ b/uas/route/routes/ground_route.py @@ -23,7 +23,6 @@ from uas.route.base_params import GroundParams from uas.route.base_route import BaseRoute from uas.route.base_segment import BaseSegment, BaseTransition, SegmentKind from uas.route.base_waypoint import EquipmentDomain -from uas.utils import geo_utils from uas.utils.geo_utils import GeoUtils @@ -77,7 +76,7 @@ def _ground_seg_interpolate(seg: GroundSegment, t: float) -> GroundParams: dist = seg.v_start * dt + 0.5 * seg.acceleration * dt * dt dist = max(0.0, min(dist, seg.distance)) - new_lat, new_lon = geo_utils.offset_position( + new_lat, new_lon = GeoUtils.offset_position( seg.start_lat, seg.start_lon, seg.bearing, dist ) speed = max(seg.v_start + seg.acceleration * dt, 0.0) diff --git a/uas/utils/geo_utils.py b/uas/utils/geo_utils.py index 8698f36..a27a5d7 100644 --- a/uas/utils/geo_utils.py +++ b/uas/utils/geo_utils.py @@ -10,30 +10,6 @@ EARTH_RADIUS = 6_371_000.0 DEG2RAD = math.pi / 180.0 RAD2DEG = 180.0 / math.pi - -# def determine_phase1(alt_start, alt_end, vs): -# if alt_start < 500 and alt_end < 500: -# return FlightPhase.TAKEOFF if vs > 0.5 else FlightPhase.LAND -# if alt_start < 500: return FlightPhase.TAKEOFF -# if alt_end < 500: return FlightPhase.APPROACH -# if vs > 1.0: return FlightPhase.CLIMB -# if vs < -1.0: return FlightPhase.DESCENT -# return FlightPhase.CRUISE -# -# def determine_phase2(altitude, vs, airspeed): -# """判断飞行阶段(同 v1.0)""" -# if altitude < 50.0: -# return FlightPhase.LAND -# if vs > 2.0: -# return FlightPhase.CLIMB -# if vs < -2.0: -# if altitude < 3000.0 and airspeed < 103.0: -# return FlightPhase.APPROACH -# return FlightPhase.DESCENT -# if altitude < 3000.0: -# return FlightPhase.APPROACH -# return FlightPhase.CRUISE - def determine_phase(alt: float, vs: float, min_alt: float, max_alt: float): if alt <= min_alt + 50: