M
MastraAIโ€ข2mo ago
Rafik Belkadi

Tool calling & Structured output

When calling my agent through the mastra client sdk, it does not use the tools i provided, with the same prompt in the chat in the playground it does call the tool, why ?
const tool = await this._mastraClientService
.getEmailRespondAgent()
.getTool('googleCalendarTool')
.catch((error) => {
this._logger.warn('Failed to get google calendar tool', error);
return null;
});
const agent = await this._mastraClientService.getEmailRespondAgent()
const tool = await this._mastraClientService
.getEmailRespondAgent()
.getTool('googleCalendarTool')
.catch((error) => {
this._logger.warn('Failed to get google calendar tool', error);
return null;
});
const agent = await this._mastraClientService.getEmailRespondAgent()
after debuging, tool is not found 404 agent definition :
export const emailRespondAgent = new Agent({
name: 'Email Respond Agent',
tools: { googleCalendarTool },
});


export const googleCalendarTool = createTool({
id: 'googleCalendarTool',
description:
'A tool to view a user calendar, schedule, events and availability',

export const emailRespondAgent = new Agent({
name: 'Email Respond Agent',
tools: { googleCalendarTool },
});


export const googleCalendarTool = createTool({
id: 'googleCalendarTool',
description:
'A tool to view a user calendar, schedule, events and availability',

very frustrating and blocking, any workaround ?
35 Replies
_roamin_
_roamin_โ€ข2mo ago
Hey @Rafik Belkadi ! Looks like getTool is calling an endpoint that does not exist. As a workaround, you can gets the tools this way for now:
const agent = mastraClient.getAgent("weatherAgent");
const details = await agent.details()
console.log("agent tools:", details.tools);
const agent = mastraClient.getAgent("weatherAgent");
const details = await agent.details()
console.log("agent tools:", details.tools);
Opened this PR to add the missing endpoint: https://github.com/mastra-ai/mastra/pull/7250
Rafik Belkadi
Rafik BelkadiOPโ€ข2mo ago
Thanks for the PR @Romain :), although getToolmethod was just for debugging, the issue was the agent was not calling any tools when called from the client sdk alright i found out that when adding threadId & resourceId it does not call tools, after removing these two it does call the tools means i can't use memory with that
const response = await this._mastraClientService.getEmailRespondAgent().generate({
messages: [
{
role: 'system',
content: systemPrompt,
},
{
role: 'user',
content: userPrompt,
},
],
// Mastra uses threadId for memory scoping
threadId: `${user.id}_${contactEmail}`,
// Resource ID for contact-specific memory
resourceId: contactEmail,
const response = await this._mastraClientService.getEmailRespondAgent().generate({
messages: [
{
role: 'system',
content: systemPrompt,
},
{
role: 'user',
content: userPrompt,
},
],
// Mastra uses threadId for memory scoping
threadId: `${user.id}_${contactEmail}`,
// Resource ID for contact-specific memory
resourceId: contactEmail,
also even when tools are called, the toolCalls & toolResults`arrays are always empty in the response
Mastra Triager
Mastra Triagerโ€ข2mo ago
๐Ÿ“ Created GitHub issue: https://github.com/mastra-ai/mastra/issues/7302
GitHub
[DISCORD:1412091272453161082] Tool calling & Structured output ยท I...
This issue was created from Discord post: https://discord.com/channels/1309558646228779139/1412091272453161082 When calling my agent through the mastra client sdk, it does not use the tools i provi...
_roamin_
_roamin_โ€ข2mo ago
Hey @Rafik Belkadi ! Could you share a small repro example please? Thanks ๐Ÿ™
Rafik Belkadi
Rafik BelkadiOPโ€ข2mo ago
@Romain want a repo or just my actual code ?
_roamin_
_roamin_โ€ข2mo ago
Doesn't have to be a repo, just enough code to reproduce the issue.
Rafik Belkadi
Rafik BelkadiOPโ€ข2mo ago
@Romain here it is : agent.ts:
import { Agent } from '@mastra/core/agent';
import { anthropic } from '@ai-sdk/anthropic';
import { Memory } from '@mastra/memory';
import { openai } from '@ai-sdk/openai';
import { MongoDBStore, MongoDBVector } from '@mastra/mongodb';
import { googleCalendarTool } from '../tools/google-calendar/google-calendar.tool';

const store = new MongoDBStore({
url: process.env.DATABASE_URL!,
dbName: 'recapp',
});

const vectorStore = new MongoDBVector({
uri: process.env.DATABASE_URL!,
dbName: 'recapp',
});

export const emailRespondAgent = new Agent({
name: 'Email Respond Agent',
tools: { googleCalendarTool },
instructions: prompt,
model: anthropic('claude-4-sonnet-20250514'),
memory: new Memory({
embedder: openai.embedding('text-embedding-3-small'),
storage: store,
vector: vectorStore,
options: {
semanticRecall: {
topK: 10, // Increased to get more context
messageRange: {
before: 3,
after: 3,
},
scope: 'resource', // Contact-specific memory
},
},
}),
});
import { Agent } from '@mastra/core/agent';
import { anthropic } from '@ai-sdk/anthropic';
import { Memory } from '@mastra/memory';
import { openai } from '@ai-sdk/openai';
import { MongoDBStore, MongoDBVector } from '@mastra/mongodb';
import { googleCalendarTool } from '../tools/google-calendar/google-calendar.tool';

const store = new MongoDBStore({
url: process.env.DATABASE_URL!,
dbName: 'recapp',
});

const vectorStore = new MongoDBVector({
uri: process.env.DATABASE_URL!,
dbName: 'recapp',
});

export const emailRespondAgent = new Agent({
name: 'Email Respond Agent',
tools: { googleCalendarTool },
instructions: prompt,
model: anthropic('claude-4-sonnet-20250514'),
memory: new Memory({
embedder: openai.embedding('text-embedding-3-small'),
storage: store,
vector: vectorStore,
options: {
semanticRecall: {
topK: 10, // Increased to get more context
messageRange: {
before: 3,
after: 3,
},
scope: 'resource', // Contact-specific memory
},
},
}),
});
tool.ts :
import { createTool } from '@mastra/core';
import { getEvents, getFreeTimeSlots } from './calendar.service';
import z from 'zod';

export const googleCalendarTool = createTool({
id: 'googleCalendarTool',
description:
'A tool to view a user calendar, schedule, events and availability',
inputSchema: z.object({
date: z
.string()
.refine(
(date) =>
new Date(date).toISOString() !==
'Invalid Date, please provide a valid date in ISO format',
)
.optional(),
accessToken: z.string().optional(),
}),
outputSchema: z.object({
events: z.array(
z.object({
title: z.string(),
description: z.string(),
start: z.string(),
end: z.string(),
}),
),
availability: z.array(
z.object({
start: z.string(),
end: z.string(),
}),
),
}),
execute: async ({ context, runtimeContext }) => {
const google_auth_token =
(runtimeContext.get('google_auth_token') as string) ??
context.accessToken;

const events = await getEvents(context.date, google_auth_token);
const availability = await getFreeTimeSlots(google_auth_token);

return { events, availability: availability.free };
},
});
import { createTool } from '@mastra/core';
import { getEvents, getFreeTimeSlots } from './calendar.service';
import z from 'zod';

export const googleCalendarTool = createTool({
id: 'googleCalendarTool',
description:
'A tool to view a user calendar, schedule, events and availability',
inputSchema: z.object({
date: z
.string()
.refine(
(date) =>
new Date(date).toISOString() !==
'Invalid Date, please provide a valid date in ISO format',
)
.optional(),
accessToken: z.string().optional(),
}),
outputSchema: z.object({
events: z.array(
z.object({
title: z.string(),
description: z.string(),
start: z.string(),
end: z.string(),
}),
),
availability: z.array(
z.object({
start: z.string(),
end: z.string(),
}),
),
}),
execute: async ({ context, runtimeContext }) => {
const google_auth_token =
(runtimeContext.get('google_auth_token') as string) ??
context.accessToken;

const events = await getEvents(context.date, google_auth_token);
const availability = await getFreeTimeSlots(google_auth_token);

return { events, availability: availability.free };
},
});
mastra/index.ts :
import './instrumentation';
import { Mastra } from '@mastra/core/mastra';
import { emailRespondAgent } from './agents/email-respond-agent';
import { MastraJwtAuth } from '@mastra/auth';

export const mastra = new Mastra({
agents: {
emailRespondAgent: emailRespondAgent,
},
server: {
experimental_auth: new MastraJwtAuth({
secret: process.env.MASTRA_JWT_SECRET,
}),
},
telemetry: {
serviceName: 'Reccap',
enabled: true,
sampling: {
type: 'always_on',
},
export: {
type: 'otlp',
// endpoint and headers will be picked up from env vars
endpoint: 'https://ingest.eu.signoz.cloud:443/v1/traces',
},
},
});
import './instrumentation';
import { Mastra } from '@mastra/core/mastra';
import { emailRespondAgent } from './agents/email-respond-agent';
import { MastraJwtAuth } from '@mastra/auth';

export const mastra = new Mastra({
agents: {
emailRespondAgent: emailRespondAgent,
},
server: {
experimental_auth: new MastraJwtAuth({
secret: process.env.MASTRA_JWT_SECRET,
}),
},
telemetry: {
serviceName: 'Reccap',
enabled: true,
sampling: {
type: 'always_on',
},
export: {
type: 'otlp',
// endpoint and headers will be picked up from env vars
endpoint: 'https://ingest.eu.signoz.cloud:443/v1/traces',
},
},
});
( by the way i don't see traces in the mastra playground on the agent, i just have the logs, is it normal or ? ) mastra-client.service.ts :
@Injectable()
export class MastraClientService {
client: MastraClient;
constructor(private readonly _configService: ConfigService) {
const jwt = this._configService.get<string>('MASTRA_JWT_TOKEN');
const baseUrl = isLocalEnv() ? 'http://localhost:4111' : MASTRA_URL;
this.client = new MastraClient({
baseUrl,
retries: 3,
headers: {
'X-Development': isLocalEnv() ? 'true' : 'false',
Authorization: `Bearer ${jwt}`,
},
});
}

getClient() {
return this.client;
}

getEmailRespondAgent() {
return this.client.getAgent('emailRespondAgent');
}
}
@Injectable()
export class MastraClientService {
client: MastraClient;
constructor(private readonly _configService: ConfigService) {
const jwt = this._configService.get<string>('MASTRA_JWT_TOKEN');
const baseUrl = isLocalEnv() ? 'http://localhost:4111' : MASTRA_URL;
this.client = new MastraClient({
baseUrl,
retries: 3,
headers: {
'X-Development': isLocalEnv() ? 'true' : 'false',
Authorization: `Bearer ${jwt}`,
},
});
}

getClient() {
return this.client;
}

getEmailRespondAgent() {
return this.client.getAgent('emailRespondAgent');
}
}
here i call the agent via the mastra client sdk : ```ts const response = await this._mastraClientService.getEmailRespondAgent().generate({ messages: [ { role: 'system', content: systemPrompt, }, { role: 'user', content: userPrompt, }, ], // // Mastra uses threadId for memory scoping threadId: ${user.id}_${contactEmail}`, // Resource ID for contact-specific memory resourceId: contactEmail, experimental_output: mySchema runtimeContext: { google_auth_token: googleAuthToken, }, }); ``` uppon removing threadId and resourceId, the tools are called ( i also removed experimental_output later cause it couldn't parse it..) tell me if you need anything else
_roamin_
_roamin_โ€ข2mo ago
Thanks @Rafik Belkadi !
Daniel Lew
Daniel Lewโ€ข2mo ago
@Rafik Belkadi I'm unable to reproduce this issue. What versions of mastra packages are you using? It would also help to get a repo for a minimal reproduction that can consistently reproduce it so we can fix it.
Rafik Belkadi
Rafik BelkadiOPโ€ข2mo ago
"@mastra/auth": "^0.1.2", "@mastra/client-js": "^0.11.2", "@mastra/core": "^0.14.1", "@mastra/libsql": "^0.0.1", "@mastra/memory": "^0.13.1", "@mastra/mongodb": "^0.13.3", i will tryto add a reproducible repo this weekend
Daniel Lew
Daniel Lewโ€ข2mo ago
It looks like you have some outdated packages, can you bump all the packages to the latest and see if it works. We ship lots of fixes every week, so chances are it might be fixed already!
Rafik Belkadi
Rafik BelkadiOPโ€ข2mo ago
I upgraded everything related to mastra and vercel ai sdk, i think it was because my resourceId was something like 'unknown' which i was defaulting to as a fallback. tool calls seem to work now thx ! still can't use structured outpout tho
Daniel Lew
Daniel Lewโ€ข2mo ago
okay great! Glad to hear that tool calls are working now. What do you mean you can't use structured output? Can you give an example of what's not working?
Rafik Belkadi
Rafik BelkadiOPโ€ข2mo ago
when i add the outputproperty to the generateVNext method, it doesnt call tools @Daniel Lew
Daniel Lew
Daniel Lewโ€ข2mo ago
can you see if the structuredOutput property works better?
Rafik Belkadi
Rafik BelkadiOPโ€ข2mo ago
ts
const response = await this._mastraClientService.getEmailRespondAgent().generateVNext({
messages: [
{
role: 'system',
content: systemPrompt,
},
{
role: 'user',
content: userPrompt,
},
],
// // Mastra uses threadId for memory scoping
threadId: `${user.id}_${contactEmail}`,
// Resource ID for contact-specific memory
resourceId: contactEmail,
// output: z.object({...,

runtimeContext: {
google_auth_token: googleAuthToken,
},
});
ts
const response = await this._mastraClientService.getEmailRespondAgent().generateVNext({
messages: [
{
role: 'system',
content: systemPrompt,
},
{
role: 'user',
content: userPrompt,
},
],
// // Mastra uses threadId for memory scoping
threadId: `${user.id}_${contactEmail}`,
// Resource ID for contact-specific memory
resourceId: contactEmail,
// output: z.object({...,

runtimeContext: {
google_auth_token: googleAuthToken,
},
});
the structuredOutput property doesn't exist on the mastra client sdk
Daniel Lew
Daniel Lewโ€ข2mo ago
it looks like that property does exist on the MastraClient agent instance. is it possible that you just need to bump the client-js package as well?
Rafik Belkadi
Rafik BelkadiOPโ€ข2mo ago
i am already at latest version 0.12.0 :/
Daniel Lew
Daniel Lewโ€ข2mo ago
ah, I misread things, I have a PR up to fix it here https://github.com/mastra-ai/mastra/pull/7597
GitHub
fix(core, client-js): add structuredOutput to client js by DanielSL...
Description add structuredOutput property to client-js, falls back to using agent model model is optional now in structuredOutput Fixes #7302 Related Issue(s) Type of Change Bug fix (non-brea...
Rafik Belkadi
Rafik BelkadiOPโ€ข2mo ago
Ah Great thank you รจ
Rafik Belkadi
Rafik BelkadiOPโ€ข2mo ago
@Daniel Lew , after upgrading to the latest version i got this type error, any idea ?
No description
Rafik Belkadi
Rafik BelkadiOPโ€ข2mo ago
No description
Rafik Belkadi
Rafik BelkadiOPโ€ข2mo ago
i added as any to fix the type error, and got this afterwards :
[MASTRA] ERROR from StructuredOutputProcessor: [StructuredOutputProcessor] Structuring failed: Internal agent did not generate structured output
[MASTRA] ERROR from StructuredOutputProcessor: [StructuredOutputProcessor] Processing failed: [StructuredOutputProcessor] Structuring failed: Internal agent did not generate structured output
[MASTRA] ERROR from StructuredOutputProcessor: [StructuredOutputProcessor] Structuring failed: Internal agent did not generate structured output
[MASTRA] ERROR from StructuredOutputProcessor: [StructuredOutputProcessor] Processing failed: [StructuredOutputProcessor] Structuring failed: Internal agent did not generate structured output
Daniel Lew
Daniel Lewโ€ข2mo ago
Can you paste the full code snippet oh wait I think it's supposed to be nested under a schema property
structuredOutput: {
schema: z.object({ foo: z.string() })
}
structuredOutput: {
schema: z.object({ foo: z.string() })
}
Rafik Belkadi
Rafik BelkadiOPโ€ข2mo ago
ah ok yeah after adding "schema" type is good, but got no autocompletion tho since default type for structuredOutput is "undefined" and here
structuredOutput: {
errorStrategy: 'fallback',
fallbackValue: {
choices: [],
type: ResponsesType.MULTIPLE_CHOICE,
},
structuredOutput: {
errorStrategy: 'fallback',
fallbackValue: {
choices: [],
type: ResponsesType.MULTIPLE_CHOICE,
},
wouldn't it be better to be able to define the fallbackValue as a function like :
fallbackValue: (result) => myCustomFunctionToParseResult(result)
fallbackValue: (result) => myCustomFunctionToParseResult(result)
Rafik Belkadi
Rafik BelkadiOPโ€ข2mo ago
and structuredOutput fails here are the logs :
`
`
Rafik Belkadi
Rafik BelkadiOPโ€ข2mo ago
my code:
this._mastraClientService.getEmailRespondAgent().generateVNext({
threadId: `${user.id}_${contactEmail}`,
resourceId: `user_${user.id}`,
runtimeContext: {
google_auth_token: googleAuthToken,
},
structuredOutput: {
errorStrategy: 'fallback',
schema: z.object({
choices: z
.array(
z.object({
content: z
.string()
.describe('The response to the email, should be in html format and with nice formatting')
.refine((val) => val.length > 0 && isHtml(val), {
message: 'Response must be in html format',
}),
type: z.nativeEnum(ResponseType).describe('The type of the response'),
title: z.string().describe('Short title for the response (2-3 words MAX)'),
relativeRelevance: z
.number()
.describe("The relative relevance of the response to the user's question between 0 and 3"),
})
)
.describe('Response choices to the email')
.refine((val) => val.length > 0, {
message: 'Response choices cannot be empty',
}),
type: z.nativeEnum(ResponsesType).describe('The type of the response'),
}),
},
});
this._mastraClientService.getEmailRespondAgent().generateVNext({
threadId: `${user.id}_${contactEmail}`,
resourceId: `user_${user.id}`,
runtimeContext: {
google_auth_token: googleAuthToken,
},
structuredOutput: {
errorStrategy: 'fallback',
schema: z.object({
choices: z
.array(
z.object({
content: z
.string()
.describe('The response to the email, should be in html format and with nice formatting')
.refine((val) => val.length > 0 && isHtml(val), {
message: 'Response must be in html format',
}),
type: z.nativeEnum(ResponseType).describe('The type of the response'),
title: z.string().describe('Short title for the response (2-3 words MAX)'),
relativeRelevance: z
.number()
.describe("The relative relevance of the response to the user's question between 0 and 3"),
})
)
.describe('Response choices to the email')
.refine((val) => val.length > 0, {
message: 'Response choices cannot be empty',
}),
type: z.nativeEnum(ResponsesType).describe('The type of the response'),
}),
},
});
`
Daniel Lew
Daniel Lewโ€ข2mo ago
We've got a PR in to fix the types for it https://github.com/mastra-ai/mastra/pull/7668, and that's a good point for the fallback, if you want to open a issue for that, or even PR it that would be great! I'll look into the issue with this schema. It looks like youre using zod v3? I'm guessing it's just not parsing the schema properly
Rafik Belkadi
Rafik BelkadiOPโ€ข2mo ago
i'll try to open a PR for that asap ๐Ÿ™‚ Yup it's v3
Daniel Lew
Daniel Lewโ€ข2mo ago
And you're on the latest version? we just released a new version yesterday with some fixes for structuredOutput, might be worth trying to upgrade first
Rafik Belkadi
Rafik BelkadiOPโ€ข2mo ago
Yep just upgraded this morning
Daniel Lew
Daniel Lewโ€ข2mo ago
what is the prompt & system instruction you're using for this?
Rafik Belkadi
Rafik BelkadiOPโ€ข2mo ago
gonna delete this after if you don't mind :p
Daniel Lew
Daniel Lewโ€ข2mo ago
for sure! I got it okay I'm able to repro, it works fine by invoking the agent directly, but using MastraClient it doesn't work. So I'm guessing our schema serialization logic to send it over the wire isn't working properly
Daniel Lew
Daniel Lewโ€ข2mo ago
GitHub
fix(core): fix structuredOutput over clientjs by DanielSLew ยท Pull...
Description When structuredOutput gets sent over the wire through client-js, it becomes jsonSchema, but the backend code incorrectly assumes it will be of type Schema instead of JSONSchema Related...

Did you find this page helpful?