Skip to content

Duplicate tool calls during conversation - Need better approach than manual caching #6

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
umerfarok opened this issue Apr 10, 2025 · 0 comments

Comments

@umerfarok
Copy link

umerfarok commented Apr 10, 2025

Current Issue: I'm experiencing multiple identical tool calls being sent to my backend API during a single conversation. My backend receives repeated calls with the same parameters throughout a conversation, leading to duplicate processing.

The issue here is on each message it sends call to my backend that causes tools calls duplication if we have something in configuration the agent only send req to api call when a tool the functions we provide to that agent configuration is called or how can i manage this

`app.post('/api/tools', async (req, res) => {
try {
logger.info('POST /api/tools endpoint called');

// Enhanced logging with request identification
const requestId = `req-${Date.now()}-${Math.random().toString(36).substring(2, 7)}`;
logger.debug(`Processing request ${requestId}`, { body: req.body });

// Check if this is a handshake or health check request (empty or minimal payload)
if (!req.body || (Object.keys(req.body).length === 0)) {
  logger.info('Received handshake/health check request');
  return res.json({
    status: 'ok',
    message: 'Server is operational'
  });
}

// Check if this is a status update message
if (req.body.message && req.body.message.type) {
  if (req.body.message.type === 'status-update') {
    logger.info(`Status update received: ${req.body.message.status}`);
    logger.info(`Reason: ${req.body.message.endedReason || 'N/A'}`);
    return res.json({
      results: [{
        result: `Received status update: ${req.body.message.status}`
      }]
    });
  } else if (req.body.message.type === 'end-of-call-report') {
    logger.info(`End of call report received`);
    
    // Log details of the call report for analysis
    if (req.body.message.analysis) {
      logger.info('Call analysis', {
        summary: req.body.message.analysis.summary,
        success: req.body.message.analysis.successEvaluation
      });
    }
    
    return res.json({
      results: [{
        result: `Received end of call report`
      }]
    });
  }
}

// Extract tool calls from the request body - handling different formats
const { toolCalls, format } = extractToolCallsWithFormat(req.body);

if (!toolCalls || !Array.isArray(toolCalls) || toolCalls.length === 0) {
  logger.warn(`No valid tool calls found in request ${requestId}`, { body: req.body });
  return res.status(400).json({ 
    error: 'No valid tool calls found in request',
    requestId: requestId 
  });
}

logger.info(`Found ${toolCalls.length} tool calls in ${format} format`, { requestId });

// Handle regular tool calls
const results = await Promise.all(toolCalls.map(async (toolCall) => {
  // Check for duplicate tool calls before processing
  if (isDuplicateToolCall(toolCall)) {
    // Return cached response for duplicate call
    return {
      toolCallId: toolCall.id,
      result: JSON.stringify({
        status: 'duplicate',
        message: 'This tool call was already processed recently.'
      })
    };
  }
  
  const result = await handleToolCall(toolCall);
  return {
    toolCallId: toolCall.id,
    result: result
  };
}));

logger.info(`Response sent for request ${requestId}`, { results });
res.json({ results });

} catch (error) {
logger.error('Error processing tool call', { error: error.message, stack: error.stack });
// Send a properly formatted response even on error
res.json({
results: [{
toolCallId: req.body.toolCallId || "unknown",
result: JSON.stringify({
status: 'error',
message: error.message
})
}]
});
}
});`

