#ifdef _WIN32 #define NOMINMAX #define WIN32_LEAN_AND_MEAN #endif #include "llm_client.h" #include #include #include #include #include namespace hhb { namespace core { struct LLMClient::Impl { std::string endpoint; std::string api_key; std::string model = "gpt-4o-mini"; int timeout_ms = 60000; mutable std::string last_error; mutable std::mutex mutex; // 具身智能工具注册表:存储 LLM 可调用的 C++ 能力 std::map tools; std::string buildToolsJson() const { nlohmann::json tools_arr = nlohmann::json::array(); for (const auto& [name, tool] : tools) { nlohmann::json tool_obj; tool_obj["type"] = "function"; tool_obj["function"]["name"] = tool.name; tool_obj["function"]["description"] = tool.description; nlohmann::json params; try { params = nlohmann::json::parse(tool.parameters_json_schema); } catch (...) { params = nlohmann::json::parse(R"({"type":"object","properties":{}})"); } tool_obj["function"]["parameters"] = params; tools_arr.push_back(tool_obj); } return tools_arr.dump(); } std::string sendHttpRequest(const std::string& json_body) const { last_error.clear(); try { httplib::Client client(endpoint); client.set_timeout_sec(timeout_ms / 1000); httplib::Headers headers; headers["Content-Type"] = "application/json"; if (!api_key.empty()) { headers["Authorization"] = "Bearer " + api_key; } auto res = client.Post("/v1/chat/completions", headers, json_body, "application/json"); if (res) { if (res->status == 200) { return res->body; } else { last_error = "HTTP " + std::to_string(res->status) + ": " + res->body.substr(0, 500); return ""; } } else { last_error = client.get_last_error(); if (last_error.empty()) last_error = "Connection failed"; return ""; } } catch (const std::exception& e) { last_error = std::string("Exception: ") + e.what(); return ""; } } // 解析 LLM 返回的 tool_calls,将语义指令映射为 C++ 可执行结构 std::vector parseToolCallsFromResponse(const std::string& jsonResponse) const { std::vector result; try { auto json = nlohmann::json::parse(jsonResponse); if (!json.contains("choices") || json["choices"].empty()) return result; auto& message = json["choices"][0].contains("message") ? json["choices"][0]["message"] : json["choices"][0]; // 修复点:不能用 auto& 绑定到临时对象 nlohmann::json tool_calls_json; if (message.contains("tool_calls")) { tool_calls_json = message["tool_calls"]; } else { tool_calls_json = nlohmann::json::array(); } for (auto& tc : tool_calls_json) { ToolCall call; call.id = tc.contains("id") ? tc["id"].get() : ("call_" + std::to_string(result.size())); if (tc.contains("function")) { auto& func = tc["function"]; call.name = func.contains("name") ? func["name"].get() : ""; call.arguments_json = func.contains("arguments") ? func["arguments"].get() : "{}"; } result.push_back(call); } } catch (const std::exception& e) { std::cerr << "Error parsing ToolCalls: " << e.what() << std::endl; } return result; } // 提取 LLM 文本回复 std::string extractTextContent(const std::string& jsonResponse) const { try { auto json = nlohmann::json::parse(jsonResponse); if (!json.contains("choices") || json["choices"].empty()) return ""; auto& message = json["choices"][0].contains("message") ? json["choices"][0]["message"] : json["choices"][0]; if (message.contains("content")) { return message["content"].get(); } } catch (const std::exception& e) { std::cerr << "Error extracting TextContent: " << e.what() << std::endl; } return ""; } // 执行工具调用:将 LLM 的语义参数传入 C++ 回调,获取几何分析结果 ToolResult executeToolCall(const ToolCall& call) const { ToolResult result; result.tool_call_id = call.id; auto it = tools.find(call.name); if (it == tools.end()) { result.success = false; result.result_json = R"({"error": "Unknown tool: )" + call.name + R"("})"; return result; } try { std::string exec_result = it->second.execute(call.arguments_json); result.success = true; result.result_json = exec_result; } catch (const std::exception& e) { result.success = false; result.result_json = R"({"error": ")" + std::string(e.what()) + R"("})"; } return result; } }; private: std::string apiKey_; std::string endpoint_; }; LLMClient::LLMClient() : impl_(std::make_unique()) {} LLMClient::~LLMClient() = default; LLMClient& LLMClient::getInstance() { static LLMClient instance; return instance; } void LLMClient::setEndpoint(const std::string& endpoint) { std::lock_guard lock(impl_->mutex); impl_->endpoint = endpoint; } void LLMClient::setApiKey(const std::string& api_key) { std::lock_guard lock(impl_->mutex); impl_->api_key = api_key; } void LLMClient::setModel(const std::string& model) { std::lock_guard lock(impl_->mutex); impl_->model = model; } void LLMClient::setTimeout(int timeout_ms) { std::lock_guard lock(impl_->mutex); impl_->timeout_ms = timeout_ms; } void LLMClient::registerTool(const ToolDefinition& tool) { std::lock_guard lock(impl_->mutex); impl_->tools[tool.name] = tool; } void LLMClient::clearTools() { std::lock_guard lock(impl_->mutex); impl_->tools.clear(); } std::string LLMClient::sendChat(const std::vector>& messages) { std::lock_guard lock(impl_->mutex); nlohmann::json request; request["model"] = impl_->model; request["temperature"] = 0.7; request["max_tokens"] = 2048; nlohmann::json msgs = nlohmann::json::array(); for (const auto& msg : messages) { nlohmann::json m; for (const auto& [key, val] : msg) { m[key] = val; } msgs.push_back(m); } request["messages"] = msgs; if (!impl_->tools.empty()) { request["tools"] = nlohmann::json::parse(impl_->buildToolsJson()); request["tool_choice"] = "auto"; } return impl_->sendHttpRequest(request.dump()); } std::future LLMClient::sendChatAsync(const std::vector>& messages) { return std::async(std::launch::async, [this, messages]() { return sendChat(messages); }); } std::string LLMClient::embodiedQuery(const std::string& user_input, int max_rounds) { std::lock_guard lock(impl_->mutex); // 构建初始消息列表 // 具身智能接口:将 3D 拓扑数据转换为 LLM 可理解的语义上下文 nlohmann::json messages = nlohmann::json::array(); nlohmann::json sys_msg; sys_msg["role"] = "system"; sys_msg["content"] = "You are an embodied AI CAD assistant. You can analyze 3D models using the provided tools. " "When the user asks about model properties, weaknesses, or geometry, use the appropriate tool. " "Always respond in the user's language. Be concise and specific about what you found."; messages.push_back(sys_msg); nlohmann::json user_msg; user_msg["role"] = "user"; user_msg["content"] = user_input; messages.push_back(user_msg); for (int round = 0; round < max_rounds; ++round) { // 构建 OpenAI 兼容的请求体 nlohmann::json request; request["model"] = impl_->model; request["temperature"] = 0.7; request["max_tokens"] = 2048; request["messages"] = messages; if (!impl_->tools.empty()) { request["tools"] = nlohmann::json::parse(impl_->buildToolsJson()); request["tool_choice"] = "auto"; } std::string response_body = impl_->sendHttpRequest(request.dump()); if (response_body.empty()) { return "[Error] " + impl_->last_error; } // 解析 LLM 响应,检查是否有工具调用 auto tool_calls = impl_->parseToolCallsFromResponse(response_body); if (tool_calls.empty()) { // LLM 没有调用工具,直接返回文本回复 return impl_->extractTextContent(response_body); } // 将 LLM 的 assistant 消息(含 tool_calls)加入上下文 nlohmann::json assistant_msg; assistant_msg["role"] = "assistant"; // 重新解析原始响应以保留完整结构 try { nlohmann::json resp = nlohmann::json::parse(response_body); assistant_msg = resp["choices"][0]["message"]; } catch (...) { assistant_msg["content"] = impl_->extractTextContent(response_body); } messages.push_back(assistant_msg); // 执行每个工具调用,将结果反馈给 LLM for (const auto& tc : tool_calls) { std::cout << "[EmbodiedAI] Tool call: " << tc.name << " args: " << tc.arguments_json << std::endl; ToolResult result = impl_->executeToolCall(tc); std::cout << "[EmbodiedAI] Tool result: success=" << result.success << " result=" << result.result_json.substr(0, 200) << std::endl; // 将工具执行结果作为 tool message 反馈给 LLM nlohmann::json tool_msg; tool_msg["role"] = "tool"; tool_msg["tool_call_id"] = result.tool_call_id; tool_msg["content"] = result.result_json; messages.push_back(tool_msg); } } // 达到最大轮次,做最后一次请求获取最终文本回复 nlohmann::json final_request; final_request["model"] = impl_->model; final_request["temperature"] = 0.7; final_request["max_tokens"] = 2048; final_request["messages"] = messages; std::string final_response = impl_->sendHttpRequest(final_request.dump()); if (final_response.empty()) { return "[Error] " + impl_->last_error; } return impl_->extractTextContent(final_response); } std::future LLMClient::embodiedQueryAsync(const std::string& user_input, int max_rounds) { return std::async(std::launch::async, [this, user_input, max_rounds]() { return embodiedQuery(user_input, max_rounds); }); } std::string LLMClient::getLastError() const { return impl_->last_error; } std::vector LLMClient::getRegisteredToolNames() const { std::lock_guard lock(impl_->mutex); std::vector names; for (const auto& [name, _] : impl_->tools) { names.push_back(name); } return names; } } // namespace core } // namespace hhb