Add ToolDispatcher for Enhanced Tool Call Management

- Introduced `ToolDispatcher` class to handle tool calls from the AI agent, allowing for dynamic execution of commands based on user requests.
- Updated `ConversationalAgentService` to integrate tool dispatching, enabling the agent to respond to tool calls and manage execution results.
- Enhanced `AgentResponse` structure to include a list of tool calls, facilitating communication between the AI and the tool dispatcher.
- Modified AI service implementations to parse and include tool calls in responses, improving the agent's interactive capabilities.

This commit significantly enhances the z3ed system's ability to manage and execute tool calls, paving the way for more complex interactions in ROM hacking.
This commit is contained in:
scawful
2025-10-03 12:47:15 -04:00
parent 208b9ade51
commit 7c2bf8e1c7
11 changed files with 156 additions and 2 deletions

View File

@@ -3,15 +3,25 @@
#include <string>
#include <vector>
#include <map>
namespace yaze {
namespace cli {
// Represents a request from the AI to call a tool.
struct ToolCall {
std::string tool_name;
std::map<std::string, std::string> args;
};
// A structured response from an AI service.
struct AgentResponse {
// A natural language response to the user.
std::string text_response;
// A list of tool calls the agent wants to make.
std::vector<ToolCall> tool_calls;
// A list of z3ed commands to be executed.
std::vector<std::string> commands;

View File

@@ -207,6 +207,23 @@ absl::StatusOr<AgentResponse> GeminiAIService::ParseGeminiResponse(
agent_response.reasoning =
response_json["reasoning"].get<std::string>();
}
if (response_json.contains("tool_calls") &&
response_json["tool_calls"].is_array()) {
for (const auto& call : response_json["tool_calls"]) {
if (call.contains("tool_name") && call["tool_name"].is_string()) {
ToolCall tool_call;
tool_call.tool_name = call["tool_name"].get<std::string>();
if (call.contains("args") && call["args"].is_object()) {
for (auto& [key, value] : call["args"].items()) {
if (value.is_string()) {
tool_call.args[key] = value.get<std::string>();
}
}
}
agent_response.tool_calls.push_back(tool_call);
}
}
}
if (response_json.contains("commands") &&
response_json["commands"].is_array()) {
for (const auto& cmd : response_json["commands"]) {

View File

@@ -246,6 +246,23 @@ absl::StatusOr<AgentResponse> OllamaAIService::GenerateResponse(
response_json["reasoning"].is_string()) {
agent_response.reasoning = response_json["reasoning"].get<std::string>();
}
if (response_json.contains("tool_calls") &&
response_json["tool_calls"].is_array()) {
for (const auto& call : response_json["tool_calls"]) {
if (call.contains("tool_name") && call["tool_name"].is_string()) {
ToolCall tool_call;
tool_call.tool_name = call["tool_name"].get<std::string>();
if (call.contains("args") && call["args"].is_object()) {
for (auto& [key, value] : call["args"].items()) {
if (value.is_string()) {
tool_call.args[key] = value.get<std::string>();
}
}
}
agent_response.tool_calls.push_back(tool_call);
}
}
}
if (response_json.contains("commands") &&
response_json["commands"].is_array()) {
for (const auto& cmd : response_json["commands"]) {

View File

@@ -122,6 +122,17 @@ void PromptBuilder::LoadDefaultExamples() {
"Yes, I can validate the ROM for you.",
{"rom validate"},
"Validation ensures ROM integrity after tile modifications"});
// ==========================================================================
// Q&A / Tool Use
// ==========================================================================
examples_.push_back(
{"What dungeons are in this project?",
"I can list the dungeons for you. Let me check the resource labels.",
{},
"The user is asking a question. I need to use the `resource-list` tool "
"to find the answer.",
{{"resource-list", {{"type", "dungeon"}}}}});
}
absl::Status PromptBuilder::LoadResourceCatalogue(const std::string& yaml_path) {
@@ -197,6 +208,18 @@ std::string PromptBuilder::BuildFewShotExamplesSection() {
oss << "**Commands:**\n";
oss << "```json\n{";
oss << " \"text_response\": \"" << example.text_response << "\",\n";
oss << " \"tool_calls\": [";
std::vector<std::string> tool_calls;
for (const auto& call : example.tool_calls) {
std::vector<std::string> args;
for (const auto& [key, value] : call.args) {
args.push_back("\"" + key + "\": \"" + value + "\"");
}
tool_calls.push_back("{\"tool_name\": \"" + call.tool_name +
"\", \"args\": {" + absl::StrJoin(args, ", ") + "}}");
}
oss << absl::StrJoin(tool_calls, ", ");
oss << "],\n";
oss << " \"commands\": [";
std::vector<std::string> quoted_cmds;
@@ -220,12 +243,14 @@ std::string PromptBuilder::BuildConstraintsSection() {
1. **Output Format:** You MUST respond with ONLY a JSON object with the following structure:
{
"text_response": "Your natural language reply to the user.",
"tool_calls": [{ "tool_name": "tool_name", "args": { "arg1": "value1" } }],
"commands": ["command1", "command2"],
"reasoning": "Your thought process."
}
- `text_response` is for conversational replies.
- `commands` is for executable z3ed commands. It can be an empty array.
- NO explanatory text before or after the JSON object.
- `tool_calls` is for asking questions about the ROM. Use the available tools.
- `commands` is for generating commands to modify the ROM.
- All fields are optional.
2. **Command Syntax:** Follow the exact syntax shown in examples
- Use correct flag names (--group, --id, --to, --from, etc.)

View File

@@ -23,6 +23,7 @@ struct FewShotExample {
std::string text_response;
std::vector<std::string> expected_commands;
std::string explanation; // Why these commands work
std::vector<ToolCall> tool_calls;
};
// ROM context information to inject into prompts