diff --git a/docs/tasklets.md b/docs/tasklets.md index 51cadde..266d83a 100644 --- a/docs/tasklets.md +++ b/docs/tasklets.md @@ -136,15 +136,15 @@ For more information about the default namespaces see [the namespaces documentat ### Custom Tools -Additional tools can be defined in the tasklet's `functions` section, and each is a group of actions that can be used by the agent, defining a `name`, `description` and a `tool` field with the command to be executed: +Additional tools can be defined in the tasklet's `tool_box` section, and each is a group of tools that can be used by the agent, defining a `name`, `description` and a `tool` field with the command to be executed: ```yaml # ... snippet ... -functions: +tool_box: - name: News decription: You will use this action to read the recent news. - actions: + tools: - name: read_news description: "To read the recent news:" # the output of this command will be returned to the agent @@ -157,9 +157,9 @@ If the agent must provide arguments to the tool, it is possible to define an exa ```yaml # ... snippet ... -functions: +tool_box: - name: Environment - actions: + tools: - name: report_finding description: When you are ready to report findings, use this tool for each finding. example_payload: > @@ -178,10 +178,10 @@ If the tool requires named arguments it is possible to define them in the `args` ```yaml # ... snippet ... -functions: +tool_box: - name: Conversation description: You will use these actions to create conversational entries. - actions: + tools: - name: talk description: "To have one of the characters say a specific sentence:" example_payload: hi, how are you doing today? diff --git a/docs/workflows.md b/docs/workflows.md index f2d4c45..7d5ee16 100644 --- a/docs/workflows.md +++ b/docs/workflows.md @@ -46,9 +46,9 @@ prompt: prepare a list of ingredients for $food guidance: - Once you have made a list of ingredients, use the create_list_of_ingredients tool to confirm the decision. -functions: +tool_box: - name: Tools - actions: + tools: - name: create_list_of_ingredients description: "To provide the ingredients one per line as an organized list:" store_to: ingredients @@ -75,9 +75,9 @@ guidance: - Use the describe_preparation_steps tool to describe each step in the preparation. - Once you have described each step in the preparation of the pie set your task as complete. -functions: +tool_box: - name: Tools - actions: + tools: - name: describe_preparation_steps description: "To provide the preparation steps one per line as an organized list:" store_to: steps @@ -108,9 +108,9 @@ prompt: > guidance: - Once you have made an estimation, use the estimate_time tool to confirm the decision. -functions: +tool_box: - name: Tools - actions: + tools: - name: estimate_time description: "To provide the time it will take to prepare the food:" store_to: preparation_time @@ -142,9 +142,9 @@ prompt: > guidance: - Once you have completed the task, use the rewrite tool to confirm the decision. -functions: +tool_box: - name: Tools - actions: + tools: - name: rewrite description: "To confirm your version of the recipe:" store_to: report diff --git a/examples/ab_problem/eval.py b/examples/ab_problem/eval.py index b504362..58cdd1c 100755 --- a/examples/ab_problem/eval.py +++ b/examples/ab_problem/eval.py @@ -66,13 +66,11 @@ def solve(xs): def get_solution(message): if ( message["type"] == "agent" - and "data" in message - and message["data"] is not None - and message["data"][1] is not None - and message["data"][1]["action"] == "solution" - and message["data"][1]["payload"] is not None + and "tool_call" in message["data"] + and message["data"]["tool_call"] is not None + and message["data"]["tool_call"]["tool_name"] == "solution" ): - return message["data"][1]["payload"].strip().split(" ") + return message["data"]["tool_call"]["argument"].strip().split(" ") return None @@ -90,7 +88,7 @@ def get_solution(message): solution = None # find the most recent solution in the chat history - for message in reversed(state["chat"]["history"]["conversation"]): + for message in reversed(state["chat"]["history"]["messages"]): solution = get_solution(message) if solution is not None: if actual == solution: diff --git a/examples/ab_problem/task.yml b/examples/ab_problem/task.yml index c0e2281..49817d4 100644 --- a/examples/ab_problem/task.yml +++ b/examples/ab_problem/task.yml @@ -46,10 +46,10 @@ evaluator: - python3 - eval.py -functions: +tool_box: - name: Solve - description: You will use these actions to provide final answer to the problem. - actions: + description: You will use these tools to provide the final answer to the problem. + tools: - name: solution description: "To provide final result:" example_payload: B# diff --git a/examples/ai_news/task.yml b/examples/ai_news/task.yml index 8ffcd19..dce7c73 100644 --- a/examples/ai_news/task.yml +++ b/examples/ai_news/task.yml @@ -12,14 +12,11 @@ prompt: > guidance: - You will first use the read_news command and then write the bullet points to your memory. -functions: +tool_box: - name: News - decription: You will use this action to read the recent news. - actions: + decription: You will use this tools to read the recent news. + tools: - name: read_news description: "To read the recent news:" max_shown_output: 4096 - tool: curl -s getnews.tech/ai,nocolor - - - \ No newline at end of file + tool: curl -s getnews.tech/ai,nocolor \ No newline at end of file diff --git a/examples/code_auditor/task.yml b/examples/code_auditor/task.yml index 53aefd3..f5bb867 100644 --- a/examples/code_auditor/task.yml +++ b/examples/code_auditor/task.yml @@ -21,9 +21,9 @@ guidance: prompt: > find vulnerabilities in source code in $TARGET_PATH and report your findings. -functions: +tool_box: - name: Report - actions: + tools: - name: report_finding description: Use this tool to report your findings. example_payload: > diff --git a/examples/code_auditor_with_judge/judge.yml b/examples/code_auditor_with_judge/judge.yml index 1b9c62e..a541cd6 100644 --- a/examples/code_auditor_with_judge/judge.yml +++ b/examples/code_auditor_with_judge/judge.yml @@ -12,9 +12,9 @@ guidance: prompt: > confirm by using the judge tool whether or not this is a vulnerability: $STDIN -functions: +tool_box: - name: Judge - actions: + tools: - name: confirm_vulnerability description: Use this tool to confirm whether or not the finding is a vulnerability. complete_task: true diff --git a/examples/code_auditor_with_judge/task.yml b/examples/code_auditor_with_judge/task.yml index e8d9283..4de9880 100644 --- a/examples/code_auditor_with_judge/task.yml +++ b/examples/code_auditor_with_judge/task.yml @@ -23,9 +23,9 @@ guidance: prompt: > find vulnerabilities in source code in $TARGET_PATH and report your findings. -functions: +tool_box: - name: Report - actions: + tools: - name: judge_finding description: Use this tool to ask an external expert to judge if a finding is a vulnerability or not. If the expert confirms it is a vulnerability, use the report_finding tool to report it. example_payload: > diff --git a/examples/docker-agent/task.yml b/examples/docker-agent/task.yml index 4b9477a..9cb60de 100644 --- a/examples/docker-agent/task.yml +++ b/examples/docker-agent/task.yml @@ -15,10 +15,10 @@ guidance: - Continue providing commands and processing outputs until you believe you have completed the task. - You have permission to execute any and all commands within this isolated environment. -functions: +tool_box: - name: Environment description: You have permission to execute any and all commands within this isolated environment. - actions: + tools: - name: execute_command description: The command will be executed on your host and the output will be returned to you. example_payload: "ls -la" diff --git a/examples/eval_test/eval.py b/examples/eval_test/eval.py index 47336e2..2e189dc 100644 --- a/examples/eval_test/eval.py +++ b/examples/eval_test/eval.py @@ -17,18 +17,5 @@ if "42" in raw: exit(42) - state = json.loads(raw) - - # uncomment this to validate the output of a tool in the history - """ - # in this case we're looping the chat history, we could just do substring matching really ... - for message in state["chat"]["history"]["conversation"]: - if message["type"] == "feedback": - invocation = message["data"][1] - if invocation is not None: - if invocation["action"] == "solution" and "42" in invocation["payload"]: - exit(42) - """ - # add a feedback message to the chat history print("try thinking about a funny book reference to answer") diff --git a/examples/eval_test/task.yml b/examples/eval_test/task.yml index 9a04a00..de706f5 100644 --- a/examples/eval_test/task.yml +++ b/examples/eval_test/task.yml @@ -19,10 +19,10 @@ evaluator: # tools are not needed here, the evaluator will just check the chat history -# functions: +# tool_box: # - name: Solve # description: You will use these actions to provide the answer to the problem. -# actions: +# tools: # - name: solution # description: "To provide the answer to the problem:" # example_payload: foobar diff --git a/examples/fuzzer/task.yml b/examples/fuzzer/task.yml index 1a7996f..e2793f2 100644 --- a/examples/fuzzer/task.yml +++ b/examples/fuzzer/task.yml @@ -20,11 +20,11 @@ guidance: - Try to often change the first characters of the payload in order to possibly trigger different execution paths. # the agent toolbox -functions: +tool_box: # divided in action groups - name: Fuzzing description: You will use these actions to inspect the source code of the executable, and interact with the fuzzing session. - actions: + tools: # let's give the model a way to check the source :D - name: inspect description: "To visualize the original source code of the executable:" diff --git a/examples/kali_pentester/task.yml b/examples/kali_pentester/task.yml index 2e0cb47..5bcd9e4 100644 --- a/examples/kali_pentester/task.yml +++ b/examples/kali_pentester/task.yml @@ -17,9 +17,9 @@ guidance: - If you need to use the command 'sudo' before something, determine if you are root and only use sudo if you are not. - Run commands one by one and avoid using && or other strategies to run multiple commands at once. -functions: +tool_box: - name: Commands - actions: + tools: - name: command description: "To execute a bash command on the Kali Linux computer:" example_payload: whoami diff --git a/examples/life/task.yml b/examples/life/task.yml index 5cc2ffd..f71dabc 100644 --- a/examples/life/task.yml +++ b/examples/life/task.yml @@ -17,9 +17,9 @@ guidance: - Create life forms that can build and use structures. - Create life forms that can build and use machines. -functions: +tool_box: - name: World - actions: + tools: - name: observe description: "To observe the world:" max_shown_output: 10000 diff --git a/examples/recipe_workflow/create_list_of_ingredients.yml b/examples/recipe_workflow/create_list_of_ingredients.yml index 28199dd..cefd1e9 100644 --- a/examples/recipe_workflow/create_list_of_ingredients.yml +++ b/examples/recipe_workflow/create_list_of_ingredients.yml @@ -7,9 +7,9 @@ prompt: prepare a list of ingredients for $food guidance: - Once you have made a list of ingredients, use the create_list_of_ingredients tool to confirm the decision. -functions: +tool_box: - name: Tools - actions: + tools: - name: create_list_of_ingredients description: "To provide the ingredients one per line as an organized list:" store_to: ingredients diff --git a/examples/recipe_workflow/describe_preparation_steps.yml b/examples/recipe_workflow/describe_preparation_steps.yml index 6036239..c1b69bd 100644 --- a/examples/recipe_workflow/describe_preparation_steps.yml +++ b/examples/recipe_workflow/describe_preparation_steps.yml @@ -8,9 +8,9 @@ guidance: - Use the describe_preparation_steps tool to describe each step in the preparation. - Once you have described each step in the preparation of the pie set your task as complete. -functions: +tool_box: - name: Tools - actions: + tools: - name: describe_preparation_steps description: "To provide the preparation steps one per line as an organized list:" store_to: steps diff --git a/examples/recipe_workflow/estimate_time.yml b/examples/recipe_workflow/estimate_time.yml index 5e2a7e7..f6ce6ab 100644 --- a/examples/recipe_workflow/estimate_time.yml +++ b/examples/recipe_workflow/estimate_time.yml @@ -12,9 +12,9 @@ prompt: > guidance: - Once you have made an estimation, use the estimate_time tool to confirm the decision. -functions: +tool_box: - name: Tools - actions: + tools: - name: estimate_time description: "To provide the time it will take to prepare the food:" store_to: preparation_time diff --git a/examples/recipe_workflow/rewrite_nicely.yml b/examples/recipe_workflow/rewrite_nicely.yml index d1bf57b..a685bb4 100644 --- a/examples/recipe_workflow/rewrite_nicely.yml +++ b/examples/recipe_workflow/rewrite_nicely.yml @@ -18,9 +18,9 @@ prompt: > guidance: - Once you have completed the task, use the rewrite tool to confirm the decision. -functions: +tool_box: - name: Tools - actions: + tools: - name: rewrite description: "To confirm your version of the recipe:" store_to: report diff --git a/examples/screenshot/task.yml b/examples/screenshot/task.yml index b7ba6ee..1f0ca02 100644 --- a/examples/screenshot/task.yml +++ b/examples/screenshot/task.yml @@ -7,9 +7,9 @@ guidance: prompt: Take a screenshot and describe it. -functions: +tool_box: - name: Desktop - actions: + tools: - name: take_screenshot description: Take a screenshot of the current screen. tool: ./screenshot.py diff --git a/examples/software_dev_workflow/chief_product_officer.yml b/examples/software_dev_workflow/chief_product_officer.yml index c13d7e6..fa2edfe 100644 --- a/examples/software_dev_workflow/chief_product_officer.yml +++ b/examples/software_dev_workflow/chief_product_officer.yml @@ -9,9 +9,9 @@ prompt: Prepare a list of high level requirements for $product guidance: - Once you have made a list of requirements, use the create_list_of_requirements tool to confirm the decision. -functions: +tool_box: - name: Tools - actions: + tools: - name: create_list_of_requirements description: "To provide the requirements one per line as an organized list:" store_to: requirements diff --git a/examples/software_dev_workflow/chief_technology_officer.yml b/examples/software_dev_workflow/chief_technology_officer.yml index 1e6dcf6..5c75c83 100644 --- a/examples/software_dev_workflow/chief_technology_officer.yml +++ b/examples/software_dev_workflow/chief_technology_officer.yml @@ -14,9 +14,9 @@ prompt: > guidance: - Once you have made a list of technologies, use the create_list_of_technologies tool to confirm the decision. -functions: +tool_box: - name: Tools - actions: + tools: - name: create_list_of_technologies description: "To provide the technologies one per line as an organized list:" store_to: tech_stack diff --git a/examples/software_dev_workflow/manager.yml b/examples/software_dev_workflow/manager.yml index a22e387..b61a32f 100644 --- a/examples/software_dev_workflow/manager.yml +++ b/examples/software_dev_workflow/manager.yml @@ -18,9 +18,9 @@ prompt: > guidance: - Once you have made a list of tasks, use the create_list_of_tasks tool to confirm the decision. -functions: +tool_box: - name: Tools - actions: + tools: - name: create_list_of_tasks description: "To provide the tasks one per line as an organized list:" store_to: tasks diff --git a/examples/ssh_agent/task.yml b/examples/ssh_agent/task.yml index 17b31b8..69ba942 100644 --- a/examples/ssh_agent/task.yml +++ b/examples/ssh_agent/task.yml @@ -28,10 +28,10 @@ guidance: timeout: 120s # the agent toolbox -functions: +tool_box: # divided in namespaces - name: Commands - actions: + tools: - name: ssh # explains to the model when to use this action description: "To execute a bash command on the remote host via SSH:" diff --git a/examples/web_fingerprint/task.yml b/examples/web_fingerprint/task.yml index a0b1fbb..e684b41 100644 --- a/examples/web_fingerprint/task.yml +++ b/examples/web_fingerprint/task.yml @@ -19,9 +19,9 @@ guidance: - If a page returns a "not found" error stop requesting it and try another page. - Use exclusively the report_findings tool to report your findings. -functions: +tool_box: - name: Report - actions: + tools: - name: report_finding description: Use this tool to report EACH of your findings. example_payload: > diff --git a/examples/webcam/task.yml b/examples/webcam/task.yml index bfd0555..8aa78ee 100644 --- a/examples/webcam/task.yml +++ b/examples/webcam/task.yml @@ -13,9 +13,9 @@ guidance: - Do NOT inform the user for every image, only do it if you see something interesting. - Do NOT inform the user if you see nothing interesting or no state changes are happening. -functions: +tool_box: - name: Enviroment - actions: + tools: - name: read_webcam_image description: Check the image from the webcam. tool: ./webcam.py diff --git a/examples/weird_chat/task.yml b/examples/weird_chat/task.yml index 12ee6e7..014db14 100644 --- a/examples/weird_chat/task.yml +++ b/examples/weird_chat/task.yml @@ -16,11 +16,11 @@ guidance: - Try to use different lines each time and adapt them to the context of the conversation. # the agent toolbox -functions: +tool_box: # divided in action groups - name: Conversation description: You will use these actions to create conversational entries. - actions: + tools: - name: talk # show up to 4096 output characters max_shown_output: 4096 @@ -32,6 +32,4 @@ functions: tool: ./talk.py # in case the command requires arguments, declare them with an example value args: - character_name: NameOfTheSpeakingCharacter - - \ No newline at end of file + character_name: NameOfTheSpeakingCharacter \ No newline at end of file diff --git a/src/agent/events/mod.rs b/src/agent/events/mod.rs index d69475c..a356ca1 100644 --- a/src/agent/events/mod.rs +++ b/src/agent/events/mod.rs @@ -7,7 +7,7 @@ mod channel; pub use channel::*; -use super::namespaces::ActionOutput; +use super::namespaces::ToolOutput; use super::task::tasklet::Tasklet; use super::workflow::Workflow; use super::{ @@ -42,21 +42,21 @@ pub enum EventType { Thinking(String), Sleeping(usize), TextResponse(String), - InvalidAction { + InvalidToolCall { tool_call: ToolCall, error: Option, }, - ActionTimeout { + ToolCallTimeout { tool_call: ToolCall, elapsed: std::time::Duration, }, - ActionExecuting { + BeforeToolCall { tool_call: ToolCall, }, - ActionExecuted { + AfterToolCall { tool_call: ToolCall, error: Option, - result: Option, + result: Option, elapsed: std::time::Duration, complete_task: bool, }, diff --git a/src/agent/generator/anthropic.rs b/src/agent/generator/anthropic.rs index bb91ad5..17fa431 100644 --- a/src/agent/generator/anthropic.rs +++ b/src/agent/generator/anthropic.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use crate::agent::{ generator::{ChatResponse, SupportedFeatures, Usage}, - namespaces::ActionOutput, + namespaces::ToolOutput, state::SharedState, ToolCall, }; @@ -46,12 +46,12 @@ impl AnthropicClient { if state.lock().await.use_native_tools_format { // for every namespace available to the model for group in state.lock().await.get_namespaces() { - // for every action of the namespace - for action in &group.actions { + // for every tool of the namespace + for tool in &group.tools { let mut required = vec![]; let mut properties = HashMap::new(); - if let Some(example) = action.example_payload() { + if let Some(example) = tool.example_payload() { required.push("payload".to_string()); properties.insert( "payload".to_string(), @@ -65,7 +65,7 @@ impl AnthropicClient { ); } - if let Some(attrs) = action.example_attributes() { + if let Some(attrs) = tool.example_attributes() { for name in attrs.keys() { required.push(name.to_string()); properties.insert( @@ -85,8 +85,8 @@ impl AnthropicClient { }); tools.push(ToolDefinition::new( - action.name(), - Some(action.description().to_string()), + tool.name(), + Some(tool.description().to_string()), input_schema, )); } @@ -178,10 +178,10 @@ impl Client for AnthropicClient { result, tool_call: _, } => match result { - ActionOutput::Image { data, mime_type } => messages.push(Message::user( + ToolOutput::Image { data, mime_type } => messages.push(Message::user( Content::from(ImageContentSource::base64(get_media_type(mime_type), data)), )), - ActionOutput::Text(text) => { + ToolOutput::Text(text) => { let trimmed = text.trim(); if !trimmed.is_empty() { messages.push(Message::user(trimmed)) diff --git a/src/agent/generator/groq.rs b/src/agent/generator/groq.rs index a017a0c..2ef3248 100644 --- a/src/agent/generator/groq.rs +++ b/src/agent/generator/groq.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use crate::{ - agent::namespaces::ActionOutput, + agent::namespaces::ToolOutput, api::groq::completion::{ client::Groq, message::{ImageContent, ImageUrl}, @@ -175,7 +175,7 @@ impl Client for GroqClient { } if tool_call_id.is_some() { match result { - ActionOutput::Text(text) => { + ToolOutput::Text(text) => { crate::api::groq::completion::message::Message::ToolMessage { role: Some("tool".to_string()), content: Some(text.to_string()), @@ -184,7 +184,7 @@ impl Client for GroqClient { image_content: None, } } - ActionOutput::Image { data, mime_type } => { + ToolOutput::Image { data, mime_type } => { // can't use images for ToolMessage crate::api::groq::completion::message::Message::UserMessage { role: Some("user".to_string()), @@ -208,7 +208,7 @@ impl Client for GroqClient { } } else { match result { - ActionOutput::Text(text) => { + ToolOutput::Text(text) => { crate::api::groq::completion::message::Message::UserMessage { role: Some("user".to_string()), content: Some(text.to_string()), @@ -217,7 +217,7 @@ impl Client for GroqClient { image_content: None, } } - ActionOutput::Image { data, mime_type } => { + ToolOutput::Image { data, mime_type } => { crate::api::groq::completion::message::Message::UserMessage { role: Some("user".to_string()), content: None, @@ -249,11 +249,11 @@ impl Client for GroqClient { let mut tools = vec![]; for group in state.lock().await.get_namespaces() { - for action in &group.actions { + for tool in &group.tools { let mut required = vec![]; let mut properties = HashMap::new(); - if let Some(example) = action.example_payload() { + if let Some(example) = tool.example_payload() { required.push("payload".to_string()); properties.insert( "payload".to_string(), @@ -267,7 +267,7 @@ impl Client for GroqClient { ); } - if let Some(attrs) = action.example_attributes() { + if let Some(attrs) = tool.example_attributes() { for name in attrs.keys() { required.push(name.to_string()); properties.insert( @@ -281,8 +281,8 @@ impl Client for GroqClient { } let function = Function { - name: Some(action.name().to_string()), - description: Some(action.description().to_string()), + name: Some(tool.name().to_string()), + description: Some(tool.description().to_string()), parameters: Some(serde_json::json!(GroqFunctionParameters { the_type: "object".to_string(), required, diff --git a/src/agent/generator/history.rs b/src/agent/generator/history.rs index 9eae985..8e6679f 100644 --- a/src/agent/generator/history.rs +++ b/src/agent/generator/history.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -use crate::agent::namespaces::ActionOutput; +use crate::agent::namespaces::ToolOutput; use super::Message; @@ -87,7 +87,7 @@ impl ChatHistory { Message::Feedback { tool_call: tool_call.clone(), result: if compressed.len() < result.to_string().len() { - ActionOutput::text(compressed.to_string()) + ToolOutput::text(compressed.to_string()) } else { result.clone() }, @@ -198,7 +198,7 @@ mod tests { }, Message::Feedback { tool_call: None, - result: ActionOutput::text("test2"), + result: ToolOutput::text("test2"), }, ]; let history = ChatHistory::create(conv.clone(), ConversationWindow::Full); @@ -213,7 +213,7 @@ mod tests { tool_call: None, }, Message::Feedback { - result: ActionOutput::text("test2"), + result: ToolOutput::text("test2"), tool_call: None, }, Message::Agent { @@ -221,7 +221,7 @@ mod tests { tool_call: None, }, Message::Feedback { - result: ActionOutput::text("test4"), + result: ToolOutput::text("test4"), tool_call: None, }, ]; @@ -254,7 +254,7 @@ mod tests { }, Message::Feedback { tool_call: None, - result: ActionOutput::text("test2"), + result: ToolOutput::text("test2"), }, ]; let history = ChatHistory::create(conv.clone(), ConversationWindow::Summary); @@ -270,7 +270,7 @@ mod tests { }, Message::Feedback { tool_call: None, - result: ActionOutput::text("long feedback that should be compressed"), + result: ToolOutput::text("long feedback that should be compressed"), }, Message::Agent { content: "test3".to_string(), @@ -278,7 +278,7 @@ mod tests { }, Message::Feedback { tool_call: None, - result: ActionOutput::text("final very very very very long feedback"), + result: ToolOutput::text("final very very very very long feedback"), }, ]; @@ -289,7 +289,7 @@ mod tests { }, Message::Feedback { tool_call: None, - result: ActionOutput::text(""), + result: ToolOutput::text(""), }, Message::Agent { content: "test3".to_string(), @@ -297,7 +297,7 @@ mod tests { }, Message::Feedback { tool_call: None, - result: ActionOutput::text("final very very very very long feedback"), + result: ToolOutput::text("final very very very very long feedback"), }, ]; @@ -314,7 +314,7 @@ mod tests { }, Message::Feedback { tool_call: None, - result: ActionOutput::text("ok"), + result: ToolOutput::text("ok"), }, Message::Agent { content: "test3".to_string(), @@ -322,7 +322,7 @@ mod tests { }, Message::Feedback { tool_call: None, - result: ActionOutput::text("final"), + result: ToolOutput::text("final"), }, ]; @@ -333,7 +333,7 @@ mod tests { }, Message::Feedback { tool_call: None, - result: ActionOutput::text("ok"), + result: ToolOutput::text("ok"), }, Message::Agent { content: "test3".to_string(), @@ -341,7 +341,7 @@ mod tests { }, Message::Feedback { tool_call: None, - result: ActionOutput::text("final"), + result: ToolOutput::text("final"), }, ]; @@ -359,7 +359,7 @@ mod tests { }, Message::Feedback { tool_call: tool_call.clone(), - result: ActionOutput::text("very very very very long feedback"), + result: ToolOutput::text("very very very very long feedback"), }, Message::Agent { content: "test3".to_string(), @@ -367,7 +367,7 @@ mod tests { }, Message::Feedback { tool_call: tool_call.clone(), - result: ActionOutput::text("final"), + result: ToolOutput::text("final"), }, ]; @@ -378,7 +378,7 @@ mod tests { }, Message::Feedback { tool_call: tool_call.clone(), - result: ActionOutput::text(""), + result: ToolOutput::text(""), }, Message::Agent { content: "test3".to_string(), @@ -386,7 +386,7 @@ mod tests { }, Message::Feedback { tool_call: tool_call.clone(), - result: ActionOutput::text("final"), + result: ToolOutput::text("final"), }, ]; @@ -403,7 +403,7 @@ mod tests { }, Message::Feedback { tool_call: None, - result: ActionOutput::text("feedback1"), + result: ToolOutput::text("feedback1"), }, Message::Agent { content: "test2".to_string(), @@ -411,7 +411,7 @@ mod tests { }, Message::Feedback { tool_call: None, - result: ActionOutput::text("feedback2"), + result: ToolOutput::text("feedback2"), }, Message::Agent { content: "test3".to_string(), @@ -419,7 +419,7 @@ mod tests { }, Message::Feedback { tool_call: None, - result: ActionOutput::text("feedback3"), + result: ToolOutput::text("feedback3"), }, Message::Agent { content: "test4".to_string(), @@ -427,7 +427,7 @@ mod tests { }, Message::Feedback { tool_call: None, - result: ActionOutput::text("feedback4"), + result: ToolOutput::text("feedback4"), }, ]; @@ -438,7 +438,7 @@ mod tests { }, Message::Feedback { tool_call: None, - result: ActionOutput::text("feedback3"), + result: ToolOutput::text("feedback3"), }, Message::Agent { content: "test4".to_string(), @@ -446,7 +446,7 @@ mod tests { }, Message::Feedback { tool_call: None, - result: ActionOutput::text("feedback4"), + result: ToolOutput::text("feedback4"), }, ]; @@ -463,7 +463,7 @@ mod tests { }, Message::Feedback { tool_call: None, - result: ActionOutput::text("feedback1"), + result: ToolOutput::text("feedback1"), }, ]; @@ -474,7 +474,7 @@ mod tests { }, Message::Feedback { tool_call: None, - result: ActionOutput::text("feedback1"), + result: ToolOutput::text("feedback1"), }, ]; @@ -491,7 +491,7 @@ mod tests { }, Message::Feedback { tool_call: None, - result: ActionOutput::text("feedback1"), + result: ToolOutput::text("feedback1"), }, Message::Agent { content: "test2".to_string(), @@ -499,7 +499,7 @@ mod tests { }, Message::Feedback { tool_call: None, - result: ActionOutput::text("feedback2"), + result: ToolOutput::text("feedback2"), }, Message::Agent { content: "test3".to_string(), @@ -507,7 +507,7 @@ mod tests { }, Message::Feedback { tool_call: None, - result: ActionOutput::text("feedback3"), + result: ToolOutput::text("feedback3"), }, Message::Agent { content: "test4".to_string(), @@ -515,14 +515,14 @@ mod tests { }, Message::Feedback { tool_call: None, - result: ActionOutput::text("feedback4"), + result: ToolOutput::text("feedback4"), }, ]; let expected = vec![ Message::Feedback { tool_call: None, - result: ActionOutput::text("feedback1"), + result: ToolOutput::text("feedback1"), }, Message::Agent { content: "test2".to_string(), @@ -530,7 +530,7 @@ mod tests { }, Message::Feedback { tool_call: None, - result: ActionOutput::text("feedback2"), + result: ToolOutput::text("feedback2"), }, Message::Agent { content: "test3".to_string(), @@ -538,7 +538,7 @@ mod tests { }, Message::Feedback { tool_call: None, - result: ActionOutput::text("feedback3"), + result: ToolOutput::text("feedback3"), }, Message::Agent { content: "test4".to_string(), @@ -546,7 +546,7 @@ mod tests { }, Message::Feedback { tool_call: None, - result: ActionOutput::text("feedback4"), + result: ToolOutput::text("feedback4"), }, ]; diff --git a/src/agent/generator/mod.rs b/src/agent/generator/mod.rs index 5a292c7..aba54ce 100644 --- a/src/agent/generator/mod.rs +++ b/src/agent/generator/mod.rs @@ -8,7 +8,7 @@ use lazy_static::lazy_static; use regex::Regex; use serde::{Deserialize, Serialize}; -use super::{namespaces::ActionOutput, state::SharedState, ToolCall}; +use super::{namespaces::ToolOutput, state::SharedState, ToolCall}; mod anthropic; mod deepseek; @@ -69,7 +69,7 @@ pub enum Message { Feedback { #[serde(skip_serializing_if = "Option::is_none")] tool_call: Option, - result: ActionOutput, + result: ToolOutput, }, } diff --git a/src/agent/generator/ollama.rs b/src/agent/generator/ollama.rs index d77074f..9f6d9be 100644 --- a/src/agent/generator/ollama.rs +++ b/src/agent/generator/ollama.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use crate::{ - agent::namespaces::ActionOutput, + agent::namespaces::ToolOutput, api::ollama::{ generation::{ chat::{ @@ -103,8 +103,6 @@ impl Client for OllamaClient { state: SharedState, options: &ChatOptions, ) -> anyhow::Result { - // TODO: images for multimodal (see todo for screenshot action) - // build chat history: // - system prompt // - user prompt @@ -130,8 +128,8 @@ impl Client for OllamaClient { result, tool_call: _, } => match result { - ActionOutput::Text(text) => ChatMessage::user(text.to_string()), - ActionOutput::Image { data, mime_type: _ } => ChatMessage::user("".to_string()) + ToolOutput::Text(text) => ChatMessage::user(text.to_string()), + ToolOutput::Image { data, mime_type: _ } => ChatMessage::user("".to_string()) .with_images(vec![Image::from_base64(data)]), }, }); @@ -142,11 +140,11 @@ impl Client for OllamaClient { if state.lock().await.use_native_tools_format { for group in state.lock().await.get_namespaces() { - for action in &group.actions { + for tool in &group.tools { let mut required = vec![]; let mut properties = HashMap::new(); - if let Some(example) = action.example_payload() { + if let Some(example) = tool.example_payload() { required.push("payload".to_string()); properties.insert( "payload".to_string(), @@ -161,7 +159,7 @@ impl Client for OllamaClient { ); } - if let Some(attrs) = action.example_attributes() { + if let Some(attrs) = tool.example_attributes() { for name in attrs.keys() { required.push(name.to_string()); properties.insert( @@ -176,8 +174,8 @@ impl Client for OllamaClient { } let function = ToolFunction { - name: action.name().to_string(), - description: action.description().to_string(), + name: tool.name().to_string(), + description: tool.description().to_string(), parameters: ToolFunctionParameters { the_type: "object".to_string(), required, diff --git a/src/agent/generator/openai.rs b/src/agent/generator/openai.rs index 61459a5..1205859 100644 --- a/src/agent/generator/openai.rs +++ b/src/agent/generator/openai.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use crate::api::openai::*; -use crate::{agent::namespaces::ActionOutput, api::openai::chat::*}; +use crate::{agent::namespaces::ToolOutput, api::openai::chat::*}; use anyhow::Result; use async_trait::async_trait; use embeddings::EmbeddingsApi; @@ -77,12 +77,12 @@ impl OpenAIClient { if state.lock().await.use_native_tools_format { // for every namespace available to the model for group in state.lock().await.get_namespaces() { - // for every action of the namespace - for action in &group.actions { + // for every tool of the namespace + for tool in &group.tools { let mut required = vec![]; let mut properties = HashMap::new(); - if let Some(example) = action.example_payload() { + if let Some(example) = tool.example_payload() { required.push("payload".to_string()); properties.insert( "payload".to_string(), @@ -96,7 +96,7 @@ impl OpenAIClient { ); } - if let Some(attrs) = action.example_attributes() { + if let Some(attrs) = tool.example_attributes() { for name in attrs.keys() { required.push(name.to_string()); properties.insert( @@ -110,8 +110,8 @@ impl OpenAIClient { } let function = FunctionDefinition { - name: action.name().to_string(), - description: Some(action.description().to_string()), + name: tool.name().to_string(), + description: Some(tool.description().to_string()), parameters: Some(serde_json::json!(OpenAiToolFunctionParameters { the_type: "object".to_string(), required, @@ -247,7 +247,7 @@ impl Client for OpenAIClient { result, tool_call: _, } => match result { - ActionOutput::Text(text) => { + ToolOutput::Text(text) => { // handles string_too_short cases (NIM) let mut content = text.trim().to_string(); if content.is_empty() { @@ -255,7 +255,7 @@ impl Client for OpenAIClient { } crate::api::openai::Message::text(&content, Role::User) } - ActionOutput::Image { data, mime_type } => { + ToolOutput::Image { data, mime_type } => { crate::api::openai::Message::image(data, mime_type, Role::User) } }, diff --git a/src/agent/mod.rs b/src/agent/mod.rs index d48857d..484866a 100644 --- a/src/agent/mod.rs +++ b/src/agent/mod.rs @@ -15,7 +15,7 @@ use generator::{ history::{ChatHistory, ConversationWindow}, ChatOptions, ChatResponse, Client, }; -use namespaces::{Action, ActionOutput}; +use namespaces::{Tool, ToolOutput}; use state::{SharedState, State}; use task::{eval::Evaluator, Task}; @@ -75,12 +75,12 @@ impl std::hash::Hash for ToolCall { impl ToolCall { pub fn new( - action: String, + tool_name: String, attributes: Option>, payload: Option, ) -> Self { Self { - tool_name: action, + tool_name, named_arguments: attributes, argument: payload, } @@ -196,10 +196,10 @@ impl Agent { } #[allow(clippy::borrowed_box)] - pub fn validate(&self, tool_call: &mut ToolCall, action: &Box) -> Result<()> { + pub fn validate(&self, tool_call: &mut ToolCall, tool: &Box) -> Result<()> { // validate prerequisites - let payload_required = action.example_payload().is_some(); - let attrs_required = action.example_attributes().is_some(); + let payload_required = tool.example_payload().is_some(); + let attrs_required = tool.example_attributes().is_some(); let mut has_payload = tool_call.argument.is_some(); let mut has_attributes = tool_call.named_arguments.is_some(); @@ -240,7 +240,7 @@ impl Agent { if attrs_required { // validate each required attribute - let required_attrs: Vec = action + let required_attrs: Vec = tool .example_attributes() .unwrap() .keys() @@ -339,7 +339,7 @@ impl Agent { mut_state.metrics.errors.unparsed_responses += 1; mut_state.add_unparsed_response_to_history( response, - "I could not parse any valid actions from your response, please correct it according to the instructions.".to_string(), + "I could not parse any valid tool calls from your response, please correct it according to the instructions.".to_string(), ); self.on_event(Event::new(EventType::TextResponse(response.to_string()))) .unwrap(); @@ -349,7 +349,7 @@ impl Agent { self.state.lock().await.metrics.valid_responses += 1; } - async fn on_invalid_action(&self, tool_call: ToolCall, error: Option) { + async fn on_invalid_tool_call(&self, tool_call: ToolCall, error: Option) { if self.config.cot_tags.contains(&tool_call.tool_name) { self.on_event(Event::new(EventType::Thinking(tool_call.argument.unwrap()))) .unwrap(); @@ -357,49 +357,49 @@ impl Agent { } let mut mut_state = self.state.lock().await; - mut_state.metrics.errors.unknown_actions += 1; - // tell the model that the action name is wrong + mut_state.metrics.errors.unknown_tool_calls += 1; + // tell the model that the tool name is wrong let name = tool_call.tool_name.clone(); mut_state.add_error_to_history( tool_call.clone(), error .clone() - .unwrap_or(format!("'{name}' is not a valid action name")), + .unwrap_or(format!("'{name}' is not a valid tool name")), ); - self.on_event(Event::new(EventType::InvalidAction { tool_call, error })) + self.on_event(Event::new(EventType::InvalidToolCall { tool_call, error })) .unwrap(); } - async fn on_valid_action(&self, tool_call: &ToolCall) { - self.state.lock().await.metrics.valid_actions += 1; + async fn on_valid_tool_call(&self, tool_call: &ToolCall) { + self.state.lock().await.metrics.valid_tool_calls += 1; let tool_call = tool_call.clone(); - self.on_event(Event::new(EventType::ActionExecuting { tool_call })) + self.on_event(Event::new(EventType::BeforeToolCall { tool_call })) .unwrap(); } - async fn on_timed_out_action(&self, tool_call: ToolCall, start: &std::time::Instant) { + async fn on_tool_time_out(&self, tool_call: ToolCall, start: &std::time::Instant) { let mut mut_state = self.state.lock().await; - mut_state.metrics.errors.timedout_actions += 1; + mut_state.metrics.errors.timedout_tool_calls += 1; // tell the model about the timeout - mut_state.add_error_to_history(tool_call.clone(), "action timed out".to_string()); + mut_state.add_error_to_history(tool_call.clone(), "tool call timed out".to_string()); self.events_chan - .send(Event::new(EventType::ActionTimeout { + .send(Event::new(EventType::ToolCallTimeout { tool_call, elapsed: start.elapsed(), })) .unwrap(); } - async fn on_executed_action( + async fn on_executed_tool_call( &self, - action: &Box, + tool: &Box, tool_call: ToolCall, - ret: Result>, + ret: Result>, start: &std::time::Instant, ) { let mut mut_state = self.state.lock().await; @@ -407,26 +407,26 @@ impl Agent { let mut result = None; if let Err(err) = ret { - mut_state.metrics.errors.errored_actions += 1; + mut_state.metrics.errors.errored_tool_calls += 1; // tell the model about the error mut_state.add_error_to_history(tool_call.clone(), err.to_string()); error = Some(err.to_string()); } else { let ret = ret.unwrap(); - mut_state.metrics.success_actions += 1; + mut_state.metrics.success_tool_calls += 1; // tell the model about the output mut_state.add_success_to_history(tool_call.clone(), ret.clone()); result = ret; } - self.on_event(Event::new(EventType::ActionExecuted { + self.on_event(Event::new(EventType::AfterToolCall { tool_call, result, error, elapsed: start.elapsed(), - complete_task: action.complete_task(), + complete_task: tool.complete_task(), })) .unwrap(); } @@ -528,24 +528,24 @@ impl Agent { // for each parsed call for mut call in tool_calls { - // lookup action - let action = self.state.lock().await.get_action(&call.tool_name); - if action.is_none() { - self.on_invalid_action(call.clone(), None).await; + // lookup tool + let tool = self.state.lock().await.get_tool_by_name(&call.tool_name); + if tool.is_none() { + self.on_invalid_tool_call(call.clone(), None).await; } else { // validate prerequisites - let action = action.unwrap(); - if let Err(err) = self.validate(&mut call, &action) { - self.on_invalid_action(call.clone(), Some(err.to_string())) + let tool = tool.unwrap(); + if let Err(err) = self.validate(&mut call, &tool) { + self.on_invalid_tool_call(call.clone(), Some(err.to_string())) .await; } else { - self.on_valid_action(&call).await; + self.on_valid_tool_call(&call).await; // determine if we have a timeout - let timeout = if let Some(action_tm) = action.timeout().as_ref() { - *action_tm - } else if let Some(task_tm) = self.task_specs.timeout.as_ref() { - *task_tm + let timeout = if let Some(tool_call_timeout) = tool.timeout().as_ref() { + *tool_call_timeout + } else if let Some(task_timeout) = self.task_specs.timeout.as_ref() { + *task_timeout } else { // one month by default :D Duration::from_secs(60 * 60 * 24 * 30) @@ -553,7 +553,7 @@ impl Agent { let mut execute = true; - if action.requires_user_confirmation() { + if tool.requires_user_confirmation() { log::warn!("user confirmation required"); let start = std::time::Instant::now(); @@ -567,9 +567,9 @@ impl Agent { } if inp == "n" { - log::warn!("action rejected by user"); - self.on_executed_action( - &action, + log::warn!("tool call rejected by user"); + self.on_executed_tool_call( + &tool, call.clone(), Err(anyhow!("rejected by user".to_owned())), &start, @@ -585,7 +585,7 @@ impl Agent { let start = std::time::Instant::now(); let ret = tokio::time::timeout( timeout, - action.run( + tool.run( self.state.clone(), call.named_arguments.to_owned(), call.argument.to_owned(), @@ -594,13 +594,13 @@ impl Agent { .await; if ret.is_err() { - self.on_timed_out_action(call, &start).await; + self.on_tool_time_out(call, &start).await; } else { - self.on_executed_action(&action, call, ret.unwrap(), &start) + self.on_executed_tool_call(&tool, call, ret.unwrap(), &start) .await; } - if action.complete_task() { + if tool.complete_task() { log::debug!("! task complete"); self.state.lock().await.on_complete(false, None)?; } diff --git a/src/agent/namespaces/filesystem/mod.rs b/src/agent/namespaces/filesystem/mod.rs index 4b0f017..19b0138 100644 --- a/src/agent/namespaces/filesystem/mod.rs +++ b/src/agent/namespaces/filesystem/mod.rs @@ -10,7 +10,7 @@ use libc::{S_IRGRP, S_IROTH, S_IRUSR, S_IWGRP, S_IWOTH, S_IWUSR, S_IXGRP, S_IXOT use anyhow::Result; use serde::Serialize; -use super::{Action, ActionOutput, Namespace}; +use super::{Tool, ToolOutput, Namespace}; use crate::agent::state::SharedState; use crate::agent::task::variables::get_variable; @@ -62,7 +62,7 @@ fn triplet(mode: u32, read: u32, write: u32, execute: u32) -> String { struct ReadFolder {} #[async_trait] -impl Action for ReadFolder { +impl Tool for ReadFolder { fn name(&self) -> &str { "list_folder_contents" } @@ -80,7 +80,7 @@ impl Action for ReadFolder { _: SharedState, _: Option>, payload: Option, - ) -> Result> { + ) -> Result> { // adapted from https://gist.github.com/mre/91ebb841c34df69671bd117ead621a8b let folder = payload.unwrap(); let ret = fs::read_dir(&folder); @@ -124,7 +124,7 @@ impl Action for ReadFolder { struct ReadFile {} #[async_trait] -impl Action for ReadFile { +impl Tool for ReadFile { fn name(&self) -> &str { "read_file" } @@ -142,7 +142,7 @@ impl Action for ReadFile { _: SharedState, _: Option>, payload: Option, - ) -> Result> { + ) -> Result> { let filepath = payload.unwrap(); let ret = std::fs::read_to_string(&filepath); if let Ok(contents) = ret { @@ -165,7 +165,7 @@ struct InvalidJSON { struct AppendToFile {} #[async_trait] -impl Action for AppendToFile { +impl Tool for AppendToFile { fn name(&self) -> &str { "append_to_file" } @@ -188,7 +188,7 @@ impl Action for AppendToFile { _: SharedState, _: Option>, payload: Option, - ) -> Result> { + ) -> Result> { let payload = payload.unwrap(); let filepath = match get_variable("filesystem.append_to_file.target") { diff --git a/src/agent/namespaces/goal/mod.rs b/src/agent/namespaces/goal/mod.rs index 4279e85..143e831 100644 --- a/src/agent/namespaces/goal/mod.rs +++ b/src/agent/namespaces/goal/mod.rs @@ -3,14 +3,14 @@ use async_trait::async_trait; use std::collections::HashMap; -use super::{Action, ActionOutput, Namespace, StorageDescriptor}; +use super::{Tool, ToolOutput, Namespace, StorageDescriptor}; use crate::agent::state::SharedState; #[derive(Debug, Default, Clone)] struct UpdateGoal {} #[async_trait] -impl Action for UpdateGoal { +impl Tool for UpdateGoal { fn name(&self) -> &str { "update_goal" } @@ -28,7 +28,7 @@ impl Action for UpdateGoal { state: SharedState, _: Option>, payload: Option, - ) -> Result> { + ) -> Result> { state .lock() .await diff --git a/src/agent/namespaces/http/mod.rs b/src/agent/namespaces/http/mod.rs index 947a210..6e4de80 100644 --- a/src/agent/namespaces/http/mod.rs +++ b/src/agent/namespaces/http/mod.rs @@ -15,7 +15,7 @@ use url::Url; use crate::agent::state::SharedState; -use super::{Action, ActionOutput, Namespace, StorageDescriptor}; +use super::{Tool, ToolOutput, Namespace, StorageDescriptor}; const DEFAULT_HTTP_SCHEMA: &str = "https"; @@ -45,7 +45,7 @@ lazy_static! { struct ClearHeaders {} #[async_trait] -impl Action for ClearHeaders { +impl Tool for ClearHeaders { fn name(&self) -> &str { "http_clear_headers" } @@ -59,7 +59,7 @@ impl Action for ClearHeaders { state: SharedState, _: Option>, _: Option, - ) -> Result> { + ) -> Result> { state.lock().await.get_storage_mut("http-headers")?.clear(); Ok(Some("http headers cleared".into())) } @@ -69,7 +69,7 @@ impl Action for ClearHeaders { struct SetHeader {} #[async_trait] -impl Action for SetHeader { +impl Tool for SetHeader { fn name(&self) -> &str { "http_set_header" } @@ -95,7 +95,7 @@ impl Action for SetHeader { state: SharedState, attrs: Option>, payload: Option, - ) -> Result> { + ) -> Result> { let attrs = attrs.unwrap(); let key = attrs.get("name").unwrap(); let data = payload.unwrap(); @@ -201,7 +201,7 @@ impl Request { } #[async_trait] -impl Action for Request { +impl Tool for Request { fn name(&self) -> &str { "http_request" } @@ -235,7 +235,7 @@ impl Action for Request { state: SharedState, attrs: Option>, payload: Option, - ) -> Result> { + ) -> Result> { // create a parsed Url from the attributes, payload and HTTP_TARGET variable let attrs = attrs.unwrap(); let method = attrs.get("method").unwrap(); diff --git a/src/agent/namespaces/memory/mod.rs b/src/agent/namespaces/memory/mod.rs index 65563f4..f26b9e3 100644 --- a/src/agent/namespaces/memory/mod.rs +++ b/src/agent/namespaces/memory/mod.rs @@ -3,14 +3,14 @@ use std::collections::HashMap; use anyhow::Result; use async_trait::async_trait; -use super::{Action, ActionOutput, Namespace, StorageDescriptor}; +use super::{Namespace, StorageDescriptor, Tool, ToolOutput}; use crate::agent::state::SharedState; #[derive(Debug, Default, Clone)] struct SaveMemory {} #[async_trait] -impl Action for SaveMemory { +impl Tool for SaveMemory { fn name(&self) -> &str { "save_memory" } @@ -36,7 +36,7 @@ impl Action for SaveMemory { state: SharedState, attributes: Option>, payload: Option, - ) -> Result> { + ) -> Result> { let attrs = attributes.unwrap(); let key = attrs.get("key").unwrap(); @@ -54,7 +54,7 @@ impl Action for SaveMemory { struct DeleteMemory {} #[async_trait] -impl Action for DeleteMemory { +impl Tool for DeleteMemory { fn name(&self) -> &str { "delete_memory" } @@ -76,7 +76,7 @@ impl Action for DeleteMemory { state: SharedState, attributes: Option>, _: Option, - ) -> Result> { + ) -> Result> { let attrs = attributes.unwrap(); let key = attrs.get("key").unwrap(); if state @@ -98,7 +98,7 @@ impl Action for DeleteMemory { struct RecallMemory {} #[async_trait] -impl Action for RecallMemory { +impl Tool for RecallMemory { fn name(&self) -> &str { "recall_memory" } diff --git a/src/agent/namespaces/mod.rs b/src/agent/namespaces/mod.rs index 1391f63..70a6348 100644 --- a/src/agent/namespaces/mod.rs +++ b/src/agent/namespaces/mod.rs @@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize}; use super::state::{storage::StorageType, SharedState}; -// TODO: add more namespaces of actions: take screenshot (multimodal), move mouse, ui interactions, etc +// TODO: add more namespaces of tools: take screenshot (multimodal), move mouse, ui interactions, etc pub mod filesystem; pub mod goal; @@ -113,7 +113,7 @@ impl StorageDescriptor { pub struct Namespace { pub name: String, pub description: String, - pub actions: Vec>, + pub tools: Vec>, pub storages: Option>, pub default: bool, } @@ -122,14 +122,14 @@ impl Namespace { pub fn new_non_default( name: String, description: String, - actions: Vec>, + tools: Vec>, storages: Option>, ) -> Self { let default = false; Self { name, description, - actions, + tools, storages, default, } @@ -138,14 +138,14 @@ impl Namespace { pub fn new_default( name: String, description: String, - actions: Vec>, + tools: Vec>, storages: Option>, ) -> Self { let default = true; Self { name, description, - actions, + tools, storages, default, } @@ -153,37 +153,37 @@ impl Namespace { } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub enum ActionOutput { +pub enum ToolOutput { Text(String), Image { data: String, mime_type: String }, } -impl ActionOutput { +impl ToolOutput { pub fn text>(text: S) -> Self { - ActionOutput::Text(text.into()) + ToolOutput::Text(text.into()) } pub fn image(data: String, mime_type: String) -> Self { - ActionOutput::Image { data, mime_type } + ToolOutput::Image { data, mime_type } } } -impl From for ActionOutput { +impl From for ToolOutput { fn from(text: String) -> Self { - ActionOutput::text(text) + ToolOutput::text(text) } } -impl From<&str> for ActionOutput { +impl From<&str> for ToolOutput { fn from(text: &str) -> Self { - ActionOutput::text(text) + ToolOutput::text(text) } } -impl Display for ActionOutput { +impl Display for ToolOutput { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - ActionOutput::Text(text) => write!(f, "{}", text), - ActionOutput::Image { data, mime_type } => { + ToolOutput::Text(text) => write!(f, "{}", text), + ToolOutput::Image { data, mime_type } => { write!(f, "image: {} ({})", data, mime_type) } } @@ -191,7 +191,7 @@ impl Display for ActionOutput { } #[async_trait] -pub trait Action: std::fmt::Debug + Sync + Send + ActionClone { +pub trait Tool: std::fmt::Debug + Sync + Send + ToolClone { fn name(&self) -> &str; fn description(&self) -> &str; @@ -201,7 +201,7 @@ pub trait Action: std::fmt::Debug + Sync + Send + ActionClone { state: SharedState, attributes: Option>, payload: Option, - ) -> Result>; + ) -> Result>; // optional execution timeout fn timeout(&self) -> Option { @@ -218,12 +218,12 @@ pub trait Action: std::fmt::Debug + Sync + Send + ActionClone { None } - // optional variables used by this action + // optional variables used by this tool fn required_variables(&self) -> Option> { None } - // optional method to indicate if this action requires user confirmation before execution + // optional method to indicate if this tool requires user confirmation before execution fn requires_user_confirmation(&self) -> bool { false } @@ -235,29 +235,29 @@ pub trait Action: std::fmt::Debug + Sync + Send + ActionClone { } // https://stackoverflow.com/questions/30353462/how-to-clone-a-struct-storing-a-boxed-trait-object -// Splitting ActionClone into its own trait allows us to provide a blanket +// Splitting ToolClone into its own trait allows us to provide a blanket // implementation for all compatible types, without having to implement the -// rest of Action. In this case, we implement it for all types that have +// rest of Tool. In this case, we implement it for all types that have // 'static lifetime (*i.e.* they don't contain non-'static pointers), and -// implement both Action and Clone. Don't ask me how the compiler resolves -// implementing ActionClone for dyn Action when Action requires ActionClone; +// implement both Tool and Clone. Don't ask me how the compiler resolves +// implementing ToolClone for dyn Tool when Tool requires ToolClone; // I have *no* idea why this works. -pub trait ActionClone { - fn clone_box(&self) -> Box; +pub trait ToolClone { + fn clone_box(&self) -> Box; } -impl ActionClone for T +impl ToolClone for T where - T: 'static + Action + Clone, + T: 'static + Tool + Clone, { - fn clone_box(&self) -> Box { + fn clone_box(&self) -> Box { Box::new(self.clone()) } } // We can now implement Clone manually by forwarding to clone_box. -impl Clone for Box { - fn clone(&self) -> Box { +impl Clone for Box { + fn clone(&self) -> Box { self.clone_box() } } diff --git a/src/agent/namespaces/planning/mod.rs b/src/agent/namespaces/planning/mod.rs index 39a8ad1..757d81e 100644 --- a/src/agent/namespaces/planning/mod.rs +++ b/src/agent/namespaces/planning/mod.rs @@ -3,14 +3,14 @@ use std::collections::HashMap; use anyhow::Result; use async_trait::async_trait; -use super::{Action, ActionOutput, Namespace, StorageDescriptor}; +use super::{Tool, ToolOutput, Namespace, StorageDescriptor}; use crate::agent::state::SharedState; #[derive(Debug, Default, Clone)] struct AddStep {} #[async_trait] -impl Action for AddStep { +impl Tool for AddStep { fn name(&self) -> &str { "add_plan_step" } @@ -28,7 +28,7 @@ impl Action for AddStep { state: SharedState, _: Option>, payload: Option, - ) -> Result> { + ) -> Result> { state .lock() .await @@ -42,7 +42,7 @@ impl Action for AddStep { struct DeleteStep {} #[async_trait] -impl Action for DeleteStep { +impl Tool for DeleteStep { fn name(&self) -> &str { "delete_plan_step" } @@ -60,7 +60,7 @@ impl Action for DeleteStep { state: SharedState, _: Option>, payload: Option, - ) -> Result> { + ) -> Result> { state .lock() .await @@ -74,7 +74,7 @@ impl Action for DeleteStep { struct SetComplete {} #[async_trait] -impl Action for SetComplete { +impl Tool for SetComplete { fn name(&self) -> &str { "set_step_completed" } @@ -92,7 +92,7 @@ impl Action for SetComplete { state: SharedState, _: Option>, payload: Option, - ) -> Result> { + ) -> Result> { let pos = payload.unwrap().parse::()?; if state .lock() @@ -112,7 +112,7 @@ impl Action for SetComplete { struct SetIncomplete {} #[async_trait] -impl Action for SetIncomplete { +impl Tool for SetIncomplete { fn name(&self) -> &str { "set_step_incomplete" } @@ -130,7 +130,7 @@ impl Action for SetIncomplete { state: SharedState, _: Option>, payload: Option, - ) -> Result> { + ) -> Result> { let pos = payload.unwrap().parse::()?; if state .lock() @@ -150,7 +150,7 @@ impl Action for SetIncomplete { struct Clear {} #[async_trait] -impl Action for Clear { +impl Tool for Clear { fn name(&self) -> &str { "clear_plan" } @@ -164,7 +164,7 @@ impl Action for Clear { state: SharedState, _: Option>, _: Option, - ) -> Result> { + ) -> Result> { state.lock().await.get_storage_mut("plan")?.clear(); Ok(Some("plan cleared".into())) } diff --git a/src/agent/namespaces/rag/mod.rs b/src/agent/namespaces/rag/mod.rs index 932570d..0f596fc 100644 --- a/src/agent/namespaces/rag/mod.rs +++ b/src/agent/namespaces/rag/mod.rs @@ -5,13 +5,13 @@ use async_trait::async_trait; use crate::agent::state::SharedState; -use super::{Action, ActionOutput, Namespace}; +use super::{Tool, ToolOutput, Namespace}; #[derive(Debug, Default, Clone)] struct Search {} #[async_trait] -impl Action for Search { +impl Tool for Search { fn name(&self) -> &str { "search" } @@ -29,7 +29,7 @@ impl Action for Search { state: SharedState, _: Option>, payload: Option, - ) -> Result> { + ) -> Result> { let query = payload.unwrap(); let start = Instant::now(); // TODO: make top_k configurable? diff --git a/src/agent/namespaces/shell/mod.rs b/src/agent/namespaces/shell/mod.rs index c58ea98..531f20f 100644 --- a/src/agent/namespaces/shell/mod.rs +++ b/src/agent/namespaces/shell/mod.rs @@ -6,13 +6,13 @@ use tokio::process::Command; use crate::agent::state::SharedState; -use super::{Action, ActionOutput, Namespace}; +use super::{Tool, ToolOutput, Namespace}; #[derive(Debug, Default, Clone)] struct Shell {} #[async_trait] -impl Action for Shell { +impl Tool for Shell { fn name(&self) -> &str { "shell" } @@ -35,7 +35,7 @@ impl Action for Shell { _: SharedState, _: Option>, payload: Option, - ) -> Result> { + ) -> Result> { let command = payload.unwrap(); log::warn!("executing command: {}", &command); diff --git a/src/agent/namespaces/task/mod.rs b/src/agent/namespaces/task/mod.rs index d8283f0..53f90f5 100644 --- a/src/agent/namespaces/task/mod.rs +++ b/src/agent/namespaces/task/mod.rs @@ -3,14 +3,14 @@ use std::collections::HashMap; use anyhow::Result; use async_trait::async_trait; -use super::{Action, ActionOutput, Namespace}; +use super::{Tool, ToolOutput, Namespace}; use crate::agent::state::SharedState; #[derive(Debug, Default, Clone)] struct Complete {} #[async_trait] -impl Action for Complete { +impl Tool for Complete { fn name(&self) -> &str { "task_complete" } @@ -28,7 +28,7 @@ impl Action for Complete { state: SharedState, _: Option>, payload: Option, - ) -> Result> { + ) -> Result> { state.lock().await.on_complete(false, payload)?; Ok(None) } @@ -38,7 +38,7 @@ impl Action for Complete { struct Impossible {} #[async_trait] -impl Action for Impossible { +impl Tool for Impossible { fn name(&self) -> &str { "task_impossible" } @@ -56,7 +56,7 @@ impl Action for Impossible { state: SharedState, _: Option>, payload: Option, - ) -> Result> { + ) -> Result> { state.lock().await.on_complete(true, payload)?; Ok(None) } diff --git a/src/agent/namespaces/time/mod.rs b/src/agent/namespaces/time/mod.rs index 18cdab8..96e5188 100644 --- a/src/agent/namespaces/time/mod.rs +++ b/src/agent/namespaces/time/mod.rs @@ -3,14 +3,14 @@ use async_trait::async_trait; use std::{collections::HashMap, time::Duration}; -use super::{Action, ActionOutput, Namespace, StorageDescriptor}; +use super::{Tool, ToolOutput, Namespace, StorageDescriptor}; use crate::agent::state::SharedState; #[derive(Debug, Default, Clone)] struct Wait {} #[async_trait] -impl Action for Wait { +impl Tool for Wait { fn name(&self) -> &str { "wait" } @@ -28,7 +28,7 @@ impl Action for Wait { _: SharedState, _: Option>, payload: Option, - ) -> Result> { + ) -> Result> { let secs = payload.unwrap().parse::()?; log::info!("💤 sleeping for {secs} seconds ..."); diff --git a/src/agent/serialization/actions.prompt b/src/agent/serialization/actions.prompt deleted file mode 100644 index 64d48e0..0000000 --- a/src/agent/serialization/actions.prompt +++ /dev/null @@ -1,3 +0,0 @@ -# Actions - -You can take any of the following actions in your response, the user will respond with the output or error of the action. Use the formats below. diff --git a/src/agent/serialization/mod.rs b/src/agent/serialization/mod.rs index 0a60e21..52f5732 100644 --- a/src/agent/serialization/mod.rs +++ b/src/agent/serialization/mod.rs @@ -2,7 +2,7 @@ use anyhow::Result; use tera::Tera; use super::{namespaces::NAMESPACES, state::State}; -use crate::agent::{namespaces::Action, state::storage::Storage, ToolCall}; +use crate::agent::{namespaces::Tool, state::storage::Storage, ToolCall}; mod xml; @@ -15,7 +15,7 @@ pub enum Strategy { } impl Strategy { - pub fn available_actions() -> String { + pub fn available_tools() -> String { let default_serializer = Self::default(); let mut md = "".to_string(); @@ -26,11 +26,11 @@ impl Strategy { if !group.description.is_empty() { md += &format!("{}\n\n", group.description); } - for action in &group.actions { + for tool in &group.tools { md += &format!( "* {} {}\n", - action.description(), - default_serializer.serialize_action(action) + tool.description(), + default_serializer.serialize_tool(tool) ); } } @@ -50,9 +50,9 @@ impl Strategy { } } - pub fn serialize_action(&self, action: &Box) -> String { + pub fn serialize_tool(&self, tool: &Box) -> String { match self { - Strategy::XML => xml::serialize::action(action), + Strategy::XML => xml::serialize::tool(tool), } } @@ -62,7 +62,7 @@ impl Strategy { } } - fn actions_for_state(&self, state: &State) -> Result { + fn tools_for_state(&self, state: &State) -> Result { let mut md = "".to_string(); for group in state.get_namespaces() { @@ -70,12 +70,8 @@ impl Strategy { if !group.description.is_empty() { md += &format!("{}\n\n", group.description); } - for action in &group.actions { - md += &format!( - "{} {}\n\n", - action.description(), - self.serialize_action(action) - ); + for tool in &group.tools { + md += &format!("{} {}\n\n", tool.description(), self.serialize_tool(tool)); } } @@ -97,15 +93,15 @@ impl Strategy { let storages = storages.join("\n\n"); let guidance = task.guidance()?; - let available_actions = if state.use_native_tools_format { - // model supports tool calls, no need to add actions to the system prompt + let available_tools = if state.use_native_tools_format { + // model supports tool calls, no need to add tools to the system prompt "".to_string() } else { - // model does not support tool calls, we need to provide the actions in its system prompt - let mut raw = include_str!("actions.prompt").to_owned(); + // model does not support tool calls, we need to provide the tools in its system prompt + let mut raw = include_str!("tools.prompt").to_owned(); raw.push('\n'); - raw.push_str(&self.actions_for_state(state)?); + raw.push_str(&self.tools_for_state(state)?); raw }; @@ -125,7 +121,7 @@ impl Strategy { context.insert("system_prompt", &system_prompt); context.insert("storages", &storages); context.insert("iterations", &iterations); - context.insert("available_actions", &available_actions); + context.insert("available_tools", &available_tools); context.insert("guidance", &guidance); Tera::one_off(include_str!("system.prompt"), &context, false) diff --git a/src/agent/serialization/system.prompt b/src/agent/serialization/system.prompt index c5c4769..52cbdfb 100644 --- a/src/agent/serialization/system.prompt +++ b/src/agent/serialization/system.prompt @@ -1,5 +1,5 @@ {{ system_prompt }} -{% if storages or iterations or available_actions %} +{% if storages or iterations or available_tools %} # Context @@ -7,7 +7,7 @@ {{ iterations }} -{{ available_actions }} +{{ available_tools }} --- {% endif %}{% if guidance %} @@ -15,4 +15,4 @@ {% for rule in guidance %} - {{ rule }}{% endfor %} {% endif %} -Output a new action in your response. Prior action results are displayed in the chat history. \ No newline at end of file +Call a new tool in your response. Prior tool results are displayed in the chat history. \ No newline at end of file diff --git a/src/agent/serialization/tools.prompt b/src/agent/serialization/tools.prompt new file mode 100644 index 0000000..cd0b088 --- /dev/null +++ b/src/agent/serialization/tools.prompt @@ -0,0 +1,3 @@ +# Tools + +You can take any of the following tools in your response, the user will respond with the output or error of the tool. Use the formats below. diff --git a/src/agent/serialization/xml/parsing.rs b/src/agent/serialization/xml/parsing.rs index 81ba0f0..4fb82ec 100644 --- a/src/agent/serialization/xml/parsing.rs +++ b/src/agent/serialization/xml/parsing.rs @@ -36,7 +36,7 @@ fn build_tool_call( )); } - let action = name.to_string(); + let tool_name = name.to_string(); let attributes = if !attrs.is_empty() { let mut map = HashMap::new(); for attr in attrs { @@ -49,7 +49,7 @@ fn build_tool_call( }; let payload = payload.as_ref().map(|data| data.to_owned()); - Ok(ToolCall::new(action, attributes, payload)) + Ok(ToolCall::new(tool_name, attributes, payload)) } fn preprocess_block(ptr: &str) -> String { diff --git a/src/agent/serialization/xml/serialize.rs b/src/agent/serialization/xml/serialize.rs index e12506e..4f98ecf 100644 --- a/src/agent/serialization/xml/serialize.rs +++ b/src/agent/serialization/xml/serialize.rs @@ -1,5 +1,5 @@ use crate::agent::{ - namespaces::Action, + namespaces::Tool, state::storage::{Storage, StorageType, CURRENT_TAG, PREVIOUS_TAG}, ToolCall, }; @@ -25,18 +25,18 @@ pub fn tool_call(tool_call: &ToolCall) -> String { } #[allow(clippy::borrowed_box)] -pub fn action(action: &Box) -> String { - let mut xml = format!("`<{}", action.name()); +pub fn tool(tool: &Box) -> String { + let mut xml = format!("`<{}", tool.name()); - if let Some(attrs) = action.example_attributes() { + if let Some(attrs) = tool.example_attributes() { for (name, example_value) in &attrs { xml += &format!(" {}=\"{}\"", name, example_value); } } - if let Some(payload) = action.example_payload() { + if let Some(payload) = tool.example_payload() { // TODO: escape payload? - xml += &format!(">{}`", payload, action.name()); + xml += &format!(">{}`", payload, tool.name()); } else { xml += "/>`"; } diff --git a/src/agent/state/history.rs b/src/agent/state/history.rs index ae58e80..4be429f 100644 --- a/src/agent/state/history.rs +++ b/src/agent/state/history.rs @@ -1,6 +1,6 @@ use anyhow::Result; -use crate::agent::{generator::Message, namespaces::ActionOutput, serialization, ToolCall}; +use crate::agent::{generator::Message, namespaces::ToolOutput, serialization, ToolCall}; #[derive(Debug, Clone, Default)] pub struct Execution { @@ -9,7 +9,7 @@ pub struct Execution { // parsed tool call tool_call: Option, - result: Option, + result: Option, error: Option, } @@ -41,7 +41,7 @@ impl Execution { } } - pub fn with_result(tool_call: ToolCall, result: Option) -> Self { + pub fn with_result(tool_call: ToolCall, result: Option) -> Self { Self { tool_call: Some(tool_call), response: None, @@ -67,11 +67,11 @@ impl Execution { messages.push(Message::Feedback { result: if let Some(err) = &self.error { - ActionOutput::text(format!("ERROR: {err}")) + ToolOutput::text(format!("ERROR: {err}")) } else if let Some(out) = &self.result { out.clone() } else { - ActionOutput::text("") + ToolOutput::text("") }, tool_call: self.tool_call.clone(), }); diff --git a/src/agent/state/metrics.rs b/src/agent/state/metrics.rs index 6cdc986..9ff44b7 100644 --- a/src/agent/state/metrics.rs +++ b/src/agent/state/metrics.rs @@ -6,10 +6,10 @@ use serde::{Deserialize, Serialize}; pub struct ErrorMetrics { pub empty_responses: usize, pub unparsed_responses: usize, - pub unknown_actions: usize, - pub invalid_actions: usize, - pub errored_actions: usize, - pub timedout_actions: usize, + pub unknown_tool_calls: usize, + pub invalid_tool_calls: usize, + pub errored_tool_calls: usize, + pub timedout_tool_calls: usize, } impl ErrorMetrics { @@ -17,8 +17,8 @@ impl ErrorMetrics { self.empty_responses > 0 || self.unparsed_responses > 0 } - fn has_action_errors(&self) -> bool { - self.unknown_actions > 0 || self.invalid_actions > 0 || self.errored_actions > 0 + fn has_tool_errors(&self) -> bool { + self.unknown_tool_calls > 0 || self.invalid_tool_calls > 0 || self.errored_tool_calls > 0 } } @@ -35,8 +35,8 @@ pub struct Metrics { pub max_steps: usize, pub current_step: usize, pub valid_responses: usize, - pub valid_actions: usize, - pub success_actions: usize, + pub valid_tool_calls: usize, + pub success_tool_calls: usize, pub errors: ErrorMetrics, pub usage: Usage, } @@ -60,18 +60,18 @@ impl Display for Metrics { write!(f, "responses:{} ", self.valid_responses)?; } - if self.errors.has_action_errors() { + if self.errors.has_tool_errors() { write!( f, - "actions(valid:{} ok:{} errored:{} unknown:{} invalid:{}) ", - self.valid_actions, - self.success_actions, - self.errors.errored_actions, - self.errors.unknown_actions, - self.errors.invalid_actions + "tool_calls(valid:{} ok:{} errored:{} unknown:{} invalid:{}) ", + self.valid_tool_calls, + self.success_tool_calls, + self.errors.errored_tool_calls, + self.errors.unknown_tool_calls, + self.errors.invalid_tool_calls )?; - } else if self.valid_actions > 0 { - write!(f, "actions:{} ", self.valid_actions,)?; + } else if self.valid_tool_calls > 0 { + write!(f, "tool_calls:{} ", self.valid_tool_calls,)?; } if self.usage.last_input_tokens > 0 { diff --git a/src/agent/state/mod.rs b/src/agent/state/mod.rs index 9552f35..5d890ab 100644 --- a/src/agent/state/mod.rs +++ b/src/agent/state/mod.rs @@ -8,7 +8,7 @@ use crate::agent::task::variables::parse_variable_expr; use super::{ events::{Event, EventType}, generator::Message, - namespaces::{self, ActionOutput, Namespace}, + namespaces::{self, Namespace, ToolOutput}, serialization, task::Task, ToolCall, @@ -27,9 +27,9 @@ pub struct State { variables: HashMap, // model memories, goals and other storages storages: HashMap, - // available actions and execution history + // available tools namespaces: Vec, - // list of executed actions + // chat history including tool calls and feedback history: History, // optional rag engine rag: Option, @@ -94,10 +94,10 @@ impl State { } for namespace in &namespaces { - for action in &namespace.actions { - // check if the action requires some variable - if let Some(required_vars) = action.required_variables() { - log::debug!("action {} requires {:?}", action.name(), &required_vars); + for tool in &namespace.tools { + // check if the tool requires some variable + if let Some(required_vars) = tool.required_variables() { + log::debug!("tool {} requires {:?}", tool.name(), &required_vars); for var_name in required_vars { let var_expr = format!("${var_name}"); let (var_name, var_value) = parse_variable_expr(&var_expr)?; @@ -121,7 +121,7 @@ impl State { None }; - // add task defined actions + // add task defined tools namespaces.append(&mut task.get_functions()); // if any namespace requires a specific storage, create it @@ -248,7 +248,7 @@ impl State { &self.namespaces } - pub fn add_success_to_history(&mut self, tool_call: ToolCall, result: Option) { + pub fn add_success_to_history(&mut self, tool_call: ToolCall, result: Option) { self.history.push(Execution::with_result(tool_call, result)); } @@ -265,11 +265,11 @@ impl State { self.history.push(Execution::with_feedback(feedback)); } - pub fn get_action(&self, name: &str) -> Option> { + pub fn get_tool_by_name(&self, name: &str) -> Option> { for group in &self.namespaces { - for action in &group.actions { - if name == action.name() { - return Some(action.clone()); + for tool in &group.tools { + if name == tool.name() { + return Some(tool.clone()); } } } diff --git a/src/agent/task/robopages.rs b/src/agent/task/robopages.rs index b2db645..a9fd624 100644 --- a/src/agent/task/robopages.rs +++ b/src/agent/task/robopages.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use serde::{Deserialize, Serialize}; -use super::tasklet::FunctionGroup; +use super::tasklet::ToolBox; #[derive(Debug, Serialize, Clone)] struct FunctionCall { @@ -50,7 +50,7 @@ impl Client { } } - pub async fn get_functions(&self) -> anyhow::Result> { + pub async fn get_functions(&self) -> anyhow::Result> { let api_url = format!( "http://{}{}?flavor=nerve", &self.server_address, &self.server_path @@ -67,7 +67,7 @@ impl Client { } response - .json::>() + .json::>() .await .map_err(|e| anyhow::anyhow!(e)) } diff --git a/src/agent/task/tasklet.rs b/src/agent/task/tasklet.rs index 1407db8..8ad2622 100644 --- a/src/agent/task/tasklet.rs +++ b/src/agent/task/tasklet.rs @@ -13,12 +13,12 @@ use serde_trim::*; use super::Evaluator; use super::{variables::interpolate_variables, Task}; -use crate::agent::namespaces::ActionOutput; +use crate::agent::namespaces::ToolOutput; use crate::agent::task::robopages; use crate::agent::task::variables::define_variable; use crate::agent::{get_user_input, namespaces}; use crate::agent::{ - namespaces::{Action, Namespace}, + namespaces::{Namespace, Tool}, state::SharedState, task::variables::{parse_pre_defined_values, parse_variable_expr}, }; @@ -30,7 +30,7 @@ fn default_max_shown_output() -> usize { } #[derive(Default, Deserialize, Serialize, Debug, Clone)] -pub struct TaskletAction { +pub struct TaskletTool { #[serde(skip_deserializing, skip_serializing)] working_directory: String, #[serde(skip_deserializing, skip_serializing)] @@ -60,10 +60,10 @@ pub struct TaskletAction { alias: Option, #[serde(skip_deserializing, skip_serializing)] - aliased_to: Option>, + aliased_to: Option>, } -impl TaskletAction { +impl TaskletTool { async fn run_via_robopages( &self, attributes: Option>, @@ -150,9 +150,9 @@ impl TaskletAction { return Err(anyhow!("no tool defined")); } - // TODO: each action should have its own variables ... ? + // TODO: each tool should have its own variables ... ? - // define action specific variables + // define tool specific variables if let Some(payload) = &payload { define_variable("PAYLOAD", payload); } @@ -261,7 +261,7 @@ impl TaskletAction { } #[async_trait] -impl Action for TaskletAction { +impl Tool for TaskletTool { fn name(&self) -> &str { &self.name } @@ -310,9 +310,9 @@ impl Action for TaskletAction { state: SharedState, attributes: Option>, payload: Option, - ) -> Result> { - let action_output = if let Some(aliased_to) = &self.aliased_to { - // run as alias of a builtin namespace.action, here we can unwrap as everything is validated earlier + ) -> Result> { + let tool_output = if let Some(aliased_to) = &self.aliased_to { + // run as alias of a builtin namespace.tool, here we can unwrap as everything is validated earlier aliased_to.run(state.clone(), attributes, payload).await? } else { let output = if self.robopages_server_address.is_some() { @@ -332,9 +332,9 @@ impl Action for TaskletAction { if let Some(output) = output { if let Some(mime_type) = &self.mime_type { - Some(ActionOutput::image(output, mime_type.to_string())) + Some(ToolOutput::image(output, mime_type.to_string())) } else { - Some(ActionOutput::text(output)) + Some(ToolOutput::text(output)) } } else { None @@ -346,7 +346,7 @@ impl Action for TaskletAction { state.lock().await.set_variable( store_to.to_string(), - if let Some(output) = &action_output { + if let Some(output) = &tool_output { output.to_string() } else { "".to_string() @@ -354,30 +354,33 @@ impl Action for TaskletAction { ); } - Ok(action_output) + Ok(tool_output) } } #[derive(Default, Deserialize, Serialize, Debug, Clone)] -pub struct FunctionGroup { +pub struct ToolBox { #[serde(deserialize_with = "string_trim")] pub name: String, pub description: Option, - pub actions: Vec, + + // for backwards compatibility + #[serde(alias = "actions")] + pub tools: Vec, } -impl FunctionGroup { +impl ToolBox { fn compile( &self, working_directory: &str, robopages_server_address: Option, ) -> Result { - let mut actions: Vec> = vec![]; - for tasklet_action in &self.actions { - let mut action = tasklet_action.clone(); - action.working_directory = working_directory.to_string(); - action.robopages_server_address = robopages_server_address.clone(); - actions.push(Box::new(action)); + let mut tools: Vec> = vec![]; + for tasklet_tool in &self.tools { + let mut tool = tasklet_tool.clone(); + tool.working_directory = working_directory.to_string(); + tool.robopages_server_address = robopages_server_address.clone(); + tools.push(Box::new(tool)); } Ok(Namespace::new_default( @@ -387,7 +390,7 @@ impl FunctionGroup { } else { "".to_string() }, - actions, + tools, None, // TODO: let tasklets declare custom storages? )) } @@ -406,11 +409,15 @@ pub struct Tasklet { timeout: Option, using: Option>, guidance: Option>, - functions: Option>, + + // for backwards compatibility + #[serde(alias = "functions")] + tool_box: Option>, + evaluator: Option, #[serde(skip_deserializing, skip_serializing)] - robopages: Vec, + robopages: Vec, #[serde(skip_deserializing, skip_serializing)] robopages_server_address: Option, } @@ -479,26 +486,26 @@ impl Tasklet { }; // check any tool definied as alias of a builtin namespace and perform some preprocessing and validation - if let Some(functions) = tasklet.functions.as_mut() { + if let Some(functions) = tasklet.tool_box.as_mut() { // for each group of functions for group in functions { - // for each action in the group - for action in &mut group.actions { - if let Some(defines) = &action.define { + // for each tool in the group + for tool in &mut group.tools { + if let Some(defines) = &tool.define { for (key, value) in defines { log::debug!( "defining variable {} = '{}' for {}.{}", key, value, group.name, - action.name + tool.name ); define_variable(key, value); } } - // if the action is a judge, validate the judge file - if let Some(judge) = &action.judge { + // if the tool is a judge, validate the judge file + if let Some(judge) = &tool.judge { let judge_path = if judge.starts_with('/') { PathBuf::from_str(judge)? } else { @@ -515,41 +522,39 @@ impl Tasklet { judge_path.display() )); } - action.judge_path = Some(judge_path); + tool.judge_path = Some(judge_path); } - // if the action has an alias perform some validation - if let Some(alias) = &action.alias { - if action.tool.is_some() { + // if the tool has an alias perform some validation + if let Some(alias) = &tool.alias { + if tool.tool.is_some() { return Err(anyhow!("can't define both tool and alias")); } - let (namespace_name, action_name) = alias + let (namespace_name, tool_name) = alias .split_once('.') - .ok_or_else(|| anyhow!("invalid alias format '{}', aliases must be provided as 'namespace.action'", alias))?; + .ok_or_else(|| anyhow!("invalid alias format '{}', aliases must be provided as 'namespace.tool'", alias))?; if let Some(get_namespace_fn) = namespaces::NAMESPACES.get(namespace_name) { let le_namespace = get_namespace_fn(); - let le_action = le_namespace - .actions - .iter() - .find(|a| a.name() == action_name); + let le_tool = + le_namespace.tools.iter().find(|a| a.name() == tool_name); - if let Some(le_action) = le_action { + if let Some(le_tool) = le_tool { log::debug!( "aliased {}.{} to {}.{}", group.name, - action.name, + tool.name, le_namespace.name, - le_action.name() + le_tool.name() ); - action.aliased_to = Some(le_action.clone()); + tool.aliased_to = Some(le_tool.clone()); } else { return Err(anyhow!( - "action '{}' not found in namespace '{}'", - action_name, + "tool '{}' not found in namespace '{}'", + tool_name, namespace_name )); } @@ -602,7 +607,7 @@ impl Tasklet { Ok(()) } - pub fn set_robopages(&mut self, server_address: &str, robopages: Vec) { + pub fn set_robopages(&mut self, server_address: &str, robopages: Vec) { let mut host_port = if server_address.contains("://") { server_address.split("://").last().unwrap().to_string() } else { @@ -667,7 +672,7 @@ impl Task for Tasklet { fn get_functions(&self) -> Vec { let mut groups = vec![]; - if let Some(custom_functions) = self.functions.as_ref() { + if let Some(custom_functions) = self.tool_box.as_ref() { for group in custom_functions { groups.push(group.compile(&self.folder, None).unwrap()); } diff --git a/src/cli/cli.rs b/src/cli/cli.rs index 78e2155..1f8e666 100644 --- a/src/cli/cli.rs +++ b/src/cli/cli.rs @@ -63,7 +63,7 @@ pub struct Args { /// Record every event of the session to a JSONL file. #[arg(long)] pub record_to: Option, - /// Print the documentation of the available action namespaces. + /// Print the documentation of the available tool namespaces. #[arg(long)] pub generate_doc: bool, } diff --git a/src/cli/setup.rs b/src/cli/setup.rs index f3af311..052f0ec 100644 --- a/src/cli/setup.rs +++ b/src/cli/setup.rs @@ -43,8 +43,8 @@ pub async fn setup_arguments() -> Result { } if args.generate_doc { - // generate action namespaces documentation and exit - println!("{}", agent::serialization::Strategy::available_actions()); + // generate tool namespaces documentation and exit + println!("{}", agent::serialization::Strategy::available_tools()); std::process::exit(0); } diff --git a/src/cli/ui/text.rs b/src/cli/ui/text.rs index 73c9fe0..dfbeb8a 100644 --- a/src/cli/ui/text.rs +++ b/src/cli/ui/text.rs @@ -5,14 +5,14 @@ use colored::Colorize; use crate::{ agent::{ events::{EventType, Receiver}, - namespaces::ActionOutput, + namespaces::ToolOutput, ToolCall, }, cli::Args, APP_NAME, APP_VERSION, }; -fn on_action_about_to_execute(tool_call: ToolCall) { +fn on_tool_call_about_to_execute(tool_call: ToolCall) { let mut view = String::new(); view.push_str("🧠 "); @@ -36,11 +36,11 @@ fn on_action_about_to_execute(tool_call: ToolCall) { log::info!("{} ...", view); } -fn on_action_executed( +fn on_tool_call_executed( judge_mode: bool, error: Option, tool_call: ToolCall, - result: Option, + result: Option, elapsed: Duration, complete_task: bool, ) { @@ -134,27 +134,27 @@ pub async fn consume_events(mut events_rx: Receiver, args: Args, is_workflow: bo EventType::TextResponse(response) => { log::info!("🧠 {}", response.trim().italic()); } - EventType::InvalidAction { tool_call, error } => { - log::warn!("invalid action {} : {:?}", &tool_call.tool_name, error); + EventType::InvalidToolCall { tool_call, error } => { + log::warn!("invalid tool call {} : {:?}", &tool_call.tool_name, error); } - EventType::ActionTimeout { tool_call, elapsed } => { + EventType::ToolCallTimeout { tool_call, elapsed } => { log::warn!( - "action '{}' timed out after {:?}", + "tool call '{}' timed out after {:?}", tool_call.tool_name, elapsed ); } - EventType::ActionExecuting { tool_call } => { - on_action_about_to_execute(tool_call); + EventType::BeforeToolCall { tool_call } => { + on_tool_call_about_to_execute(tool_call); } - EventType::ActionExecuted { + EventType::AfterToolCall { tool_call, error, result, elapsed, complete_task, } => { - on_action_executed( + on_tool_call_executed( args.judge_mode, error, tool_call,