Skip to content

Commit 944e402

Browse files
committed
UI components implemented for zayo tool results, Supervisor agent bug fixes..
1 parent 3d17937 commit 944e402

File tree

7 files changed

+1045
-105
lines changed

7 files changed

+1045
-105
lines changed

src/agent/agent.service.ts

Lines changed: 70 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -129,33 +129,76 @@ export class AgentService {
129129
event.name.length > 0
130130
) {
131131
if (data && typeof data === 'object' && 'output' in data) {
132-
// this.logger.log(`Tool Result: ${JSON.stringify(data)}`);
132+
this.logger.log(
133+
`Tool ${event.name} - Raw data.output type: ${typeof data.output}`,
134+
);
135+
this.logger.log(
136+
`Tool ${event.name} - Raw data.output: ${JSON.stringify(data.output).substring(0, 500)}`,
137+
);
138+
133139
let toolContent = data.output;
140+
134141
// If output is a string that looks like JSON, try to parse it.
135-
// If it is already an object (LangGraph might do this), use it directly.
136142
if (typeof toolContent === 'string') {
137143
try {
138-
// Some tools return "Content: {...}" or just "{...}"
139144
if (
140145
toolContent.trim().startsWith('{') ||
141146
toolContent.trim().startsWith('[')
142147
) {
143148
toolContent = JSON.parse(toolContent);
149+
this.logger.log(
150+
`Tool ${event.name} - Parsed string to object`,
151+
);
144152
}
145153
} catch (e) {
146-
// keep as string if parse fails
154+
this.logger.log(
155+
`Tool ${event.name} - Failed to parse: ${e.message}`,
156+
);
147157
}
148158
}
149159

150-
// If content is wrapped in "content" property (as some MCP tools do)
160+
// Handle Zayo MCP structure: {content: [], structuredContent: {...data...}, isError: false}
161+
// OR: {content: [...], metadata: {...}, isError: false}
151162
if (
152163
toolContent &&
153164
typeof toolContent === 'object' &&
154-
'content' in toolContent
165+
'isError' in toolContent
166+
) {
167+
this.logger.log(
168+
`Tool ${event.name} - Detected Zayo MCP structure`,
169+
);
170+
// Check if structuredContent exists and has data (new Zayo format)
171+
if (
172+
'structuredContent' in toolContent &&
173+
toolContent.structuredContent
174+
) {
175+
this.logger.log(
176+
`Tool ${event.name} - Using structuredContent`,
177+
);
178+
toolContent = toolContent.structuredContent;
179+
}
180+
// Otherwise use content array (old format)
181+
else if ('content' in toolContent) {
182+
this.logger.log(`Tool ${event.name} - Using content array`);
183+
toolContent = toolContent.content;
184+
}
185+
} else if (
186+
toolContent &&
187+
typeof toolContent === 'object' &&
188+
'content' in toolContent &&
189+
!('isError' in toolContent)
155190
) {
191+
// Generic MCP structure without isError (TC tools)
192+
this.logger.log(
193+
`Tool ${event.name} - Detected generic content wrapper`,
194+
);
156195
toolContent = toolContent.content;
157196
}
158197

198+
this.logger.log(
199+
`Tool ${event.name} - Final content type: ${typeof toolContent}, isArray: ${Array.isArray(toolContent)}`,
200+
);
201+
159202
accumulatedOutput += ` {{${event.name}}} `;
160203
tool_results.push({
161204
toolName: event.name,
@@ -189,11 +232,31 @@ export class AgentService {
189232
this.memoryService.clearCache(sessionId);
190233

191234
if (!res.writableEnded) {
192-
const friendlyError =
235+
// Determine user-friendly error message based on error type
236+
let friendlyError =
193237
"I'm sorry, an unexpected error occurred. Please try again.";
238+
239+
if (error.message?.includes('Input is too long')) {
240+
friendlyError =
241+
'The conversation history is too long. Please start a new conversation.';
242+
} else if (error.message?.includes('validation')) {
243+
friendlyError =
244+
'There was an issue with the data format. Please try rephrasing your request.';
245+
} else if (error.message?.includes('Branch condition')) {
246+
friendlyError =
247+
'There was a routing error. Please try again or rephrase your request.';
248+
} else if (error.$metadata?.httpStatusCode === 400) {
249+
friendlyError =
250+
'Invalid request to the AI service. Please try a different query.';
251+
} else if (error.$metadata?.httpStatusCode >= 500) {
252+
friendlyError =
253+
'The AI service is temporarily unavailable. Please try again later.';
254+
}
255+
194256
const errorMessage = JSON.stringify({
195257
type: 'error',
196258
content: friendlyError,
259+
details: error.message, // Include technical details for debugging
197260
});
198261
res.write(`event: error\ndata: ${errorMessage}\n\n`);
199262
serverAbortController.abort();

src/agent/llm/agents/prompts/zayo_system_prompt.ts

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,13 @@ In general, when a customer / user makes a request:
99
3. Request only the necessary information for each step.
1010
4. Return a clear, helpful final answer.
1111
12+
IMPORTANT Communication Guidelines:
13+
- DO NOT narrate which tools you are using (e.g., "I'll use the get_all_tickets tool...").
14+
- DO NOT mention technical tool names in your responses to users.
15+
- When using time/date tools (get_current_time, get_calendar_range), do so silently without mentioning them.
16+
- Focus on providing direct, helpful answers based on the data you retrieve.
17+
- Be conversational and natural in your responses.
18+
1219
Only call tools when needed. Compose answers from tool outputs. If you cannot answer, say so clearly.
1320
Never hallucinate customer data.
1421
`;
@@ -23,9 +30,11 @@ When the user refers to dates or times using relative terms like:
2330
you must first retrieve the current date, time, and timezone context before interpreting
2431
their request.
2532
26-
ALWAYS use the "get_current_time" tool to establish the "now".
27-
ALWAYS use the "get_calendar_range" tool to calculate relative periods like "last week", "this month", "yesterday", etc.
28-
DO NOT attempt to calculate dates purely from your internal knowledge.
33+
CRITICAL - YOU MUST USE THESE TOOLS:
34+
- ALWAYS use the \`get_current_time\` tool to establish the "now" - this is REQUIRED for any date/time query.
35+
- ALWAYS use the \`get_calendar_range\` tool to calculate relative periods like "last week", "this month", "yesterday", etc.
36+
- DO NOT attempt to calculate dates from your internal knowledge - you MUST call these tools.
37+
- However, DO NOT mention to the user that you are calling these tools. Use them silently in the background.
2938
3039
Once you have the tool outputs, use the specific ISO dates provided to query the other tools.
3140
@@ -84,6 +93,13 @@ In general, when a customer / user makes a request:
8493
3. Request only the necessary information for each step.
8594
4. Return a clear, helpful final answer.
8695
96+
IMPORTANT Communication Guidelines:
97+
- DO NOT narrate which tools you are using (e.g., "I'll use the get_all_tickets tool...").
98+
- DO NOT mention technical tool names in your responses to users.
99+
- When using time/date tools (get_current_time, get_calendar_range), do so silently without mentioning them.
100+
- Focus on providing direct, helpful answers based on the data you retrieve.
101+
- Be conversational and natural in your responses.
102+
87103
Only call tools when needed. Compose answers from tool outputs. If you cannot answer, say so clearly.
88104
Never hallucinate customer data.
89105
`;
@@ -339,6 +355,13 @@ In general, when a customer / user makes a request:
339355
3. Request only the necessary information for each step.
340356
4. Return a clear, helpful final answer.
341357
358+
IMPORTANT Communication Guidelines:
359+
- DO NOT narrate which tools you are using (e.g., "I'll use the get_address_locations tool...").
360+
- DO NOT mention technical tool names in your responses to users.
361+
- When using time/date tools (get_current_time, get_calendar_range), do so silently without mentioning them.
362+
- Focus on providing direct, helpful answers based on the data you retrieve.
363+
- Be conversational and natural in your responses.
364+
342365
Only call tools when needed. Compose answers from tool outputs.
343366
`;
344367

src/agent/llm/agents/supervisor.agent.ts

Lines changed: 71 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -44,30 +44,80 @@ STRICT OUTPUT RULES:
4444
};
4545

4646
return async (state: typeof GraphState.State) => {
47-
// 1. Strict Loop Prevention (Deterministic)
48-
// If the last message is from a worker, we stop immediately.
49-
// This prevents the Supervisor from "talking" (streaming "FINISH") or trying to route again.
50-
const lastMessage = state.messages[state.messages.length - 1];
51-
if (
52-
lastMessage &&
53-
(lastMessage.name === 'TopcoderAgent' ||
54-
lastMessage.name === 'ZayoServiceAgent' ||
55-
lastMessage.name === 'ZayoQuoteAgent')
56-
) {
57-
return { next: 'FINISH' };
58-
}
47+
// 1. Strict Loop Prevention (Deterministic)
48+
// If the last message is from a worker, we stop immediately.
49+
const lastMessage = state.messages[state.messages.length - 1];
50+
if (
51+
lastMessage &&
52+
(lastMessage.name === 'TopcoderAgent' ||
53+
lastMessage.name === 'ZayoServiceAgent' ||
54+
lastMessage.name === 'ZayoQuoteAgent')
55+
) {
56+
return { next: 'FINISH' };
57+
}
58+
59+
const messages = [new SystemMessage(systemPrompt), ...state.messages];
60+
const llmWithTool = supervisorLlm.bindTools([routeTool]);
61+
62+
// Retry loop for invalid routing
63+
const MAX_RETRIES = 2;
64+
let attempt = 0;
65+
let nextDestination: string | undefined;
5966

60-
const messages = [new SystemMessage(systemPrompt), ...state.messages];
61-
const llmWithTool = supervisorLlm.bindTools([routeTool]);
62-
const response = await llmWithTool.invoke(messages);
67+
while (attempt < MAX_RETRIES) {
68+
attempt++;
6369

64-
const toolCall = response.tool_calls?.[0];
65-
66-
// If the LLM decided to answer directly (no tool call), or if it wants to finish:
67-
if (!toolCall) {
68-
// Return the supervisor's text response as a message so the user sees it
70+
try {
71+
const response = await llmWithTool.invoke(messages);
72+
const toolCall = response.tool_calls?.[0];
73+
74+
// If the LLM decided to answer directly (no tool call):
75+
if (!toolCall) {
76+
// Return the supervisor's text response as a message
6977
return { messages: [response], next: 'FINISH' };
78+
}
79+
80+
// Extract the next destination from tool call
81+
nextDestination = toolCall.args?.next;
82+
83+
// If valid destination found, break out of retry loop
84+
if (
85+
nextDestination &&
86+
[
87+
'TopcoderAgent',
88+
'ZayoServiceAgent',
89+
'ZayoQuoteAgent',
90+
'FINISH',
91+
].includes(nextDestination)
92+
) {
93+
return { next: nextDestination };
94+
}
95+
96+
// Invalid destination - log and retry
97+
console.warn(
98+
`[Supervisor] Attempt ${attempt}/${MAX_RETRIES}: Invalid 'next' value: ${nextDestination}`,
99+
);
100+
101+
if (attempt < MAX_RETRIES) {
102+
// Add a clarification message to help the LLM
103+
messages.push(
104+
new SystemMessage(
105+
`ERROR: You must call the 'route' tool with a valid 'next' value: TopcoderAgent, ZayoServiceAgent, ZayoQuoteAgent, or FINISH. Please try again.`,
106+
),
107+
);
108+
}
109+
} catch (error) {
110+
console.error(`[Supervisor] Error on attempt ${attempt}:`, error);
111+
if (attempt === MAX_RETRIES) {
112+
throw error; // Re-throw on final attempt
113+
}
70114
}
71-
return { next: toolCall.args.next };
115+
}
116+
117+
// All retries exhausted - default to FINISH
118+
console.error(
119+
`[Supervisor] All ${MAX_RETRIES} attempts failed. Invalid or missing 'next' value: ${nextDestination}. Defaulting to FINISH.`,
120+
);
121+
return { next: 'FINISH' };
72122
};
73123
};

src/agent/llm/tools/zayo-mcp.ts

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -131,14 +131,59 @@ export class ZayoMcpClient {
131131

132132
async callTool(name: string, args: any): Promise<string> {
133133
if (!this.isEnabled) throw new Error('Zayo MCP is disabled');
134-
this.logger.log(`Calling tool: ${name}`);
135-
const result = await this.sendJsonRpc('tools/call', {
136-
name,
137-
arguments: args || {},
138-
});
139-
const content = result?.content?.[0];
140-
if (content?.type === 'text') return content.text;
141-
return JSON.stringify(result);
134+
this.logger.log(`Calling tool: ${name}\n${JSON.stringify(args)}`);
135+
136+
try {
137+
const result = await this.sendJsonRpc('tools/call', {
138+
name,
139+
arguments: args || {},
140+
});
141+
142+
if ('error' in result) {
143+
const errorMsg = JSON.stringify(result.error);
144+
// Truncate massive error messages (e.g., validation errors with 100k+ lines)
145+
if (errorMsg.length > 2000) {
146+
const truncated = errorMsg.substring(0, 2000);
147+
this.logger.error(
148+
`Tool ${name} returned large error (${errorMsg.length} chars), truncated to 2000 chars`,
149+
);
150+
// Return error as JSON so LLM can read it
151+
return JSON.stringify({
152+
error: true,
153+
message: 'Tool returned a large error response that was truncated',
154+
details: truncated,
155+
note: 'Error message was too long and has been truncated',
156+
});
157+
}
158+
// Return error as JSON instead of throwing
159+
this.logger.error(`Tool ${name} error:`, result.error);
160+
return JSON.stringify({
161+
error: true,
162+
message: 'Tool execution failed',
163+
details: result.error,
164+
});
165+
}
166+
167+
const content = result.result.content || [];
168+
const serialized = JSON.stringify(content);
169+
170+
// Warn about large successful responses
171+
if (serialized.length > 100000) {
172+
this.logger.warn(
173+
`Tool ${name} returned large response (${serialized.length} chars), may cause context issues`,
174+
);
175+
}
176+
177+
return serialized;
178+
} catch (error: any) {
179+
// Catch network errors or other exceptions
180+
this.logger.error(`Tool ${name} exception:`, error);
181+
return JSON.stringify({
182+
error: true,
183+
message: 'An error occurred while calling the tool',
184+
details: error.message,
185+
});
186+
}
142187
}
143188

144189
private async sendJsonRpc(method: string, params: any): Promise<any> {

0 commit comments

Comments
 (0)