diff --git a/README.md b/README.md index 940070f..ac2e7bc 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ to it) * **`Client Layer`**: * `OllamaClient`: Handles the actual communication with an Ollama-compatible API server via HTTP (using `libcurl`). * `DebugClient`: A CLI-friendly mock client that reads a mock JSON response from `stdin`, allowing for deterministic testing of harness logic without relying on an external LLM. -* **State Management**: Conversations are serialized to and deserialized from JSON files. This includes the full message history (`history`), the active tool registry (`tools`), and harness configuration (`config`). +* **State Management**: Conversations are serialized to and deserialized from JSON files. This includes the full message history (`history`), the active tool registry (`tools`), and harness configuration (`config` including `base_url`, `model`, `max_tool_iterations`, `verbose`, and `depth`). ## 🛠️ How to Build diff --git a/include/json_types.hpp b/include/json_types.hpp index d9c87b8..0f051ed 100644 --- a/include/json_types.hpp +++ b/include/json_types.hpp @@ -362,8 +362,10 @@ struct HarnessConfig { size_t max_tool_iterations = 16; bool verbose = false; size_t depth = 0; + std::string base_url = "https://192.168.8.63/ollama"; + std::string model = "qwen3.6:35b-a3b-128k"; }; -DRH_DEFINE_TYPE_OPTIONAL(HarnessConfig, max_tool_iterations, verbose, depth) +DRH_DEFINE_TYPE_OPTIONAL(HarnessConfig, max_tool_iterations, verbose, depth, base_url, model) struct FunctionCall { std::string name = ""; diff --git a/include/ollama_client.hpp b/include/ollama_client.hpp index 648061e..c63880c 100644 --- a/include/ollama_client.hpp +++ b/include/ollama_client.hpp @@ -9,15 +9,13 @@ using http_req_cb_t = std::function; struct OllamaConfig { - std::string base_url = "https://192.168.8.63/ollama"; - std::string model = "qwen3.6:35b-a3b-128k"; std::string auth_token = ""; int timeout_s = 60*10; }; class OllamaClient : public Client { public: - explicit OllamaClient(OllamaConfig config = {}); + explicit OllamaClient(std::string base_url, std::string model, OllamaConfig config = {}); ~OllamaClient() override; std::expected chat(const std::vector& messages, @@ -26,6 +24,8 @@ public: std::expected, std::string> list_models() const override; private: + std::string base_url_; + std::string model_; OllamaConfig config_; nlohmann::json build_body(const std::vector& messages, const std::vector& tools) const; diff --git a/src/main.cpp b/src/main.cpp index e33902a..63f86df 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -97,10 +97,11 @@ static HarnessState read_harness_state_or_exit(const char* file_path) { } int main(int argc, char** argv) { - OllamaConfig cfg; + HarnessConfig cfg; + OllamaConfig ollama_cfg; if (const char* m = std::getenv("OLLAMA_MODEL")) cfg.model = m; if (const char* u = std::getenv("OLLAMA_URL")) cfg.base_url = u; - if (const char* a = std::getenv("OLLAMA_AUTHORIZATION_TOKEN")) cfg.auth_token = a; + if (const char* a = std::getenv("OLLAMA_AUTHORIZATION_TOKEN")) ollama_cfg.auth_token = a; bool use_debug = false; bool debug_paths = false; @@ -417,7 +418,7 @@ int main(int argc, char** argv) { auto create_client = [&](bool debug) -> std::unique_ptr { if (debug) return std::make_unique(); - return std::make_unique(cfg); + return std::make_unique(cfg.base_url, cfg.model, ollama_cfg); }; // Scan for skills @@ -647,7 +648,12 @@ int main(int argc, char** argv) { } } - Harness harness(create_client(use_debug), agent_tools, {.max_tool_iterations = 1000, .verbose = true}); + Harness harness(create_client(use_debug), agent_tools, HarnessConfig{ + .max_tool_iterations = 1000, + .verbose = true, + .base_url = cfg.base_url, + .model = cfg.model + }); if (!start_new) { diff --git a/src/ollama_client.cpp b/src/ollama_client.cpp index 1e2761b..e355dd8 100644 --- a/src/ollama_client.cpp +++ b/src/ollama_client.cpp @@ -7,7 +7,8 @@ static int ollama_clients = 0; -OllamaClient::OllamaClient(OllamaConfig config) : config_(std::move(config)) { +OllamaClient::OllamaClient(std::string base_url, std::string model, OllamaConfig config) + : base_url_(std::move(base_url)), model_(std::move(model)), config_(std::move(config)) { if (ollama_clients == 0) curl_global_init(CURL_GLOBAL_DEFAULT); @@ -98,7 +99,7 @@ std::expected OllamaClient::http_request(const std::st nlohmann::json OllamaClient::build_body(const std::vector& messages, const std::vector& tools) const { nlohmann::json body = { - {"model", config_.model}, + {"model", model_}, {"stream", true}, {"messages", messages}, {"stream_options", { @@ -119,7 +120,7 @@ std::expected OllamaClient::chat(const std::vector& tools) const { nlohmann::json body = build_body(messages, tools); std::string body_str = body.dump(); - std::string url = config_.base_url + "/v1/chat/completions"; + std::string url = base_url_ + "/v1/chat/completions"; Message total{}; Usage usage{}; @@ -227,7 +228,7 @@ std::expected OllamaClient::chat(const std::vector, std::string> OllamaClient::list_models() const { - std::string url = config_.base_url + "/api/tags"; + std::string url = base_url_ + "/api/tags"; auto response_result = http_request(url, "GET", ""); if (!response_result) { return std::unexpected(response_result.error()); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 2b1686e..9040e5a 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -160,3 +160,6 @@ add_dr_test(test_print_history_multimodal) # Add encoding tests add_dr_test(test_encoding) + +# Add harness config tests (base_url and model in HarnessConfig) +add_dr_test(test_harness_config) diff --git a/tests/test_harness_config.sh b/tests/test_harness_config.sh new file mode 100755 index 0000000..246eba9 --- /dev/null +++ b/tests/test_harness_config.sh @@ -0,0 +1,61 @@ +#!/bin/bash +# test_harness_config.sh - Tests for HarnessConfig with base_url and model fields + +# Test 1: Verify config is saved with base_url and model in new conversations +echo '{"choices":[{"delta":{"content":"Response","role":"assistant"},"finish_reason":"stop"}],"usage":{"completion_tokens":10,"prompt_tokens":5,"total_tokens":15}}' | "${DRHARNESS}" \ + -F "${TEST_TEMP_DIR}/chat.json" \ + -n \ + -d \ + -m "test" 2>/dev/null + +# Verify config contains base_url and model +BASE_URL=$(cat "${TEST_TEMP_DIR}/chat.json" | jq -r '.config.base_url') +MODEL=$(cat "${TEST_TEMP_DIR}/chat.json" | jq -r '.config.model') + +if [ -z "$BASE_URL" ] || [ "$BASE_URL" = "null" ]; then + echo "FAIL: Expected config.base_url to be set" + exit 1 +fi + +if [ -z "$MODEL" ] || [ "$MODEL" = "null" ]; then + echo "FAIL: Expected config.model to be set" + exit 1 +fi + +# Test 2: Verify -M flag sets model in config +echo '{"choices":[{"delta":{"content":"Response","role":"assistant"},"finish_reason":"stop"}],"usage":{"completion_tokens":10,"prompt_tokens":5,"total_tokens":15}}' | "${DRHARNESS}" \ + -F "${TEST_TEMP_DIR}/chat2.json" \ + -n \ + -d \ + -M "custom-model" \ + -m "test" 2>/dev/null + +MODEL2=$(cat "${TEST_TEMP_DIR}/chat2.json" | jq -r '.config.model') +if [ "$MODEL2" != "custom-model" ]; then + echo "FAIL: Expected config.model to be 'custom-model', got '${MODEL2}'" + exit 1 +fi + +# Test 3: Verify backward compatibility - loading file without base_url and model +echo '{"history":[{"role":"user","content":"hello"}],"tools":[],"config":{"max_tool_iterations":16,"verbose":false,"depth":0}}' > "${TEST_TEMP_DIR}/old_format.json" + +echo '{"choices":[{"delta":{"content":"Response","role":"assistant"},"finish_reason":"stop"}],"usage":{"completion_tokens":10,"prompt_tokens":5,"total_tokens":15}}' | "${DRHARNESS}" \ + -F "${TEST_TEMP_DIR}/old_format.json" \ + -d \ + -m "test" 2>/dev/null + +# Verify the saved file now has base_url and model with defaults +SAVED_BASE_URL=$(cat "${TEST_TEMP_DIR}/old_format.json" | jq -r '.config.base_url') +SAVED_MODEL=$(cat "${TEST_TEMP_DIR}/old_format.json" | jq -r '.config.model') + +if [ -z "$SAVED_BASE_URL" ] || [ "$SAVED_BASE_URL" = "null" ]; then + echo "FAIL: Expected saved config.base_url to be set" + exit 1 +fi + +if [ -z "$SAVED_MODEL" ] || [ "$SAVED_MODEL" = "null" ]; then + echo "FAIL: Expected saved config.model to be set" + exit 1 +fi + +echo "All harness config tests passed" diff --git a/tools/agent_tool.cpp b/tools/agent_tool.cpp index adff7f1..150f3e0 100644 --- a/tools/agent_tool.cpp +++ b/tools/agent_tool.cpp @@ -48,14 +48,14 @@ Tool make_agent_tool(const AgentProfile& profile, } ToolRegistry sub_registry = std::move(sub_reg_result.value()); - Harness harness(std::make_unique(OllamaConfig{ - .base_url = base_url, - .model = profile.model, + Harness harness(std::make_unique(base_url, profile.model, OllamaConfig{ .timeout_s = 120 - }), sub_registry, { + }), sub_registry, HarnessConfig{ .max_tool_iterations = 16, .verbose = true, - .depth = depth + .depth = depth, + .base_url = base_url, + .model = profile.model }); auto result = harness.chat(task);