`import { CreateAssistantDTO } from "@vapi-ai/web/dist/api";
export const characterAssistant: CreateAssistantDTO = {
name: "Mohammad",
model: {
provider: "openai",
model: "gpt-3.5-turbo",
temperature: 0.7,
messages: [
{
role: "system",
content: "You are a helpful, polite restaurant assistant named Mohammad for a premium Middle Eastern dining restaurant called Khayal. Handle guest interactions professionally in both English (EN) and Arabic (AR), including table reservations, order placement, and menu navigation. If the user speaks Arabic, respond in Arabic. If the user speaks English, respond in English. Always provide clear options and summarize selections. If unsure, ask clarifying questions. You should use the makeReservation function when a customer wants to book a table, the addToOrder function when they want to order food, and the checkAvailability function to verify if tables are available for specific dates and times."
}
],
tools: [
{
type: "function",
function: {
name: "makeReservation",
description: "Books a table reservation for customers at the restaurant",
parameters: {
type: "object",
properties: {
guestCount: {
type: "number",
description: "Number of guests for the reservation",
},
date: {
type: "string",
description: "Date of the reservation (YYYY-MM-DD format)",
},
time: {
type: "string",
description: "Time of the reservation (HH:MM format)",
},
name: {
type: "string",
description: "Name under which the reservation is made",
},
phoneNumber: {
type: "string",
description: "Contact phone number for the reservation",
},
specialRequests: {
type: "string",
description: "Any special requests for the reservation",
},
},
required: ["guestCount", "date", "time", "name", "phoneNumber"],
},
},
async: false
},
{
type: "function",
function: {
name: "addToOrder",
description: "Adds items to a customer's order",
parameters: {
type: "object",
properties: {
items: {
type: "array",
items: {
type: "object",
properties: {
name: {
type: "string",
description: "Name of the menu item",
},
quantity: {
type: "number",
description: "Quantity of the item",
},
specialInstructions: {
type: "string",
description: "Special instructions for preparing the item",
},
},
required: ["name", "quantity"],
},
description: "List of items to add to the order",
},
},
required: ["items"],
},
},
async: false
},
{
type: "function",
function: {
name: "checkAvailability",
description: "Checks if tables are available for a specific date and time",
parameters: {
type: "object",
properties: {
guestCount: {
type: "number",
description: "Number of guests",
},
date: {
type: "string",
description: "Date to check availability (YYYY-MM-DD format)",
},
time: {
type: "string",
description: "Time to check availability (HH:MM format)",
},
},
required: ["guestCount", "date", "time"],
},
},
async: false
}
],
},

// Add server configuration for function handling
server: {
url: "https://1dd0-2a09-bac1-5b20-28-00-19b-fe.ngrok-free.app/api/tools",
timeoutSeconds: 20,
secret: "mock-secret-key"
},
serverUrl: "https://1dd0-2a09-bac1-5b20-28-00-19b-fe.ngrok-free.app/api/tools",
serverUrlSecret:"mock",

// Move idle messages to messagePlan instead of the root-level messages property
messagePlan: {
idleMessages: [
"Please wait while I process your request...",
"I'm checking our system for that information...",
"Just a moment while I update our records..."
],
idleMessageMaxSpokenCount: 1,
idleTimeoutSeconds: 5
},

voice: {
provider: "11labs",
voiceId: "paula",
},

firstMessage: "Thank you for calling Khayal Restaurant. This is Mohammad, your Arabic and English scheduling assistant. How may I help you today?",
};
`

The Problem: While this manual caching works somewhat, I have several concerns:

This feels like a workaround rather than the right solution
I'm not sure if 6 seconds is the right deduplication window
The manual cache management is not ideal and could lead to memory issues
I shouldn't need to handle this level of deduplication on my backend
My Configuration: I've configured my agent to call my backend tools at http://localhost:3000/api/tools and implemented handlers for various tool functions (makeReservation, addToOrder, checkAvailability). The agent correctly formats and sends tool calls, but I'm receiving duplicate requests.

Questions:

How can i configure in some way so my backend is only called when a tool is called?
You can ignore these caching things i have mentioned if those are unrelated.
Is there a configuration setting to prevent the agent from sending duplicate tool calls?
Is there a recommended backend approach for handling this situation that doesn't require manual caching?
Is this expected behavior, and if so, what's the recommended pattern for backend implementation?
Could this be related to how I'm responding to tool calls?
can we have a sample configuration of a sample node js server and agent config as well

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant