# agint_brain - AI Agent Brain Package

ROS2 Humble Python 包，Planner 节点的运行时接入层。

---

## 目录结构

```
agint_brain/
├── package.xml                 # ROS2 包描述
├── setup.py                    # Python 包配置
├── setup.cfg                   # setuptools 配置
├── resource/
│   └── agint_brain            # ament_index 标记文件
├── agint_brain/                # Python 包主目录
│   ├── __init__.py
│   ├── planner_node.py        # ROS2 Planner 节点（主入口）
│   ├── launch/
│   │   ├── __init__.py
│   │   └── planner.launch.py  # 启动文件
│   └── config/
│       └── planner_config.yaml # Planner 配置文件
└── README.md
```

---

## 功能说明

### 节点职责

`planner_node` 是 **Planner 的 ROS2 封装层**，负责：

1. **订阅 Topic**
   - `/world/snapshot` (std_msgs/String) - 世界状态快照 JSON
   - `/planner/user_intent` (std_msgs/String) - 用户意图文本
   - `/planner/llm_response` (std_msgs/String) - OmniNode LLM 响应

2. **发布 Topic**
   - `/plan` (std_msgs/String) - 生成的标准化 Plan JSON
   - `/planner/llm_request` (std_msgs/String) - LLM 请求（需要 OmniNode 时）

3. **核心逻辑**
   - 调用 `planner.py` 生成 Plan
   - 规则优先，复杂意图走 LLM
   - LLM 超时自动 fallback
   - 安全性保证：fallback 只生成 ASK_USER 步骤

### 当前版本限制

**单 pending request 串行版本**

当前 planner_node 仅维护一组状态：
- `pending_user_intent`
- `pending_request_id`
- `waiting_for_llm`

因此当前实现是：
- **单 pending request 的串行版本**
- 不支持并发多用户同时规划
- 不支持多个 LLM 请求同时挂起
- 不支持多任务并行等待

> 后续若要支持多轮并发/多用户，需要引入 request queue 或 session manager。

### Fallback 安全策略

当发生以下情况时，planner_node 会生成安全的 fallback Plan：
- Planner 生成 Plan 失败
- LLM 超时（超过 `llm_timeout_sec`）
- LLM 返回非法 JSON
- Plan 解析失败

Fallback Plan 的特征：
- `source = "planner_node_fallback"`
- `tool_call_type = "ask_user"`
- `risk_level = "low"`
- 用于请求用户重新澄清意图

**这是一个安全保护机制**：不会直接执行任何 capability，而是让用户确认后再继续。

### Topic 数据格式

#### /world/snapshot (订阅)
```json
{
  "environment": {
    "temperature": 32.5,
    "humidity": 70,
    "light_level": 500
  },
  "system": {
    "actuator_status": {
      "fan": "running",
      "light": "on"
    },
    "device_status": {},
    "mode": "auto"
  },
  "recent_actions": [
    {"action": "feed", "timestamp": 1713000000}
  ],
  "warnings": [],
  "errors": []
}
```

#### /planner/user_intent (订阅)
```
打开风扇降温
```

#### /plan (发布)
```json
{
  "plan_id": "plan_abc12345",
  "goal": "打开风扇降温",
  "reasoning": "用户意图: 打开风扇降温, 匹配动作: adjust_fan, 风险等级: low",
  "risk_level": "low",
  "requires_confirmation": false,
  "confirmation_message": null,
  "steps": [
    {
      "step_id": 1,
      "action": "adjust_fan",
      "tool_call_type": "execute",
      "parameters": {"user_intent": "打开风扇降温", "context": {}},
      "description": "执行动作: adjust_fan"
    }
  ],
  "status": "created",
  "source": "hybrid"
}
```

#### /planner/llm_request (发布)
```json
{
  "request_id": "uuid-xxxx",
  "user_intent": "再喂一次",
  "world_snapshot": {...},
  "available_tools": ["feed", "adjust_fan", "speak"],
  "domain_rules": {...},
  "planner_mode": "hybrid",
  "timestamp": 1713000000.0
}
```

#### /planner/llm_response (订阅)
```json
{
  "request_id": "uuid-xxxx",
  "plan": {
    "plan_id": "plan_llm_001",
    "goal": "再喂一次",
    "reasoning": "用户意图包含重复喂食请求，结合最近喂食时间判断需要用户确认",
    "risk_level": "medium",
    "requires_confirmation": true,
    "confirmation_message": "距离上次喂食不到10分钟，确定要再次喂食吗？",
    "steps": [
      {
        "step_id": 1,
        "action": "ask_user",
        "tool_call_type": "ask_user",
        "parameters": {
          "question": "距离上次喂食不到10分钟，确定要再次喂食吗？",
          "original_intent": "再喂一次"
        },
        "preconditions": {},
        "fallback": null,
        "status": "pending",
        "description": "询问用户确认重复喂食",
        "requires_confirmation": true,
        "confirmation_message": "距离上次喂食不到10分钟，确定要再次喂食吗？",
        "metadata": {"source": "llm"}
      }
    ],
    "status": "created",
    "created_at": 1713000000.0,
    "source": "llm",
    "metadata": {}
  }
}
```

