Spaces:
Build error
Build error
| 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<std::string, ToolDefinition> 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<ToolCall> parseToolCallsFromResponse(const std::string& jsonResponse) const { | |
| std::vector<ToolCall> 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<std::string>() : ("call_" + std::to_string(result.size())); | |
| if (tc.contains("function")) { | |
| auto& func = tc["function"]; | |
| call.name = func.contains("name") ? func["name"].get<std::string>() : ""; | |
| call.arguments_json = func.contains("arguments") ? func["arguments"].get<std::string>() : "{}"; | |
| } | |
| 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<std::string>(); | |
| } | |
| } 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<Impl>()) {} | |
| LLMClient::~LLMClient() = default; | |
| LLMClient& LLMClient::getInstance() { | |
| static LLMClient instance; | |
| return instance; | |
| } | |
| void LLMClient::setEndpoint(const std::string& endpoint) { | |
| std::lock_guard<std::mutex> lock(impl_->mutex); | |
| impl_->endpoint = endpoint; | |
| } | |
| void LLMClient::setApiKey(const std::string& api_key) { | |
| std::lock_guard<std::mutex> lock(impl_->mutex); | |
| impl_->api_key = api_key; | |
| } | |
| void LLMClient::setModel(const std::string& model) { | |
| std::lock_guard<std::mutex> lock(impl_->mutex); | |
| impl_->model = model; | |
| } | |
| void LLMClient::setTimeout(int timeout_ms) { | |
| std::lock_guard<std::mutex> lock(impl_->mutex); | |
| impl_->timeout_ms = timeout_ms; | |
| } | |
| void LLMClient::registerTool(const ToolDefinition& tool) { | |
| std::lock_guard<std::mutex> lock(impl_->mutex); | |
| impl_->tools[tool.name] = tool; | |
| } | |
| void LLMClient::clearTools() { | |
| std::lock_guard<std::mutex> lock(impl_->mutex); | |
| impl_->tools.clear(); | |
| } | |
| std::string LLMClient::sendChat(const std::vector<std::map<std::string, std::string>>& messages) { | |
| std::lock_guard<std::mutex> 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<std::string> LLMClient::sendChatAsync(const std::vector<std::map<std::string, std::string>>& 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<std::mutex> 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<std::string> 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<std::string> LLMClient::getRegisteredToolNames() const { | |
| std::lock_guard<std::mutex> lock(impl_->mutex); | |
| std::vector<std::string> names; | |
| for (const auto& [name, _] : impl_->tools) { | |
| names.push_back(name); | |
| } | |
| return names; | |
| } | |
| } // namespace core | |
| } // namespace hhb | |