From 0b9f71d071fd0d7358dcca0291a13f7186074019 Mon Sep 17 00:00:00 2001 From: "Nadheesh Jihan, nadheesh@wso2.com" Date: Fri, 26 Jan 2024 19:10:17 +0530 Subject: [PATCH 1/4] Improve docs --- ballerina/Ballerina.toml | 2 +- ballerina/Module.md | 247 ++++++++++++++++++++++-------------- ballerina/Package.md | 247 ++++++++++++++++++++++-------------- ballerina/agent.bal | 28 ++-- ballerina/function_call.bal | 22 +++- ballerina/llm.bal | 23 ++-- ballerina/react.bal | 18 ++- ballerina/tool.bal | 2 +- 8 files changed, 360 insertions(+), 229 deletions(-) diff --git a/ballerina/Ballerina.toml b/ballerina/Ballerina.toml index 5aad396..2654b54 100644 --- a/ballerina/Ballerina.toml +++ b/ballerina/Ballerina.toml @@ -2,7 +2,7 @@ distribution = "2201.8.4" org = "ballerinax" name = "ai.agent" -version = "0.7.1" +version = "0.7.2" license = ["Apache-2.0"] authors = ["Ballerina"] keywords = ["AI/Agent", "Cost/Freemium"] diff --git a/ballerina/Module.md b/ballerina/Module.md index 7982acd..518e617 100644 --- a/ballerina/Module.md +++ b/ballerina/Module.md @@ -140,109 +140,173 @@ agent:HttpServiceToolKit serviceAToolKit = check new ( ## Model -This is a large language model (LLM) instance. Currently, the agent module has support for the following LLM APIs. +This is a large language model (LLM) instance. This module offers three types of LLM APIs: completion, chat, and function calling. Currently, the module includes the following LLMs. -1) OpenAI GPT3 +### Completion LLM APIs +- OpenAI GPT3 ```ballerina - agent:Gpt3Model model = check new ({auth: {token: }}); + agent:Gpt3Model model = check new ({auth: {token: ""}}); ``` -2) OpenAI ChatGPT (e.g. GPT3.5, GPT4) +- Azure OpenAI GPT3 ```ballerina - agent:ChatGptModel model = check new ({auth: {token: }}); + agent:AzureGpt3Model model = check new ({auth: {apiKey: ""}}, serviceUrl, deploymentId, apiVersion); ``` -3) Azure OpenAI GPT3 +### Chat and function calling LLM APIs +- OpenAI ChatGPT (GPT3.5 and GPT4) ```ballerina - agent:AzureGpt3Model model = check new ({auth: {apiKey: }}, string serviceUrl, string deploymentId, string apiVersion); + agent:ChatGptModel model = check new ({auth: {token: ""}}); ``` - ``` -4) Azure OpenAI ChatGPT (e.g. GPT3.5, GPT4) +- Azure OpenAI ChatGPT (GPT3.5 and GPT4) ```ballerina - agent:AzureChatGptModel model = check new ({auth: {apiKey: }}, string serviceUrl, string deploymentId, string apiVersion); + agent:AzureChatGptModel model = check new ({auth: {apiKey: ""}}, serviceUrl, deploymentId, apiVersion); ``` + + ### Extending `LlmModel` for Custom Models -This module offers extended support for utilizing other LLMs by extending the `LlmModel` as demonstrated below: +This module provides extended support for leveraging other LLMs through the extension of a suitable LLM API interface. To extend an LLM to support both chat and function calling APIs, the following example can be followed. ```ballerina isolated class NewLlmModel { - *agent:LlmModel; // extends LlmModel + *agent:ChatLlmModel; // extends Chat API interface + *agent:FunctionCallLlmModel; // extends FunctionCall API interface - // Implement the init method to initialize the connection with the new LLM (if required) + function init() returns error? { + // initialize the connection with the new LLM + } + + public isolated function chatComplete(agent:ChatMessage[] messages, string? stop = ()) returns string|agent:LlmError { + // implement to call chat API of the new LLM + // return the text content of the response + return ""; + } - public isolated function generate(agent:PromptConstruct prompt) returns string|error { - // Utilize utilities to create a completion prompt (or chat prompt) if applicable - string completionPrompt = agent:createCompletionPrompt(prompt); - // Add logic to call the LLM with the completionPrompt - // Return the generated text from the LLM + public isolated function functionCall(agent:ChatMessage[] messages, agent:ChatCompletionFunctions[] functions, string? stop) returns string|agent:FunctionCall|agent:LlmError { + // implement to call function call API of the new LLM + // return the function call or the text content if the response is a chat response + return {}; } } ``` -By extending `LlmModel`, the `NewLlmModel` gains the ability to interface with other LLMs seamlessly. To utilize `NewLlmModel`, you can follow a similar approach as with other built-in LLM models. This allows you to harness the power of custom LLMs while maintaining compatibility with existing functionality. +By extending `agent:ChatLlmModel` and `agent:FunctionCallLlmModel`, the `NewLlmModel` is implemented to utilize the chat and function calling APIs of a new LLM model seamlessly. To gain a comprehensive understanding of the capabilities of this module in connecting to various custom LLM APIs for executing different types of agents, you can refer to other built-in LLM models. ## Agent -The agent facilitates the execution of natural language (NL) commands by leveraging the reasoning and text generation capabilities of LLMs (Language Models). It follows the [ReAct framework](https://arxiv.org/pdf/2210.03629.pdf): +The agent facilitates the execution of natural language (NL) commands by leveraging the reasoning and text generation capabilities of LLMs (Language Models). We have two types of Agents already in-built. + +### 1. Creation of Agents +#### a) ReAct Agent + +This Agent is implemented based on the [ReAct framework](https://arxiv.org/pdf/2210.03629.pdf). + +To create an `ReActAget`, you can use either `CompletionLlmModel` or `ChatCompletionLlmModel`. + +```ballerina +agent:ChatGptModel model = check new ({auth: {token: ""}}); +(agent:Tool|agent:BaseToolKit)[] tools = [ + //tools and toolkits +]; +agent:ReActAgent agent = check new (model, ...tools); +``` + +#### b) Function Calling Agent -To create an agent, you need an LLM model and a set of Tool (or ToolKit) definitions. +This agent is implemented to use function calling APIs (e.g. [OpenAI Function Calls](https://openai.com/blog/function-calling-and-other-api-updates)). +Creating a `FunctionCall` agent is similar to the `ReActAgent`, but you can use only `FunctionCallLlmModel` with this type of agents. ```ballerina +agent:ChatGptModel model = check new ({auth: {token: ""}}); (agent:Tool|agent:BaseToolKit)[] tools = [ //tools and toolkits ]; -agent:Agent agent = check new (model, ...tools); +agent:FunctionCallAgent agent = check new (model, ...tools); +``` + +### 2. Extending `BaseAgent` to build Custom Agents + +This module enables the extension of new types of agents by modifying the reasoning protocols. To define a new agent, the selectNextTool and parseLlmResponse methods should be implemented accordingly. + +This module allows extending new type of Agents by modifying the reasoning protocols. To define a new Agent, `selectNextTool` and `parseLlmResponse` methods should be implemented accordingly. + +```ballerina +isolated class NewAgent{ + *agent:BaseAgent; + + public isolated function selectNextTool(agent:ExecutionProgress progress) returns json|agent:LlmError { + // define the logic to reason and select the next tool + // returns the content from the LLM response, which can be parsed using the parseLlmResponse function + return ""; + } + + public isolated function parseLlmResponse(json llmResponse) returns agent:LlmToolResponse|agent:LlmChatResponse|agent:LlmInvalidGenerationError { + // defines the logic to parse the LLM response generated by the selectNextTool function + // returns a LlmToolResponse if parsed response contains a tool + // returns a LlmChatResponse if parsed response contains a chat response + // returns a LlmInvalidGenerationError if the response is invalid + return {}; + } +} ``` -There are multiple ways to utilize the agent. +### 3. Using Agents -### 1. Agent.run() for Batch Execution +#### a). `agent:run` for Batch Execution -The agent can be executed without interruptions using `Agent.run()`. It attempts to fully execute the given NL command and returns the results at each step. +The agent can be executed without interruptions using `agent:run`. ```ballerina -agent:ExecutionStep[] execution = agent.run("", maxIter = 10); +record {|(agent:ExecutionResult|agent:ExecutionError)[] steps; string answer?;|} run = agent:run(agent, "", maxIter = 10); ``` +It attempts to fully execute the given NL command and returns a record with the execution steps (whether a tool execution or an error) and the final answer to the question. -### 2. `AgentIterator` for `foreach` Execution +#### b). `agent:Iterator` for `foreach` Execution -The agent can also act as an iterator, providing reasoning and output from the tool at each step while executing the command. +The agent can function as an iterator, delivering reasoning and observation from the tool at each step during the execution of the command. ```ballerina -agent:AgentIterator agentIterator = agent.getIterator(""); -foreach agent:ExecutionStep|error step in agentIterator{ +agent:Iterator agentIterator = new (agent, query = ""); +foreach agent:ExecutionResult|agent:ExecutionError|agent:LlmChatResponse|error step in agentIterator { // logic goes here - // can decide whether to continue/rollback/exit the loop based on the observation from the tool + // can decide whether to continue/rollback/exit the loop based on returned record type and the observations during the execution } ``` -### 3. `AgentExecutor` for Reason-Act Interface +The `agent:Iterator` returns one of the record types defined in the example above, depending on the execution status. + +#### c). `agent:Executor` for advanced use-cases -The `AgentExecutor` offers enhanced flexibility for running agents through its `reason()` and `act(string thought)` methods. This separation of reasoning and acting enables developers to obtain user confirmation before executing actions based on the agent's reasoning. This feature is particularly valuable for verifying, validating, or refining the agent's reasoning by incorporating user intervention or feedback as new observations, which can be achieved using the `update(ExecutionStep step)` method of `AgentExecutor`. +The `agent:Executor` offers enhanced flexibility for running agents with a two-step process of `reason()` and `act(json llmResponse)`. This separation allows developers to obtain user confirmations before executing actions based on the agent's reasoning. This feature is particularly valuable for verifying, validating, or refining the agent's reasoning by incorporating user intervention or feedback as new observations, which can be achieved using the `update(ExecutionStep step)` method of `agent:Executor`. -Additionally, this approach empowers users to manipulate the execution trace of the agent based on specific requirements by modifying the records of previous execution steps. This capability becomes handy in situations where certain steps need to be excluded during execution (e.g., unsuccessful or outdated steps). Moreover, manual execution can be performed selectively, such as handling specific errors or acquiring user inputs. The `AgentExecutor` allows you to customize the execution trace to suit your needs effectively. +Additionally, this approach empowers users to manipulate the execution by modifying the query, history, or the context of the executor during the agent's execution. This capability becomes handy in situations where certain steps need to be excluded during execution (e.g., unsuccessful or outdated steps). Moreover, manual execution can be performed selectively, such as handling specific errors or acquiring user inputs. The `agent:Executor` allows you to customize the execution trace to suit your needs effectively. ```ballerina -string QUERY = ""; -agent:AgentExecutor agentExecutor = agent.getExecutor(QUERY); -while(agentExecutor.hasNext()){ - string|error thought = agentExecutor.reason(); // reasoning step - if thought is error { +agent:Executor agentExecutor = new (agent, query = ""); +while agentExecutor.hasNext() { + json|error llmResponse = agentExecutor.reason(); // decide the next tool to execute + if llmResponse is error { // reasoning fails due to LLM error. Handle appropriately break; } - // based on the reasoning user can decide whether to proceed with the action - // possible to validate the thought, improve it, or get user confirmation to proceed with the action - any|error observation = agentExecutor.act(thought); // acting step - if observation is error { - // error returned by the tool. Handle appropriately - // handle the error using another tool if needed tool - - // restart the execution after manipulating the trace - agent:ExecutionStep[] trace = agentExecutor.getExecutionHistory().history; - // manipulate the traces if required (e.g. remove unnecessary steps, add manual steps) - agentExecutor = agent.getExecutor(QUERY, trace); // restarts the execution from the last step - break; + + // based on the llmResponse users can take decisions here, but since it is still in raw format, processing is required + agent:ExecutionResult|agent:LlmChatResponse|agent:ExecutionError result = agentExecutor.act(llmResponse); // execute the tool based on the reasoning + if result is agent:ExecutionResult { + // tool executed and returned a result + // based on the tool result, can take decisions here + } else if result is agent:LlmChatResponse { + // execution completed with a chat response + } else { + // error during parsing the LLM response or invalid tool + // agent will retry automatically, if continue the execution } + + // can manipulate the `agentExecutor` at any point within this loop + // dynamically changing the query, history or context given to the agent can be useful in advanced use cases + // to get the current execution progress + agent:ExecutionProgress progress = agentExecutor.progress; + // modify the progress and replace the executor + agentExecutor = new (agent, progress); } ``` @@ -269,13 +333,10 @@ To begin, we need to define a `gmail->sendMessage` function as a tool. However, ```ballerina -isolated function sendEmail(gmail:MessageRequest messageRequest) returns string|error { +isolated function sendMail(record {|string senderEmail; gmail:MessageRequest messageRequest;|} input) returns string|error { gmail:Client gmail = check new ({auth: {token: gmailToken}}); - gmail:Message|error sendMessage = gmail->sendMessage(messageRequest); - if sendMessage is gmail:Message { - return sendMessage.toString(); - } - return "Error while sending the email" + sendMessage.message(); + gmail:Message message = check gmail->/users/[input.senderEmail]/messages/send.post(input.messageRequest); + return message.toString(); } ``` @@ -287,10 +348,19 @@ agent:Tool sendEmailTool = { description: "useful to send emails to a given recipient", parameters: { properties: { - recipient: {'type: agent:STRING}, - subject: {'type: agent:STRING}, - messageBody: {'type: agent:STRING}, - contentType: {'const: "text/plain"} + senderEmail: {'type: agent:STRING}, + messageRequest: { + properties: { + to: { + items: {'type: agent:STRING} + }, + subject: {'type: agent:STRING}, + bodyInHtml: { + 'type: agent:STRING, + format: "text/html" + } + } + } } }, caller: sendMail @@ -304,7 +374,13 @@ agent:HttpTool listWifiHttpTool = { name: "List wifi", path: "/guest-wifi-accounts/{ownerEmail}", method: agent:GET, - description: "useful to list the guest wifi accounts." + description: "useful to list the guest wifi accounts.", + parameters: { + ownerEmail: { + location: agent:PATH, + schema: {'type: agent:STRING} + } + } }; agent:HttpTool createWifiHttpTool = { @@ -342,7 +418,7 @@ To create the agent, we first need to initialize a LLM (e.g., `Gpt3Model`, `Chat ```ballerina agent:ChatGptModel model = check new ({auth: {token: }}); -agent:Agent agent = check new (model, wifiServiceToolKit, sendEmailTool); +agent:FunctionCallAgent agent = check new (model, wifiServiceToolKit, sendEmailTool); ``` ### Step 4 - Run the Agent @@ -351,7 +427,7 @@ Now we can run the agent with NL commands from the user. Note that in this case, ```ballerina string queryTemplate = string`create a new guest WiFi account for email ${wifiOwnerEmail} with user ${wifiUsername} and password ${wifiPassword}. Send the available list of WiFi accounts for that email to ${recipientEmail}`; -agent:ExecutionStep[] run = agent.run(query); +_ run = agent.run(agent, query); ``` ## Output @@ -365,41 +441,29 @@ The agent will proceed with multiple reasoning-action iterations as follows to e 1) Agent creates a new WiFi account for owner `johnny@wso2.com`: `````` - Reasoning iteration: 1 - Thought: We need to create a new guest WiFi account with the given username and password, and then list the available WiFi accounts for the email owner and send it to a specified recipient. - Action: + Agent Iteration 1 + Action: ``` { - "tool": "Create wifi", - "tool_input": { - "requestBody": { - "email": "johnny@wso2.com", - "username": "guest123", - "password": "john123" - } - } + name: Create_wifi, + arguments: {"requestBody":{"email":"johnny@wso2.com","username":"guest123","password":"john123"},"path":"/guest-wifi-accounts"} } ``` - Observation: Successfully added the wifi account + Observation: {"code":201,"path":"/guest-wifi-accounts","headers":{"contentType":"text/plain","contentLength":35},"body":"Successfully added the wifi account"} `````` 2) Agent finds existing guest WiFi accounts under the owner `johnny@wso2.com`: `````` - Reasoning iteration: 2 - Thought: Now we need to use the "List wifi" tool to get the available list of wifi accounts for the email "alexa@wso2.com". + Agent Iteration 2 Action: ``` { - "tool": "List wifi", - "tool_input": { - "pathParameters": { - "ownerEmail": "johnny@wso2.com" - } - } + name: List_wifi, + arguments: {"parameters":{"ownerEmail":"johnny@wso2.com"},"path":"/guest-wifi-accounts/{ownerEmail}"} } ``` - Observation: ["guest123.guestOf.johnny","newGuest.guestOf.johnny"] + Observation: {"code":200,"path":"/guest-wifi-accounts/johnny@wso2.com","headers":{"contentType":"application/json","contentLength":104},"body":"["guest123.guestOf.johnny","newGuest.guestOf.johnny"]"} `````` 3) Agent sends an email to `alexa@wso2.com` with the information about the existing accounts: @@ -407,17 +471,12 @@ The agent will proceed with multiple reasoning-action iterations as follows to e In this step, the agent is responsible for generating the email subject and message body as well. The user provides only the recipient's email. `````` - Reasoning iteration: 3 - Thought: Finally, we need to send the available wifi list to the specified recipient. + Agent Iteration 3 Action: ``` { - "tool": "Send mail", - "tool_input": { - "recipient": "alexa@wso2.com", - "subject": "Available Wifi List", - "messageBody": "The available wifi accounts for johnny@wso2.com are: guest123.guestOf.johnny, newGuest.guestOf.johnny" - } + name: Send_mail, + arguments: {"messageRequest":{"to":["alexa@wso2.com"],"subject":"List of WiFi accounts","bodyInHtml":"Here is the list of available WiFi accounts for your email:

guest123.guestOf.johnny
newGuest.guestOf.johnny"},"senderEmail":"johnny@wso2.com"} } ``` Observation: {"threadId":"1884d1bda3d2c286","id":"1884d1bda3d2c286","labelIds":["SENT"]} @@ -426,8 +485,6 @@ The agent will proceed with multiple reasoning-action iterations as follows to e 4) Agent concludes the task: ``` - Reasoning iteration: 4 - Thought: I now know the final answer Final Answer: Successfully created a new guest wifi account with username "guest123" and password "john123" for the email owner "johnny@wso2.com". The available wifi accounts for "johnny@wso2.com" are "guest123.guestOf.johnny" and "newGuest.guestOf.johnny", and this list has been sent to the specified recipient "alexa@wso2.com". ``` diff --git a/ballerina/Package.md b/ballerina/Package.md index 7982acd..518e617 100644 --- a/ballerina/Package.md +++ b/ballerina/Package.md @@ -140,109 +140,173 @@ agent:HttpServiceToolKit serviceAToolKit = check new ( ## Model -This is a large language model (LLM) instance. Currently, the agent module has support for the following LLM APIs. +This is a large language model (LLM) instance. This module offers three types of LLM APIs: completion, chat, and function calling. Currently, the module includes the following LLMs. -1) OpenAI GPT3 +### Completion LLM APIs +- OpenAI GPT3 ```ballerina - agent:Gpt3Model model = check new ({auth: {token: }}); + agent:Gpt3Model model = check new ({auth: {token: ""}}); ``` -2) OpenAI ChatGPT (e.g. GPT3.5, GPT4) +- Azure OpenAI GPT3 ```ballerina - agent:ChatGptModel model = check new ({auth: {token: }}); + agent:AzureGpt3Model model = check new ({auth: {apiKey: ""}}, serviceUrl, deploymentId, apiVersion); ``` -3) Azure OpenAI GPT3 +### Chat and function calling LLM APIs +- OpenAI ChatGPT (GPT3.5 and GPT4) ```ballerina - agent:AzureGpt3Model model = check new ({auth: {apiKey: }}, string serviceUrl, string deploymentId, string apiVersion); + agent:ChatGptModel model = check new ({auth: {token: ""}}); ``` - ``` -4) Azure OpenAI ChatGPT (e.g. GPT3.5, GPT4) +- Azure OpenAI ChatGPT (GPT3.5 and GPT4) ```ballerina - agent:AzureChatGptModel model = check new ({auth: {apiKey: }}, string serviceUrl, string deploymentId, string apiVersion); + agent:AzureChatGptModel model = check new ({auth: {apiKey: ""}}, serviceUrl, deploymentId, apiVersion); ``` + + ### Extending `LlmModel` for Custom Models -This module offers extended support for utilizing other LLMs by extending the `LlmModel` as demonstrated below: +This module provides extended support for leveraging other LLMs through the extension of a suitable LLM API interface. To extend an LLM to support both chat and function calling APIs, the following example can be followed. ```ballerina isolated class NewLlmModel { - *agent:LlmModel; // extends LlmModel + *agent:ChatLlmModel; // extends Chat API interface + *agent:FunctionCallLlmModel; // extends FunctionCall API interface - // Implement the init method to initialize the connection with the new LLM (if required) + function init() returns error? { + // initialize the connection with the new LLM + } + + public isolated function chatComplete(agent:ChatMessage[] messages, string? stop = ()) returns string|agent:LlmError { + // implement to call chat API of the new LLM + // return the text content of the response + return ""; + } - public isolated function generate(agent:PromptConstruct prompt) returns string|error { - // Utilize utilities to create a completion prompt (or chat prompt) if applicable - string completionPrompt = agent:createCompletionPrompt(prompt); - // Add logic to call the LLM with the completionPrompt - // Return the generated text from the LLM + public isolated function functionCall(agent:ChatMessage[] messages, agent:ChatCompletionFunctions[] functions, string? stop) returns string|agent:FunctionCall|agent:LlmError { + // implement to call function call API of the new LLM + // return the function call or the text content if the response is a chat response + return {}; } } ``` -By extending `LlmModel`, the `NewLlmModel` gains the ability to interface with other LLMs seamlessly. To utilize `NewLlmModel`, you can follow a similar approach as with other built-in LLM models. This allows you to harness the power of custom LLMs while maintaining compatibility with existing functionality. +By extending `agent:ChatLlmModel` and `agent:FunctionCallLlmModel`, the `NewLlmModel` is implemented to utilize the chat and function calling APIs of a new LLM model seamlessly. To gain a comprehensive understanding of the capabilities of this module in connecting to various custom LLM APIs for executing different types of agents, you can refer to other built-in LLM models. ## Agent -The agent facilitates the execution of natural language (NL) commands by leveraging the reasoning and text generation capabilities of LLMs (Language Models). It follows the [ReAct framework](https://arxiv.org/pdf/2210.03629.pdf): +The agent facilitates the execution of natural language (NL) commands by leveraging the reasoning and text generation capabilities of LLMs (Language Models). We have two types of Agents already in-built. + +### 1. Creation of Agents +#### a) ReAct Agent + +This Agent is implemented based on the [ReAct framework](https://arxiv.org/pdf/2210.03629.pdf). + +To create an `ReActAget`, you can use either `CompletionLlmModel` or `ChatCompletionLlmModel`. + +```ballerina +agent:ChatGptModel model = check new ({auth: {token: ""}}); +(agent:Tool|agent:BaseToolKit)[] tools = [ + //tools and toolkits +]; +agent:ReActAgent agent = check new (model, ...tools); +``` + +#### b) Function Calling Agent -To create an agent, you need an LLM model and a set of Tool (or ToolKit) definitions. +This agent is implemented to use function calling APIs (e.g. [OpenAI Function Calls](https://openai.com/blog/function-calling-and-other-api-updates)). +Creating a `FunctionCall` agent is similar to the `ReActAgent`, but you can use only `FunctionCallLlmModel` with this type of agents. ```ballerina +agent:ChatGptModel model = check new ({auth: {token: ""}}); (agent:Tool|agent:BaseToolKit)[] tools = [ //tools and toolkits ]; -agent:Agent agent = check new (model, ...tools); +agent:FunctionCallAgent agent = check new (model, ...tools); +``` + +### 2. Extending `BaseAgent` to build Custom Agents + +This module enables the extension of new types of agents by modifying the reasoning protocols. To define a new agent, the selectNextTool and parseLlmResponse methods should be implemented accordingly. + +This module allows extending new type of Agents by modifying the reasoning protocols. To define a new Agent, `selectNextTool` and `parseLlmResponse` methods should be implemented accordingly. + +```ballerina +isolated class NewAgent{ + *agent:BaseAgent; + + public isolated function selectNextTool(agent:ExecutionProgress progress) returns json|agent:LlmError { + // define the logic to reason and select the next tool + // returns the content from the LLM response, which can be parsed using the parseLlmResponse function + return ""; + } + + public isolated function parseLlmResponse(json llmResponse) returns agent:LlmToolResponse|agent:LlmChatResponse|agent:LlmInvalidGenerationError { + // defines the logic to parse the LLM response generated by the selectNextTool function + // returns a LlmToolResponse if parsed response contains a tool + // returns a LlmChatResponse if parsed response contains a chat response + // returns a LlmInvalidGenerationError if the response is invalid + return {}; + } +} ``` -There are multiple ways to utilize the agent. +### 3. Using Agents -### 1. Agent.run() for Batch Execution +#### a). `agent:run` for Batch Execution -The agent can be executed without interruptions using `Agent.run()`. It attempts to fully execute the given NL command and returns the results at each step. +The agent can be executed without interruptions using `agent:run`. ```ballerina -agent:ExecutionStep[] execution = agent.run("", maxIter = 10); +record {|(agent:ExecutionResult|agent:ExecutionError)[] steps; string answer?;|} run = agent:run(agent, "", maxIter = 10); ``` +It attempts to fully execute the given NL command and returns a record with the execution steps (whether a tool execution or an error) and the final answer to the question. -### 2. `AgentIterator` for `foreach` Execution +#### b). `agent:Iterator` for `foreach` Execution -The agent can also act as an iterator, providing reasoning and output from the tool at each step while executing the command. +The agent can function as an iterator, delivering reasoning and observation from the tool at each step during the execution of the command. ```ballerina -agent:AgentIterator agentIterator = agent.getIterator(""); -foreach agent:ExecutionStep|error step in agentIterator{ +agent:Iterator agentIterator = new (agent, query = ""); +foreach agent:ExecutionResult|agent:ExecutionError|agent:LlmChatResponse|error step in agentIterator { // logic goes here - // can decide whether to continue/rollback/exit the loop based on the observation from the tool + // can decide whether to continue/rollback/exit the loop based on returned record type and the observations during the execution } ``` -### 3. `AgentExecutor` for Reason-Act Interface +The `agent:Iterator` returns one of the record types defined in the example above, depending on the execution status. + +#### c). `agent:Executor` for advanced use-cases -The `AgentExecutor` offers enhanced flexibility for running agents through its `reason()` and `act(string thought)` methods. This separation of reasoning and acting enables developers to obtain user confirmation before executing actions based on the agent's reasoning. This feature is particularly valuable for verifying, validating, or refining the agent's reasoning by incorporating user intervention or feedback as new observations, which can be achieved using the `update(ExecutionStep step)` method of `AgentExecutor`. +The `agent:Executor` offers enhanced flexibility for running agents with a two-step process of `reason()` and `act(json llmResponse)`. This separation allows developers to obtain user confirmations before executing actions based on the agent's reasoning. This feature is particularly valuable for verifying, validating, or refining the agent's reasoning by incorporating user intervention or feedback as new observations, which can be achieved using the `update(ExecutionStep step)` method of `agent:Executor`. -Additionally, this approach empowers users to manipulate the execution trace of the agent based on specific requirements by modifying the records of previous execution steps. This capability becomes handy in situations where certain steps need to be excluded during execution (e.g., unsuccessful or outdated steps). Moreover, manual execution can be performed selectively, such as handling specific errors or acquiring user inputs. The `AgentExecutor` allows you to customize the execution trace to suit your needs effectively. +Additionally, this approach empowers users to manipulate the execution by modifying the query, history, or the context of the executor during the agent's execution. This capability becomes handy in situations where certain steps need to be excluded during execution (e.g., unsuccessful or outdated steps). Moreover, manual execution can be performed selectively, such as handling specific errors or acquiring user inputs. The `agent:Executor` allows you to customize the execution trace to suit your needs effectively. ```ballerina -string QUERY = ""; -agent:AgentExecutor agentExecutor = agent.getExecutor(QUERY); -while(agentExecutor.hasNext()){ - string|error thought = agentExecutor.reason(); // reasoning step - if thought is error { +agent:Executor agentExecutor = new (agent, query = ""); +while agentExecutor.hasNext() { + json|error llmResponse = agentExecutor.reason(); // decide the next tool to execute + if llmResponse is error { // reasoning fails due to LLM error. Handle appropriately break; } - // based on the reasoning user can decide whether to proceed with the action - // possible to validate the thought, improve it, or get user confirmation to proceed with the action - any|error observation = agentExecutor.act(thought); // acting step - if observation is error { - // error returned by the tool. Handle appropriately - // handle the error using another tool if needed tool - - // restart the execution after manipulating the trace - agent:ExecutionStep[] trace = agentExecutor.getExecutionHistory().history; - // manipulate the traces if required (e.g. remove unnecessary steps, add manual steps) - agentExecutor = agent.getExecutor(QUERY, trace); // restarts the execution from the last step - break; + + // based on the llmResponse users can take decisions here, but since it is still in raw format, processing is required + agent:ExecutionResult|agent:LlmChatResponse|agent:ExecutionError result = agentExecutor.act(llmResponse); // execute the tool based on the reasoning + if result is agent:ExecutionResult { + // tool executed and returned a result + // based on the tool result, can take decisions here + } else if result is agent:LlmChatResponse { + // execution completed with a chat response + } else { + // error during parsing the LLM response or invalid tool + // agent will retry automatically, if continue the execution } + + // can manipulate the `agentExecutor` at any point within this loop + // dynamically changing the query, history or context given to the agent can be useful in advanced use cases + // to get the current execution progress + agent:ExecutionProgress progress = agentExecutor.progress; + // modify the progress and replace the executor + agentExecutor = new (agent, progress); } ``` @@ -269,13 +333,10 @@ To begin, we need to define a `gmail->sendMessage` function as a tool. However, ```ballerina -isolated function sendEmail(gmail:MessageRequest messageRequest) returns string|error { +isolated function sendMail(record {|string senderEmail; gmail:MessageRequest messageRequest;|} input) returns string|error { gmail:Client gmail = check new ({auth: {token: gmailToken}}); - gmail:Message|error sendMessage = gmail->sendMessage(messageRequest); - if sendMessage is gmail:Message { - return sendMessage.toString(); - } - return "Error while sending the email" + sendMessage.message(); + gmail:Message message = check gmail->/users/[input.senderEmail]/messages/send.post(input.messageRequest); + return message.toString(); } ``` @@ -287,10 +348,19 @@ agent:Tool sendEmailTool = { description: "useful to send emails to a given recipient", parameters: { properties: { - recipient: {'type: agent:STRING}, - subject: {'type: agent:STRING}, - messageBody: {'type: agent:STRING}, - contentType: {'const: "text/plain"} + senderEmail: {'type: agent:STRING}, + messageRequest: { + properties: { + to: { + items: {'type: agent:STRING} + }, + subject: {'type: agent:STRING}, + bodyInHtml: { + 'type: agent:STRING, + format: "text/html" + } + } + } } }, caller: sendMail @@ -304,7 +374,13 @@ agent:HttpTool listWifiHttpTool = { name: "List wifi", path: "/guest-wifi-accounts/{ownerEmail}", method: agent:GET, - description: "useful to list the guest wifi accounts." + description: "useful to list the guest wifi accounts.", + parameters: { + ownerEmail: { + location: agent:PATH, + schema: {'type: agent:STRING} + } + } }; agent:HttpTool createWifiHttpTool = { @@ -342,7 +418,7 @@ To create the agent, we first need to initialize a LLM (e.g., `Gpt3Model`, `Chat ```ballerina agent:ChatGptModel model = check new ({auth: {token: }}); -agent:Agent agent = check new (model, wifiServiceToolKit, sendEmailTool); +agent:FunctionCallAgent agent = check new (model, wifiServiceToolKit, sendEmailTool); ``` ### Step 4 - Run the Agent @@ -351,7 +427,7 @@ Now we can run the agent with NL commands from the user. Note that in this case, ```ballerina string queryTemplate = string`create a new guest WiFi account for email ${wifiOwnerEmail} with user ${wifiUsername} and password ${wifiPassword}. Send the available list of WiFi accounts for that email to ${recipientEmail}`; -agent:ExecutionStep[] run = agent.run(query); +_ run = agent.run(agent, query); ``` ## Output @@ -365,41 +441,29 @@ The agent will proceed with multiple reasoning-action iterations as follows to e 1) Agent creates a new WiFi account for owner `johnny@wso2.com`: `````` - Reasoning iteration: 1 - Thought: We need to create a new guest WiFi account with the given username and password, and then list the available WiFi accounts for the email owner and send it to a specified recipient. - Action: + Agent Iteration 1 + Action: ``` { - "tool": "Create wifi", - "tool_input": { - "requestBody": { - "email": "johnny@wso2.com", - "username": "guest123", - "password": "john123" - } - } + name: Create_wifi, + arguments: {"requestBody":{"email":"johnny@wso2.com","username":"guest123","password":"john123"},"path":"/guest-wifi-accounts"} } ``` - Observation: Successfully added the wifi account + Observation: {"code":201,"path":"/guest-wifi-accounts","headers":{"contentType":"text/plain","contentLength":35},"body":"Successfully added the wifi account"} `````` 2) Agent finds existing guest WiFi accounts under the owner `johnny@wso2.com`: `````` - Reasoning iteration: 2 - Thought: Now we need to use the "List wifi" tool to get the available list of wifi accounts for the email "alexa@wso2.com". + Agent Iteration 2 Action: ``` { - "tool": "List wifi", - "tool_input": { - "pathParameters": { - "ownerEmail": "johnny@wso2.com" - } - } + name: List_wifi, + arguments: {"parameters":{"ownerEmail":"johnny@wso2.com"},"path":"/guest-wifi-accounts/{ownerEmail}"} } ``` - Observation: ["guest123.guestOf.johnny","newGuest.guestOf.johnny"] + Observation: {"code":200,"path":"/guest-wifi-accounts/johnny@wso2.com","headers":{"contentType":"application/json","contentLength":104},"body":"["guest123.guestOf.johnny","newGuest.guestOf.johnny"]"} `````` 3) Agent sends an email to `alexa@wso2.com` with the information about the existing accounts: @@ -407,17 +471,12 @@ The agent will proceed with multiple reasoning-action iterations as follows to e In this step, the agent is responsible for generating the email subject and message body as well. The user provides only the recipient's email. `````` - Reasoning iteration: 3 - Thought: Finally, we need to send the available wifi list to the specified recipient. + Agent Iteration 3 Action: ``` { - "tool": "Send mail", - "tool_input": { - "recipient": "alexa@wso2.com", - "subject": "Available Wifi List", - "messageBody": "The available wifi accounts for johnny@wso2.com are: guest123.guestOf.johnny, newGuest.guestOf.johnny" - } + name: Send_mail, + arguments: {"messageRequest":{"to":["alexa@wso2.com"],"subject":"List of WiFi accounts","bodyInHtml":"Here is the list of available WiFi accounts for your email:

guest123.guestOf.johnny
newGuest.guestOf.johnny"},"senderEmail":"johnny@wso2.com"} } ``` Observation: {"threadId":"1884d1bda3d2c286","id":"1884d1bda3d2c286","labelIds":["SENT"]} @@ -426,8 +485,6 @@ The agent will proceed with multiple reasoning-action iterations as follows to e 4) Agent concludes the task: ``` - Reasoning iteration: 4 - Thought: I now know the final answer Final Answer: Successfully created a new guest wifi account with username "guest123" and password "john123" for the email owner "johnny@wso2.com". The available wifi accounts for "johnny@wso2.com" are "guest123.guestOf.johnny" and "newGuest.guestOf.johnny", and this list has been sent to the specified recipient "alexa@wso2.com". ``` diff --git a/ballerina/agent.bal b/ballerina/agent.bal index 91edbe2..41e142b 100644 --- a/ballerina/agent.bal +++ b/ballerina/agent.bal @@ -70,20 +70,20 @@ public type ToolOutput record {| |}; public type BaseAgent distinct isolated object { - LlmModel model; - ToolStore toolStore; - - # Use LLMs to decide the next tool/step. - # - # + progress - QueryProgress with the current query and execution history - # + return - NextAction decided by the LLM or an error if call to the LLM fails - isolated function selectNextTool(ExecutionProgress progress) returns json|LlmError; + public LlmModel model; + public ToolStore toolStore; # Parse the llm response and extract the tool to be executed. # # + llmResponse - Raw LLM response - # + return - SelectedTool or an error if parsing fails - isolated function parseLlmResponse(json llmResponse) returns LlmToolResponse|LlmChatResponse|LlmInvalidGenerationError; + # + return - A record containing the tool decided by the LLM, chat response or an error if the response is invalid + public isolated function parseLlmResponse(json llmResponse) returns LlmToolResponse|LlmChatResponse|LlmInvalidGenerationError; + + # Use LLM to decide the next tool/step. + # + # + progress - Execution progress with the current query and execution history + # + return - LLM response containing the tool or chat response (or an error if the call fails) + public isolated function selectNextTool(ExecutionProgress progress) returns json|LlmError; }; # An iterator to iterate over agent's execution @@ -96,8 +96,8 @@ public class Iterator { # + agent - Agent instance to be executed # + query - Natural language query to be executed by the agent # + context - Contextual information to be used by the agent during the execution - public isolated function init(BaseAgent agent, string query, map|string? context = ()) { - self.executor = new (agent, query = query, context = context); + public isolated function init(BaseAgent agent, *ExecutionProgress progress) { + self.executor = new (agent, progress); } # Iterate over the agent's execution steps. @@ -226,7 +226,7 @@ public class Executor { public isolated function run(BaseAgent agent, string query, int maxIter = 5, string|map context = {}, boolean verbose = true) returns record {|(ExecutionResult|ExecutionError)[] steps; string answer?;|} { (ExecutionResult|ExecutionError)[] steps = []; string? content = (); - Iterator iterator = new (agent, query, context = context); + Iterator iterator = new (agent, query = query, context = context); int iter = 0; foreach ExecutionResult|LlmChatResponse|ExecutionError|error step in iterator { if iter == maxIter { @@ -253,7 +253,7 @@ public isolated function run(BaseAgent agent, string query, int maxIter = 5, str ${BACKTICKS} { ${ACTION_NAME_KEY}: ${tool.name}, - ${ACTION_ARGUEMENTS_KEY}: ${(tool.arguments ?: "None").toString()}} + ${ACTION_ARGUEMENTS_KEY}: ${(tool.arguments ?: "None").toString()} } ${BACKTICKS}`); anydata|error observation = step?.observation; diff --git a/ballerina/function_call.bal b/ballerina/function_call.bal index 884f1bc..1c0f094 100644 --- a/ballerina/function_call.bal +++ b/ballerina/function_call.bal @@ -18,19 +18,25 @@ # This agent uses OpenAI function call API to perform the tool selection. public isolated class FunctionCallAgent { *BaseAgent; - final ToolStore toolStore; - final FunctionCallLlm model; + # Tool store to be used by the agent + public final ToolStore toolStore; + # LLM model instance (should be a function call model) + public final FunctionCallLlmModel model; # Initialize an Agent. # # + model - LLM model instance # + tools - Tools to be used by the agent - public isolated function init(FunctionCallLlm model, (BaseToolKit|Tool)... tools) returns error? { + public isolated function init(FunctionCallLlmModel model, (BaseToolKit|Tool)... tools) returns error? { self.toolStore = check new (...tools); self.model = model; } - isolated function parseLlmResponse(json llmResponse) returns LlmToolResponse|LlmChatResponse|LlmInvalidGenerationError { + # Parse the function calling API response and extract the tool to be executed. + # + # + llmResponse - Raw LLM response + # + return - A record containing the tool decided by the LLM, chat response or an error if the response is invalid + public isolated function parseLlmResponse(json llmResponse) returns LlmToolResponse|LlmChatResponse|LlmInvalidGenerationError { if llmResponse is string { return {content: llmResponse}; } @@ -55,9 +61,13 @@ public isolated class FunctionCallAgent { }; } - isolated function selectNextTool(ExecutionProgress progress) returns json|LlmError { + # Use LLM to decide the next tool/step based on the function calling APIs. + # + # + progress - Execution progress with the current query and execution history + # + return - LLM response containing the tool or chat response (or an error if the call fails) + public isolated function selectNextTool(ExecutionProgress progress) returns json|LlmError { ChatMessage[] messages = createFunctionCallMessages(progress); - return self.model.functionaCall(messages, + return self.model.functionCall(messages, from AgentTool tool in self.toolStore.tools.toArray() select { name: tool.name, diff --git a/ballerina/llm.bal b/ballerina/llm.bal index a41ed89..b07d04d 100644 --- a/ballerina/llm.bal +++ b/ballerina/llm.bal @@ -86,28 +86,25 @@ public type LlmModel distinct isolated object { # Extendable LLM model object for completion models. public type CompletionLlmModel distinct isolated object { *LlmModel; - CompletionModelConfig modelConfig; public isolated function complete(string prompt, string? stop = ()) returns string|LlmError; }; # Extendable LLM model object for chat LLM models public type ChatLlmModel distinct isolated object { *LlmModel; - ChatModelConfig modelConfig; public isolated function chatComplete(ChatMessage[] messages, string? stop = ()) returns string|LlmError; }; # Extendable LLM model object for LLM models with function call API -public type FunctionCallLlm distinct isolated object { +public type FunctionCallLlmModel distinct isolated object { *LlmModel; - ChatModelConfig modelConfig; - public isolated function functionaCall(ChatMessage[] messages, ChatCompletionFunctions[] functions, string? stop = ()) returns FunctionCall|string|LlmError; + public isolated function functionCall(ChatMessage[] messages, ChatCompletionFunctions[] functions, string? stop = ()) returns string|FunctionCall|LlmError; }; public isolated class Gpt3Model { *CompletionLlmModel; final text:Client llmClient; - final CompletionModelConfig modelConfig; + public final CompletionModelConfig modelConfig; # Initializes the GPT-3 model with the given connection configuration and model configuration. # @@ -140,7 +137,7 @@ public isolated class Gpt3Model { public isolated class AzureGpt3Model { *CompletionLlmModel; final azure_text:Client llmClient; - final CompletionModelConfig modelConfig; + public final CompletionModelConfig modelConfig; private final string deploymentId; private final string apiVersion; @@ -179,10 +176,10 @@ public isolated class AzureGpt3Model { } public isolated class ChatGptModel { - *FunctionCallLlm; + *FunctionCallLlmModel; *ChatLlmModel; final chat:Client llmClient; - final ChatModelConfig modelConfig; + public final ChatModelConfig modelConfig; # Initializes the ChatGPT model with the given connection configuration and model configuration. # @@ -219,7 +216,7 @@ public isolated class ChatGptModel { # + functions - Function definitions to be used for the function call # + stop - Stop sequence to stop the completion # + return - Function to be called, chat response or an error in-case of failures - public isolated function functionaCall(ChatMessage[] messages, ChatCompletionFunctions[] functions, string? stop = ()) returns FunctionCall|string|LlmError { + public isolated function functionCall(ChatMessage[] messages, ChatCompletionFunctions[] functions, string? stop = ()) returns string|FunctionCall|LlmError { chat:CreateChatCompletionResponse|error response = self.llmClient->/chat/completions.post({ ...self.modelConfig, @@ -246,10 +243,10 @@ public isolated class ChatGptModel { } public isolated class AzureChatGptModel { - *FunctionCallLlm; + *FunctionCallLlmModel; *ChatLlmModel; final azure_chat:Client llmClient; - final ChatModelConfig modelConfig; + public final ChatModelConfig modelConfig; private final string deploymentId; private final string apiVersion; @@ -294,7 +291,7 @@ public isolated class AzureChatGptModel { # + functions - Function definitions to be used for the function call # + stop - Stop sequence to stop the completion # + return - Function to be called, chat response or an error in-case of failures - public isolated function functionaCall(ChatMessage[] messages, ChatCompletionFunctions[] functions, string? stop = ()) returns FunctionCall|string|LlmError { + public isolated function functionCall(ChatMessage[] messages, ChatCompletionFunctions[] functions, string? stop = ()) returns string|FunctionCall|LlmError { azure_chat:CreateChatCompletionResponse|error response = self.llmClient->/deployments/[self.deploymentId]/chat/completions.post(self.apiVersion, { ...self.modelConfig, diff --git a/ballerina/react.bal b/ballerina/react.bal index bb6b4b6..fce0054 100644 --- a/ballerina/react.bal +++ b/ballerina/react.bal @@ -25,8 +25,10 @@ type ToolInfo readonly & record {| public isolated class ReActAgent { *BaseAgent; final string instructionPrompt; - final ToolStore toolStore; - final CompletionLlmModel|ChatLlmModel model; + # ToolStore instance to store the tools used by the agent + public final ToolStore toolStore; + # LLM model instance to be used by the agent (Can be either CompletionLlmModel or ChatLlmModel) + public final CompletionLlmModel|ChatLlmModel model; # Initialize an Agent. # @@ -39,9 +41,17 @@ public isolated class ReActAgent { log:printDebug("Instruction Prompt Generated Successfully", instructionPrompt = self.instructionPrompt); } - isolated function parseLlmResponse(json llmResponse) returns LlmToolResponse|LlmChatResponse|LlmInvalidGenerationError => parseReActLlmResponse(normalizeLlmResponse(llmResponse.toString())); + # Parse the ReAct llm response and extract the tool to be executed. + # + # + llmResponse - Raw LLM response + # + return - A record containing the tool decided by the LLM, chat response or an error if the response is invalid + public isolated function parseLlmResponse(json llmResponse) returns LlmToolResponse|LlmChatResponse|LlmInvalidGenerationError => parseReActLlmResponse(normalizeLlmResponse(llmResponse.toString())); - isolated function selectNextTool(ExecutionProgress progress) returns json|LlmError { + # Use LLM to decide the next tool/step based on the ReAct prompting + # + # + progress - Execution progress with the current query and execution history + # + return - LLM response containing the tool or chat response (or an error if the call fails) + public isolated function selectNextTool(ExecutionProgress progress) returns json|LlmError { map|string? context = progress.context; string contextPrompt = context is () ? "" : string `${"\n\n"}You can use these information if needed: ${context.toString()}$`; diff --git a/ballerina/tool.bal b/ballerina/tool.bal index 09135c4..91d046d 100644 --- a/ballerina/tool.bal +++ b/ballerina/tool.bal @@ -31,7 +31,7 @@ public type AgentTool record {| isolated function caller; |}; -isolated class ToolStore { +public isolated class ToolStore { final map & readonly tools; # Register tools to the agent. From 880e27764a561a703b7540ffcb8b72b675744a46 Mon Sep 17 00:00:00 2001 From: "Nadheesh Jihan, nadheesh@wso2.com" Date: Fri, 26 Jan 2024 19:11:50 +0530 Subject: [PATCH 2/4] Update gitignore --- .gitignore | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index f3d16e6..162f57a 100644 --- a/.gitignore +++ b/.gitignore @@ -50,5 +50,4 @@ ballerina/target ballerina/sample* ballerina/openapi* ballerina/schema* - - +$(pwd) From 594b96a6408840c0bbc3967d22226c44e01e50ae Mon Sep 17 00:00:00 2001 From: "Nadheesh Jihan, nadheesh@wso2.com" Date: Sat, 27 Jan 2024 21:21:46 +0530 Subject: [PATCH 3/4] Address PR comments --- ballerina/Module.md | 2 +- ballerina/Package.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ballerina/Module.md b/ballerina/Module.md index 518e617..4c4d5c8 100644 --- a/ballerina/Module.md +++ b/ballerina/Module.md @@ -183,7 +183,7 @@ isolated class NewLlmModel { public isolated function functionCall(agent:ChatMessage[] messages, agent:ChatCompletionFunctions[] functions, string? stop) returns string|agent:FunctionCall|agent:LlmError { // implement to call function call API of the new LLM // return the function call or the text content if the response is a chat response - return {}; + return {name: "FUNCTION_NAME", arguments: "FUNCTION_ARGUMENTS"}; } } ``` diff --git a/ballerina/Package.md b/ballerina/Package.md index 518e617..4c4d5c8 100644 --- a/ballerina/Package.md +++ b/ballerina/Package.md @@ -183,7 +183,7 @@ isolated class NewLlmModel { public isolated function functionCall(agent:ChatMessage[] messages, agent:ChatCompletionFunctions[] functions, string? stop) returns string|agent:FunctionCall|agent:LlmError { // implement to call function call API of the new LLM // return the function call or the text content if the response is a chat response - return {}; + return {name: "FUNCTION_NAME", arguments: "FUNCTION_ARGUMENTS"}; } } ``` From ab899d0627a85a7c6c8ee12fc33fa92db1f10a77 Mon Sep 17 00:00:00 2001 From: "Nadheesh Jihan, nadheesh@wso2.com" Date: Mon, 29 Jan 2024 10:27:36 +0530 Subject: [PATCH 4/4] Improve doc sample for extending agents --- ballerina/Module.md | 15 ++++++++++++--- ballerina/Package.md | 15 ++++++++++++--- ballerina/tool.bal | 2 +- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/ballerina/Module.md b/ballerina/Module.md index 4c4d5c8..90ffdf9 100644 --- a/ballerina/Module.md +++ b/ballerina/Module.md @@ -230,9 +230,18 @@ This module enables the extension of new types of agents by modifying the reason This module allows extending new type of Agents by modifying the reasoning protocols. To define a new Agent, `selectNextTool` and `parseLlmResponse` methods should be implemented accordingly. ```ballerina -isolated class NewAgent{ +isolated class NewChatAgent { *agent:BaseAgent; - + public final agent:ChatLlmModel model; // define the type of model to be used chat agent + public final agent:ToolStore toolStore; + + // defines the init function to initialize the agent + public function init(agent:ChatLlmModel model, (agent:BaseToolKit|agent:Tool)... tools) returns error? { + // initialize the agent with the given model and tools is mandatory + self.model = model; + self.toolStore = check new (...tools); + } + public isolated function selectNextTool(agent:ExecutionProgress progress) returns json|agent:LlmError { // define the logic to reason and select the next tool // returns the content from the LLM response, which can be parsed using the parseLlmResponse function @@ -244,7 +253,7 @@ isolated class NewAgent{ // returns a LlmToolResponse if parsed response contains a tool // returns a LlmChatResponse if parsed response contains a chat response // returns a LlmInvalidGenerationError if the response is invalid - return {}; + return {name: "