---

## 依赖

### 系统依赖
- Ubuntu 22.04
- ROS2 Humble
- Python 3.10+

### Python 依赖
- `rclpy` (ROS2)
- `std_msgs` (ROS2)
- `pyyaml`

### 核心模块（在同一 workspace）
- `planner.py` - 规划核心逻辑
- `tool_protocol.py` - Plan 数据结构
- `planner_config_loader.py` - 配置加载器
- `llm_client.py` - LLM 客户端

---

## 编译

### 1. 进入工作空间

```bash
cd ~/ros2_ws
```

### 2. 克隆或放置包

将 `agint_brain` 包放置到 `src/` 目录下：

```bash
cp -r /path/to/agint_brain ~/ros2_ws/src/
```

### 3. 确保核心模块在 PYTHONPATH

如果 `planner.py` 等核心模块在其他位置，需要设置环境变量：

```bash
export PYTHONPATH="${PYTHONPATH}:/path/to/agint/brain"
```

或者在 `~/.bashrc` 中添加：

```bash
echo 'export PYTHONPATH="${PYTHONPATH}:/path/to/agint/brain"' >> ~/.bashrc
source ~/.bashrc
```

### 4. 安装 Python 依赖

```bash
pip3 install pyyaml
```

### 5. 编译包

```bash
cd ~/ros2_ws
colcon build --packages-select agint_brain
source install/setup.bash
```

### 6. 安装配置文件（可选）

```bash
mkdir -p ~/.ros2_ws/install/agint_brain/share/agint_brain/config/
cp ~/ros2_ws/src/agint_brain/agint_brain/config/planner_config.yaml \
   ~/.ros2_ws/install/agint_brain/share/agint_brain/config/
```

---

## 启动

### 基本启动

```bash
ros2 launch agint_brain planner.launch.py
```

### 带参数启动

```bash
ros2 launch agint_brain planner.launch.py \
    planner_config_path:=/path/to/planner_config.yaml \
    input_world_topic:=/world/snapshot \
    input_user_intent_topic:=/planner/user_intent \
    output_plan_topic:=/plan \
    use_llm:=true \
    llm_timeout_sec:=10.0 \
    debug_log:=true
```

### 参数说明

| 参数 | 默认值 | 说明 |
|------|--------|------|
| `planner_config_path` | `planner_config.yaml` | 配置文件路径 |
| `input_world_topic` | `/world/snapshot` | 世界状态输入 topic |
| `input_user_intent_topic` | `/planner/user_intent` | 用户意图输入 topic |
| `output_plan_topic` | `/plan` | Plan 输出 topic |
| `llm_request_topic` | `/planner/llm_request` | LLM 请求 topic |
| `llm_response_topic` | `/planner/llm_response` | LLM 响应 topic |
| `use_llm` | `true` | 是否启用 LLM |
| `llm_timeout_sec` | `10.0` | LLM 超时时间（秒） |
| `debug_log` | `true` | 是否输出调试日志 |

---

## 测试

### 1. 查看节点是否运行

```bash
ros2 node list
# 应显示 /planner_node
```

### 2. 查看 topic 列表

```bash
ros2 topic list
# 应显示:
#   /plan
#   /planner/llm_request
#   /planner/llm_response
#   /planner/user_intent
#   /world/snapshot
```

### 3. 发送测试数据

#### 发送 world snapshot

```bash
ros2 topic pub /world/snapshot std_msgs/String \
    '{data: "{\"environment\": {\"temperature\": 32}, \"system\": {\"actuator_status\": {\"fan\": \"running\"}}}"}' -1
```

#### 发送用户意图

```bash
ros2 topic pub /planner/user_intent std_msgs/String \
    '{data: "打开风扇降温"}' -1
```

### 4. 监听 Plan 输出

```bash
ros2 topic echo /plan
```

---

## 测试场景

### 场景 1：规则可直接处理

**输入：**
```bash
ros2 topic pub /world/snapshot std_msgs/String \
    '{data: "{\"environment\": {\"temperature\": 35}, \"system\": {\"actuator_status\": {\"fan\": \"off\"}}}"}' -1

ros2 topic pub /planner/user_intent std_msgs/String \
    '{data: "打开风扇降温"}' -1
```

**预期输出：**
```
[plan] plan_id=plan_xxxx, source=rule_engine, goal=打开风扇降温
```

---

### 场景 2：需要 LLM

