Files
qing 7e44025671 feat(robot_agent): 基于 libhttp 的 DeepSeek 视觉机器人 agent 骨架
- 工具循环:capture_image / get_robot_status / spawn_subagent
- 子代理:技能超集(run_command 带超时截断、read_file / write_file)
- V4L2 摄像头抓帧:MJPEG 优先零转码,YUYV+libjpeg 回退,/dev/video* 自动探测
- 时间戳注入:system prompt 会话级当前时间 + 工具结果执行时间
- CLI:--prompt-file / --camera / --camera-device / --model / --max-turns
2026-08-21 14:47:29 +00:00

130 lines
4.8 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "agent.hpp"
#include "tools.hpp"
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
namespace {
constexpr const char* DEFAULT_ENDPOINT = "https://api.deepseek.com/chat/completions";
constexpr const char* DEFAULT_MODEL = "deepseek-v4-flash-vision-exp";
constexpr const char* MAIN_SYSTEM_PROMPT =
"你是控制硬件机器人的主智能体。当用户的问题涉及观察环境、看东西、识别物体时,"
"必须先调用 capture_image 工具获取照片再回答。"
"当任务需要执行 shell 命令、读写文件等操作时,调用 spawn_subagent 工具委派给子代理,"
"不要自己尝试执行。回答使用中文。";
struct Options {
std::string question;
std::string prompt_file; // 非空 = 从文件读取问题
std::string camera_path; // 非空 = 模拟摄像头(读文件)
std::string camera_device; // 空 = 自动探测 /dev/video*
std::string model = DEFAULT_MODEL;
std::string endpoint = DEFAULT_ENDPOINT;
std::string api_key;
int max_turns = 8;
};
void print_usage(const char* prog) {
std::cerr << "用法: " << prog << " \"问题\" [选项]\n"
<< " " << prog << " --prompt-file <文件> [选项]\n"
<< "选项:\n"
<< " --prompt-file <文件> 从文件读取问题(与位置参数互斥)\n"
<< " --camera <文件> 模拟摄像头:拍照工具读该图片(不指定则用真实摄像头)\n"
<< " --camera-device <设备> 摄像头设备(默认自动探测 /dev/video*)\n"
<< " --model <名称> 模型名(默认 " << DEFAULT_MODEL << ")\n"
<< " --key <API_KEY> API key(默认读环境变量 DEEPSEEK_API_KEY)\n"
<< " --endpoint <URL> 接口地址(默认 DeepSeek 官方)\n"
<< " --max-turns <N> 最大工具轮次(默认 8)\n";
}
Options parse_args(int argc, char** argv) {
Options opt;
if (const char* key_env = std::getenv("DEEPSEEK_API_KEY")) opt.api_key = key_env;
for (int i = 1; i < argc; ++i) {
std::string arg = argv[i];
auto next = [&](const char* name) -> std::string {
if (i + 1 >= argc) {
std::cerr << "错误: " << name << " 缺少参数\n";
std::exit(2);
}
return argv[++i];
};
if (arg == "--camera") opt.camera_path = next("--camera");
else if (arg == "--camera-device") opt.camera_device = next("--camera-device");
else if (arg == "--prompt-file") opt.prompt_file = next("--prompt-file");
else if (arg == "--model") opt.model = next("--model");
else if (arg == "--key") opt.api_key = next("--key");
else if (arg == "--endpoint") opt.endpoint = next("--endpoint");
else if (arg == "--max-turns") opt.max_turns = std::stoi(next("--max-turns"));
else if (arg == "--help" || arg == "-h") { print_usage(argv[0]); std::exit(0); }
else if (!arg.empty() && arg[0] == '-') {
std::cerr << "错误: 未知选项 " << arg << "\n";
print_usage(argv[0]);
std::exit(2);
} else {
opt.question = arg;
}
}
return opt;
}
} // namespace
int main(int argc, char** argv) {
Options opt = parse_args(argc, argv);
if (!opt.prompt_file.empty()) {
if (!opt.question.empty()) {
std::cerr << "错误: --prompt-file 与位置参数问题不能同时使用\n";
return 2;
}
std::ifstream f(opt.prompt_file);
if (!f) {
std::cerr << "错误: 无法读取提示词文件 " << opt.prompt_file << "\n";
return 2;
}
std::ostringstream ss;
ss << f.rdbuf();
opt.question = ss.str();
}
if (opt.question.empty()) {
std::cerr << "错误: 缺少问题参数(用位置参数或 --prompt-file)\n";
print_usage(argv[0]);
return 2;
}
if (opt.api_key.empty()) {
std::cerr << "错误: 缺少 API key(用 --key 或设置环境变量 DEEPSEEK_API_KEY)\n";
return 2;
}
AgentConfig cfg;
cfg.model = opt.model;
cfg.endpoint = opt.endpoint;
cfg.api_key = opt.api_key;
cfg.camera_path = opt.camera_path;
cfg.camera_device = opt.camera_device;
cfg.max_turns = opt.max_turns;
ToolRegistry registry;
registry.add(make_capture_image_tool(opt.camera_path, opt.camera_device));
registry.add(make_robot_status_tool());
registry.add(make_spawn_subagent_tool(cfg));
try {
std::string answer =
run_chat_loop(cfg, MAIN_SYSTEM_PROMPT, opt.question, registry, "主代理");
std::cout << answer << "\n";
} catch (const std::exception& e) {
std::cerr << "错误: " << e.what() << "\n";
return 1;
}
return 0;
}