#ifndef HUMANUS_CONFIG_H #define HUMANUS_CONFIG_H #include #include #include #include #include #include #include #include "schema.h" #include "prompt.h" namespace humanus { // Get project root directory static std::filesystem::path get_project_root() { return std::filesystem::path(__FILE__).parent_path(); } static const std::filesystem::path PROJECT_ROOT = get_project_root(); struct LLMConfig { std::string model; std::string api_key; std::string base_url; std::string end_point; int max_tokens; int timeout; double temperature; bool oai_tool_support; LLMConfig( const std::string& model = "deepseek-chat", const std::string& api_key = "sk-", const std::string& base_url = "https://api.deepseek.com", const std::string& end_point = "/v1/chat/completions", int max_tokens = 4096, int timeout = 120, double temperature = 1.0, bool oai_tool_support = true ) : model(model), api_key(api_key), base_url(base_url), end_point(end_point), max_tokens(max_tokens), timeout(timeout), temperature(temperature), oai_tool_support(oai_tool_support) {} json to_json() const { json j; j["model"] = model; j["api_key"] = api_key; j["base_url"] = base_url; j["end_point"] = end_point; j["max_tokens"] = max_tokens; j["temperature"] = temperature; return j; } }; struct ToolParser { std::string tool_start; std::string tool_end; std::string tool_hint_template; ToolParser(const std::string& tool_start = "", const std::string& tool_end = "", const std::string& tool_hint_template = prompt::toolcall::TOOL_HINT_TEMPLATE) : tool_start(tool_start), tool_end(tool_end), tool_hint_template(tool_hint_template) {} static ToolParser get_instance() { static ToolParser instance; return instance; } static std::string str_replace(std::string& str, const std::string& from, const std::string& to) { size_t start_pos = 0; while ((start_pos = str.find(from, start_pos)) != std::string::npos) { str.replace(start_pos, from.length(), to); start_pos += to.length(); // In case 'to' contains 'from', like replacing 'x' with 'yx' } return str; } std::string hint(const std::string& tool_list) const { std::string hint_str = tool_hint_template; hint_str = str_replace(hint_str, "{tool_start}", tool_start); hint_str = str_replace(hint_str, "{tool_end}", tool_end); hint_str = str_replace(hint_str, "{tool_list}", tool_list); return hint_str; } json parse(const std::string& content) const { std::string new_content = content; json tool_calls = json::array(); size_t pos_start = new_content.find(tool_start); size_t pos_end = pos_start == std::string::npos ? std::string::npos : new_content.find(tool_end, pos_start + tool_start.size()); if (pos_start != std::string::npos && pos_end == std::string::npos) { // Some might not have tool_end pos_end = new_content.size(); } while (pos_start != std::string::npos) { std::string tool_content = new_content.substr(pos_start + tool_start.size(), pos_end - pos_start - tool_start.size()); if (!tool_content.empty()) { try { tool_calls.push_back({ {"type", "function"}, {"function", json::parse(tool_content)} }); tool_calls.back()["id"] = "call_" + std::to_string(std::chrono::system_clock::now().time_since_epoch().count()); } catch (const json::exception& e) { throw std::runtime_error("Invalid tool call: " + tool_content); } } auto trim = [](const std::string& str) -> std::string { auto not_space = [](unsigned char ch) { return !std::isspace(ch); }; auto start = std::find_if(str.begin(), str.end(), not_space); auto end = std::find_if(str.rbegin(), str.rend(), not_space).base(); if (start >= end) return ""; return std::string(start, end); }; std::string lhs = trim(new_content.substr(0, pos_start)); std::string rhs = trim(new_content.substr(std::min(pos_end + tool_end.size(), new_content.size()))); new_content = lhs + rhs; pos_start = new_content.find(tool_start, pos_start); // Previous tool_call has been cut off pos_end = pos_start == std::string::npos ? std::string::npos : new_content.find(tool_end, pos_start + tool_start.size()); if (pos_start != std::string::npos && pos_end == std::string::npos) { // Some might not have tool_end pos_end = new_content.size(); } } return { {"content", new_content}, {"tool_calls", tool_calls} // Might be empty if no tool calls found }; } json dump(const json& tool_calls) const { std::string content; if (!tool_calls.is_array()) { throw std::runtime_error("Tool calls should be an array"); } for (const auto& tool_call : tool_calls) { content += tool_start; content += tool_call[tool_call["type"]].dump(2); content += tool_end; } return content; } }; struct AppConfig { std::map llm; std::map tool_parser; }; class Config { private: static Config* _instance; static std::mutex _mutex; bool _initialized = false; AppConfig _config; Config() { _load_initial_config(); _initialized = true; } Config(const Config&) = delete; Config& operator=(const Config&) = delete; /** * @brief Get the config path * @return The config path */ static std::filesystem::path _get_config_path() { auto root = PROJECT_ROOT; auto config_path = root / "config" / "config_llm.toml"; if (std::filesystem::exists(config_path)) { return config_path; } throw std::runtime_error("Config file not found"); } /** * @brief Load the initial config */ void _load_initial_config(); public: /** * @brief Get the singleton instance * @return The config instance */ static Config& get_instance() { if (_instance == nullptr) { std::lock_guard lock(_mutex); if (_instance == nullptr) { _instance = new Config(); } } return *_instance; } /** * @brief Get the LLM settings * @return The LLM settings map */ const std::map& llm() const { return _config.llm; } /** * @brief Get the tool helpers * @return The tool helpers map */ const std::map& tool_parser() const { return _config.tool_parser; } /** * @brief Get the app config * @return The app config */ const AppConfig& get_config() const { return _config; } }; } // namespace humanus #endif // HUMANUS_CONFIG_H