**输入：**
```bash
ros2 topic pub /world/snapshot std_msgs/String \
    '{data: "{\"environment\": {\"temperature\": 32}, \"system\": {\"actuator_status\": {}}}"}' -1

ros2 topic pub /planner/user_intent std_msgs/String \
    '{data: "再喂一次"}' -1
```

**预期行为：**
1. 发布 `/planner/llm_request`（request_id=xxx）
2. 等待 OmniNode 响应
3. 收到响应后发布 `/plan`

**模拟 OmniNode 响应（完整 Plan JSON）：**
```bash
ros2 topic pub /planner/llm_response std_msgs/String \
    '{data: "{\"request_id\": \"xxx\", \"plan\": {\"plan_id\": \"plan_llm_001\", \"goal\": \"再喂一次\", \"reasoning\": \"LLM生成分步规划\", \"risk_level\": \"low\", \"requires_confirmation\": false, \"confirmation_message\": null, \"steps\": [{\"step_id\": 1, \"action\": \"feed\", \"tool_call_type\": \"execute\", \"parameters\": {\"amount\": \"normal\"}, \"preconditions\": {}, \"fallback\": null, \"status\": \"pending\", \"description\": \"执行喂食\", \"requires_confirmation\": false, \"confirmation_message\": null, \"metadata\": {}}], \"status\": \"created\", \"source\": \"llm\", \"metadata\": {}}}"}' -1
```

---

### 场景 3：LLM 超时

**步骤：**
1. 发送触发 LLM 的用户意图
2. 不发送 LLM 响应
3. 等待超过 `llm_timeout_sec`（默认 10 秒）

**预期输出：**
```
[plan] plan_id=plan_fallback_xxx, source=planner_node_fallback
```

---

### 场景 4：LLM 非法响应

**步骤：**
1. 发送触发 LLM 的用户意图
2. 发送非法 JSON 响应

```bash
ros2 topic pub /planner/llm_response std_msgs/String \
    '{data: "not valid json"}' -1
```

**预期输出：**
```
[ERROR] LLM 响应 JSON 解析失败
[plan] plan_id=plan_fallback_xxx, source=planner_node_fallback
```

---

## 架构设计

> **版本限制**：planner_node 当前为单 pending request 串行版本，**不支持**并发多用户/多任务并行规划。

```
┌─────────────────────────────────────────────────────────────────┐
│                         world_node                              │
│                     (/world/snapshot)                            │
└─────────────────────────────┬───────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│                       planner_node                               │
│                                                                   │
│  ┌──────────────┐  ┌──────────────┐  ┌────────────────────────┐ │
│  │ on_world_    │  │ on_user_     │  │ on_llm_response        │ │
│  │ snapshot     │  │ intent       │  │                        │ │
│  └──────┬───────┘  └──────┬───────┘  └───────────┬────────────┘ │
│         │                 │                      │               │
│         ▼                 ▼                      ▼               │
│  ┌─────────────────────────────────────────────────────────────┐│
│  │                    Planner 核心逻辑                          ││
│  │              (planner.py - generate_plan)                   ││
│  └──────┬───────────────────────────────────────┬───────────────┘│
│         │                                       │                 │
│         ▼                                       ▼                 │
│  ┌──────────────┐                    ┌──────────────────────┐    │
│  │ 直接发布 /plan│                    │ 发布 /planner/        │    │
│  │ (规则可处理)  │                    │ llm_request          │    │
│  └──────────────┘                    └──────────┬───────────┘    │
│                                                  │                 │
│                                                  ▼                 │
│                                    ┌─────────────────────────┐     │
│                                    │     OmniNode            │     │
│                                    │   (LLM 服务)            │     │
│                                    └──────────┬──────────────┘    │
│                                               │                    │
│                                               ▼                    │
│                                    ┌─────────────────────────┐     │
│                                    │ /planner/llm_response   │     │
│                                    └─────────────────────────┘     │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│                       executor_node                               │
│                         (/plan)                                  │
└─────────────────────────────────────────────────────────────────┘
```

---

## 故障排除

### 1. 核心模块导入失败

```
ImportError: cannot import name 'Planner' from 'planner'
```

**解决方案：** 确保 `planner.py` 等核心模块在 PYTHONPATH 中：

```bash
export PYTHONPATH="${PYTHONPATH}:/path/to/agint/brain"
```

### 2. 配置文件找不到

```
[ERROR] 加载 planner 配置失败: 配置文件不存在
```

**解决方案：** 使用绝对路径或确保配置文件在正确位置：

```bash
ros2 launch agint_brain planner.launch.py \
    planner_config_path:=/full/path/to/planner_config.yaml
```

### 3. 编译时缺少依赖

```
Could not find a package configuration file provided by "ament_python"
```

**解决方案：** 安装 ROS2 构建工具：

```bash
sudo apt install ros-humble-ament-python
```

---

## License

MIT License

---

## Author

Jonathan
