Skip to content

Commit be42c60

Browse files
committed
feat: add support for multi-tools
1 parent 2dd88a2 commit be42c60

File tree

6 files changed

+178
-117
lines changed

6 files changed

+178
-117
lines changed

packages/sui-agent/src/@types/interface.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Response interface for the intent agent's operations
22
export interface IntentAgentResponse {
33
success: boolean; // Indicates if the operation was successful
4-
selected_tool: null | string; // Name of the tool that was selected for the operation
4+
selected_tools: null | string[]; // Name of the tool that was selected for the operation
55
response: null | string; // Response message from the operation
66
needs_additional_info: boolean; // Indicates if more information is needed
77
additional_info_required: null | string[]; // List of additional information fields needed
@@ -13,7 +13,7 @@ export type ToolArgument = string | number | boolean | bigint;
1313
// Response interface for tool operations (similar to IntentAgentResponse)
1414
export interface toolResponse {
1515
success: boolean;
16-
selected_tool: null | string;
16+
selected_tools: null | string;
1717
response: null | string;
1818
needs_additional_info: boolean;
1919
additional_info_required: null | string[];

packages/sui-agent/src/agents/SuiAgent.ts

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import Utils from '../utils';
55
import intent_agent_prompt from '../prompts/intent_agent_prompt';
66
import final_answer_agent_prompt from '../prompts/final_answer_agent';
77
import Atoma from '../config/atoma';
8+
import decomposerPrompt from '../prompts/decomposer';
89

910
/**
1011
* Main agent class that handles intent processing and decision making
@@ -28,18 +29,25 @@ class Agents {
2829
registerAllTools(this.tools);
2930
}
3031

32+
async QueryDecomposer(prompt: string) {
33+
return await this.AtomaClass.atomaChat([
34+
{ content: decomposerPrompt, role: 'assistant' },
35+
{ content: prompt, role: 'user' },
36+
]);
37+
}
38+
3139
/**
3240
* Processes initial user intent and selects appropriate tools
3341
* @param prompt - User's input query
3442
* @returns IntentAgentResponse containing tool selection and processing details
3543
*/
36-
async IntentAgent(prompt: string, address?: string) {
37-
const IntentResponse: IntentAgentResponse =
44+
async IntentAgent(subqueries: string[], address?: string) {
45+
const IntentResponse: IntentAgentResponse[] =
3846
(await this.tools.selectAppropriateTool(
3947
this.AtomaClass,
40-
prompt,
48+
`${subqueries}`,
4149
address,
42-
)) as IntentAgentResponse;
50+
)) as IntentAgentResponse[];
4351

4452
return IntentResponse;
4553
}
@@ -51,15 +59,15 @@ class Agents {
5159
* @returns Processed response after decision making
5260
*/
5361
async DecisionMakingAgent(
54-
intentResponse: IntentAgentResponse,
62+
intentResponse: IntentAgentResponse[],
5563
query: string,
5664
) {
5765
// Pass both the selected tool name and arguments to processQuery
66+
5867
return await this.utils.processQuery(
5968
this.AtomaClass,
6069
query,
61-
intentResponse.selected_tool,
62-
intentResponse.tool_arguments,
70+
intentResponse,
6371
);
6472
}
6573

@@ -71,8 +79,13 @@ class Agents {
7179
*/
7280
async SuperVisorAgent(prompt: string, walletAddress?: string) {
7381
// Process intent
74-
const res = await this.IntentAgent(prompt, walletAddress);
75-
82+
const decomposer = await this.QueryDecomposer(prompt);
83+
const decomposed: string[] = JSON.parse(
84+
decomposer.choices[0].message.content,
85+
);
86+
console.log(decomposed);
87+
const res = await this.IntentAgent(decomposed, walletAddress);
88+
console.log(res, 'this is intent agent response');
7689
// Make decision based on intent
7790
const finalAnswer = await this.DecisionMakingAgent(res, prompt);
7891
console.log('Final Answer:', finalAnswer);
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
export default "You are the Query Decomposer.\n\n\
2+
\n\
3+
Your task is to analyze the user's query and break it into multiple subqueries **only if necessary**, following strict rules.\n\n\
4+
\n\
5+
### **Rules for Decomposition:**\n\
6+
1️ **Determine if decomposition is needed**\n\
7+
- If the query requires multiple tools or separate logical steps, split it into subqueries.\n\
8+
- If a single tool can handle the query, return it as is.\n\n\
9+
\n\
10+
2️ **Subquery Format (Strict JSON Array)**\n\
11+
- Each subquery must be **clear, self-contained, and executable**.\n\
12+
- Maintain **logical order** for execution.\n\
13+
\n\
14+
### **Output Format:** DO NOT STRAY FROM THE RESPONSE FORMAT (Array or single string) RETURN ONLY THE RESPONSE FORMAT \n\
15+
- If decomposition **is needed**, return an array of strings: ELSE return an array with a single string\n\
16+
";
Lines changed: 71 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,85 @@
1-
/**
2-
* Prompt template for the intent agent (CoinSage)
3-
*
4-
* This template defines the behavior and response format for the AI assistant
5-
* specifically focused on Sui blockchain related queries. The agent follows
6-
* a structured decision-making process to handle user queries:
7-
*
8-
* 1. Self-Assessment: Determines if direct response is possible
9-
* 2. Tool Selection: Chooses appropriate tool if direct response isn't possible
10-
* 3. Information Gathering: Identifies when additional context is needed
11-
*
12-
* Response Format:
13-
* {
14-
* success: boolean, // true if query can be handled without additional info
15-
* selected_tool: string, // name of chosen tool, or null if not needed
16-
* response: string, // direct response if available, null otherwise
17-
* needs_additional_info: boolean, // true if more context is required
18-
* additional_info_required: string[], // list of required information
19-
* tool_arguments: any[] // arguments needed for the selected tool
20-
* }
21-
*/
221
const intent_query = `You are an intelligent assistant called AtomaSage.
232
YOUR NAME IS ATOMASAGE
243
25-
You are tasked with ONLY answering questions that could be related to the sui blockchain (directly or indirectly). Follow these steps to respond effectively:
4+
You are tasked with ONLY answering questions that could be related to the Sui blockchain (directly or indirectly). Follow these steps to respond effectively:
265
27-
Self-Assessment: First, determine if you can answer the user's question directly based on your current knowledge and capabilities.
6+
---
287
29-
Tool Selection: If you cannot answer the question directly, review the following list of tools:
8+
### Step 1: **Self-Assessment**
9+
- Determine if you can answer each user query directly based on your knowledge.
10+
- If yes, generate a response.
11+
12+
### Step 2: **Tool Selection**
13+
- If a query **cannot** be answered directly, review the available tools:
3014
3115
\${toolsList}
3216
33-
Select the most appropriate tool that can help answer the user's query. Clearly specify the chosen tool.
17+
- **Each subquery MUST have its own separate tools and tool arguments.**
18+
- Do NOT share tools between subqueries unless absolutely necessary.
19+
20+
### Step 3: **Needs Info Handling**
21+
- If neither your knowledge nor the available tools can fully answer a query, respond with a "Needs Info Error" and specify what additional information is required.
22+
23+
---
24+
25+
### **Response Format (Each Subquery Must Be Separate)**
26+
Respond with an **array** where each entry corresponds to a single subquery. Each subquery MUST have its own selected tools and tool arguments.
27+
YOU ARE ONLY ALLOWED TO RESPOND WITH THE JSON FORMAT BELOW, NEVER RETURN A SINGLE STRING
28+
\`\`\`json
29+
[
30+
{
31+
"subquery": string, // The specific subquery being answered
32+
"success": boolean, // Set to true ONLY if needs_additional_info is false
33+
"selected_tools": null | string[], // array of single element, selected tool for that subquery
34+
"response": null | string, // Direct response if available
35+
"needs_additional_info": boolean, // True if more context is required
36+
"additional_info_required": null | string[], // List of required information
37+
"tool_arguments": null | string[] // Arguments for selected tools (specific to this subquery)
38+
}
39+
]
40+
\`\`\`
41+
42+
---
43+
44+
### **Important Rules**
45+
46+
- **Each subquery must be handled separately.**
47+
- **Tools must be specific to each subquery.**
48+
- **DO NOT** combine tools across multiple subqueries unless required.
49+
- **DO NOT** combine tool arguments, only add tool arguments relevant to that query.
50+
- **DO NOT** select unnecessary tools.
51+
- **DO NOT** deviate from the response format.
52+
53+
---
3454
35-
Needs Info Error: If neither your knowledge nor the available tools can provide a solution, respond with a "Needs Info Error," indicating that additional information or context is required to proceed.
55+
### **Example Response**
56+
#### User Query:
57+
*"What is Move, and can you check the latest transactions on Sui?"*
3658
37-
This is the response format
38-
[{
39-
"success":boolean,//set it to true, only if needs_additional info is false
40-
"selected_tool":null | string,
41-
"response":null | string,
42-
"needs_additional_info": boolean,
43-
"additional_info_required": null | string array
44-
"tool_arguments": null | array //based on user query .and tool input,
45-
}]
59+
#### Correct Response:
60+
\`\`\`json
61+
[
62+
{
63+
"subquery": "What is Move?",
64+
"success": true,
65+
"selected_tools": null,
66+
"response": "Move is a programming language designed for the Sui blockchain...",
67+
"needs_additional_info": false,
68+
"additional_info_required": null,
69+
"tool_arguments": null
70+
},
71+
{
72+
"subquery": "Can you check the latest transactions on Sui?",
73+
"success": false,
74+
"selected_tools": ["SuiExplorerAPI"],
75+
"response": null,
76+
"needs_additional_info": false,
77+
"additional_info_required": null,
78+
"tool_arguments": [5]
79+
}
80+
]
81+
\`\`\`
4682
47-
DO NOT UNDER ANY CIRCUMSTANCES STRAY FROM THE RESONSE FORMAT
48-
RESPOND WITH ONLY THE RESONSE FORMAT
49-
ADD PARAMS IN THE EXACT ORDER, PUT JUST THE VALUE IN THE ARRAY
5083
`;
5184

5285
export default intent_query;

packages/sui-agent/src/utils/index.ts

Lines changed: 62 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { randomUUID } from 'crypto';
22
import { AtomaSDK } from 'atoma-sdk';
33
import Atoma from '../config/atoma';
44
import Tools from './tools';
5-
import { ToolArgument } from '../@types/interface';
5+
import { IntentAgentResponse, ToolArgument } from '../@types/interface';
66

77
/**
88
* Utility class for processing agent responses and making decisions
@@ -38,19 +38,49 @@ class Utils {
3838
async processQuery(
3939
AtomaInstance: Atoma,
4040
query: string,
41-
selectedTool: string | null,
42-
toolArguments: ToolArgument[] = [],
41+
intentResponses: IntentAgentResponse[],
4342
) {
4443
try {
45-
if (!selectedTool) {
44+
if (!intentResponses || intentResponses.length === 0) {
4645
return this.finalAnswer(
4746
AtomaInstance,
48-
'No tool selected for the query',
47+
'No tools selected for the query',
4948
query,
5049
);
5150
}
5251

53-
return this.executeTools(selectedTool, toolArguments, AtomaInstance);
52+
let aggregatedResults = '';
53+
54+
for (const response of intentResponses) {
55+
const { selected_tools, tool_arguments } = response;
56+
57+
if (!selected_tools?.length) {
58+
continue; // Skip if no tool selected
59+
}
60+
console.log(selected_tools, 'selected tools /...');
61+
// Execute the tool and append its result
62+
const result = await this.executeTools(selected_tools, tool_arguments);
63+
64+
// Aggregate results (you might want to customize this based on your needs)
65+
aggregatedResults += result + '\n';
66+
}
67+
68+
// If no tools were successfully executed
69+
if (!aggregatedResults) {
70+
return this.finalAnswer(
71+
AtomaInstance,
72+
'No valid tools were executed for the query',
73+
query,
74+
);
75+
}
76+
77+
// Return final answer with aggregated results
78+
return this.finalAnswer(
79+
AtomaInstance,
80+
aggregatedResults.trim(),
81+
query,
82+
intentResponses.map((r) => r.selected_tools).join(', '),
83+
);
5484
} catch (error: unknown) {
5585
console.error('Error processing query:', error);
5686
return handleError(error, {
@@ -61,14 +91,28 @@ class Utils {
6191
}
6292
}
6393

64-
/**
65-
* Format final answer
66-
* @param response - Raw response
67-
* @param query - Original query
68-
* @param tools - Tools used
69-
* @returns Formatted response
70-
* @private
71-
*/
94+
private async executeTools(
95+
selected_tool: string[],
96+
args: ToolArgument[] | null,
97+
) {
98+
const tool = this.tools
99+
.getAllTools()
100+
.find((t) => t.name.trim() === selected_tool[0]);
101+
102+
if (!tool) {
103+
throw new Error(`Tool ${selected_tool} not found`);
104+
}
105+
106+
try {
107+
const toolArgs = args || [];
108+
const result = await tool.process(...toolArgs);
109+
return result;
110+
} catch (error: unknown) {
111+
console.error('Error executing tool:', error);
112+
throw error; // Let the main processQuery handle the error
113+
}
114+
}
115+
72116
private async finalAnswer(
73117
AtomaInstance: Atoma,
74118
response: string,
@@ -80,62 +124,15 @@ class Utils {
80124
.replace('${response}', response)
81125
.replace('tools', `${tools || null}`);
82126

83-
// const finalAns = await new AtomaSDK({bearerAuth:'bearer auth here'}).chat.create({
84-
// messages: [
85-
// {role:"assistant",content:finalPrompt},
86-
// // { role: "user", content: query }
87-
// ],
88-
// model: "meta-llama/Llama-3.3-70B-Instruct"
89-
// });
90-
91127
const finalAns = await AtomaInstance.atomaChat([
92128
{ role: 'assistant', content: finalPrompt },
93129
{ role: 'user', content: query },
94130
]);
95-
console.log('new one');
96-
97-
// const finalAns = await atomaChat(this.sdk, [
98-
// {
99-
// content: finalPrompt,
100-
// role: 'assistant',
101-
// },
102-
// ]);
103-
const res = finalAns.choices[0].message.content;
104-
console.log(finalPrompt);
105-
return JSON.parse(res);
106-
}
107-
108-
/**
109-
* Executes selected tools with provided arguments
110-
* @param selected_tool - Name of the tool to execute
111-
* @param args - Arguments to pass to the tool
112-
* @returns Processed tool response
113-
* @private
114-
*/
115-
private async executeTools(
116-
selected_tool: string,
117-
args: ToolArgument[] | null,
118-
AtomaInstance: Atoma,
119-
) {
120-
const tool = this.tools.getAllTools().find((t) => t.name === selected_tool);
121-
console.log('Selected tool:', selected_tool);
122-
console.log('Tool arguments:', args);
123131

124-
if (!tool) {
125-
throw new Error(`Tool ${selected_tool} not found`);
126-
}
127-
128-
try {
129-
const toolArgs = args || [];
130-
const result = await tool.process(...toolArgs);
131-
return await this.finalAnswer(AtomaInstance, result, '', selected_tool);
132-
} catch (error: unknown) {
133-
console.error('Error executing tool:', error);
134-
return handleError(error, {
135-
reasoning: `The system encountered an issue while executing the tool ${selected_tool}`,
136-
query: `Attempted to execute ${selected_tool} with arguments: ${JSON.stringify(args)}`,
137-
});
138-
}
132+
const res = finalAns.choices[0].message.content;
133+
const parsedRes = JSON.parse(res);
134+
console.log(parsedRes, 'parsed response');
135+
return parsedRes;
139136
}
140137
}
141138

0 commit comments

Comments
 (0)