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
This commit is contained in:
1 parent
2dc79f0637
commit
7e44025671
12 files changed
+1132
No files matched your search
@@ -0,0 +1,129 @@
|
||||
#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;
|
||||
}
|
||||
Reference in new issue
Block a user