Test that tools are executed only once per step in multi-step mode
| 55 | |
| 56 | // Test that tools are executed only once per step in multi-step mode |
| 57 | TEST_P(MultiStepDuplicateExecutionTest, ToolsExecutedOncePerStepNotTwice) { |
| 58 | if (!use_real_api_) { |
| 59 | GTEST_SKIP() << "No API key set for " << GetParam(); |
| 60 | } |
| 61 | |
| 62 | // Create a shared counter to track tool executions |
| 63 | auto execution_count = std::make_shared<std::atomic<int>>(0); |
| 64 | |
| 65 | // Create a tool that increments the counter each time it's called |
| 66 | Tool counter_tool = create_simple_tool( |
| 67 | "get_counter", "Returns the current count", {{"message", "string"}}, |
| 68 | [execution_count](const JsonValue& args, |
| 69 | const ToolExecutionContext& context) { |
| 70 | int count = execution_count->fetch_add(1) + 1; |
| 71 | std::string message = args["message"].get<std::string>(); |
| 72 | ai::logger::log_info("Counter tool executed! Count: {}, Message: {}", |
| 73 | count, message); |
| 74 | return JsonValue{{"count", count}, {"message", message}}; |
| 75 | }); |
| 76 | |
| 77 | ToolSet tools = {{"get_counter", counter_tool}}; |
| 78 | |
| 79 | // Configure options with multi-step mode enabled (max_steps > 1) |
| 80 | GenerateOptions options(model_, |
| 81 | "Please use the get_counter tool with message " |
| 82 | "'test' to get the current count."); |
| 83 | options.tools = tools; |
| 84 | options.max_steps = 2; // Enable multi-step mode |
| 85 | options.max_tokens = 300; |
| 86 | |
| 87 | // Reset counter before test |
| 88 | execution_count->store(0); |
| 89 | |
| 90 | // Execute the request |
| 91 | auto result = client_->generate_text(options); |
| 92 | |
| 93 | // Verify result is successful |
| 94 | EXPECT_TRUE(result.is_success()) |
| 95 | << "Expected successful result but got error: " << result.error_message(); |
| 96 | |
| 97 | // Verify the tool was called |
| 98 | EXPECT_TRUE(result.has_tool_calls()) << "Expected tool calls to be made"; |
| 99 | EXPECT_GT(result.tool_calls.size(), 0) << "Expected at least one tool call"; |
| 100 | |
| 101 | // Verify tool results exist |
| 102 | EXPECT_TRUE(result.has_tool_results()) |
| 103 | << "Expected tool results to be present"; |
| 104 | |
| 105 | // Log the execution count |
| 106 | int final_count = execution_count->load(); |
| 107 | ai::logger::log_info( |
| 108 | "Final execution count: {} (expected 1 per tool call, got {})", |
| 109 | final_count, result.tool_calls.size()); |
| 110 | |
| 111 | // CRITICAL ASSERTION: Each tool call should be executed exactly once |
| 112 | // If this fails, it means tools are being executed multiple times |
| 113 | EXPECT_EQ(final_count, static_cast<int>(result.tool_calls.size())) |
| 114 | << "Tool execution count (" << final_count |
nothing calls this directly
no test coverage detected