Why Your AI Meeting Summaries Get Names Wrong (and How Web Search Fixes Them)
• 11 min read

Why Your AI Meeting Summaries Get Names Wrong (and How Web Search Fixes Them)

Root-causing misspelled names in AI meeting summaries, then fixing them with meeting context and AI SDK 7 native web search, benchmarked on OpenAI, Claude and Mistral models.

by Jonathan Beurel

I'm the creator of Goelo, an AI meeting assistant built on the Dopamine Notetaker engine: a bot joins the meeting, Recall.ai records it, the transcript goes to an LLM, and a summary lands in the app a minute later. Since day one, the accuracy of those summaries has been my priority. People act on them: they send the follow-up email, call back the right person, keep the deadline.

I record my own calls with Goelo, and I use each of them to make it better. After a 34-minute call in French last week, I opened the summary. It was well written and well structured: key points, action items, decisions, next steps. But three details were wrong, and they were exactly the ones I needed for the follow-up:

  • The other party's company was spelled the way it sounds, not the way it is written.
  • The person who recommended me was attached to the wrong company.
  • The dates said "today", "this week" and "probably tomorrow". Fine on the day of the call, useless two days later.

In this article, I'll walk through how I found the root cause, the cheap fix that solves half of it, and the AI SDK 7 setup that solves the rest by letting the model search the web. And because "it looks better" is not a measurement, I benchmarked every summary model of the kit on the same real meeting.

To protect the person I was talking to, I anonymized the call with names from Silicon Valley. The mistakes and the numbers are real.

The Problem: Fluent, Structured, and Wrong on Proper Nouns

The transcript saidThe summary wroteReality
"Pie Piper"Pie PiperPied Piper, the company of the person I was talking to
"Big Head, who works with us at Hooli"Big Head (Hooli)Co-founder of his own startup, only on a mission at Hooli
"the GDL meetup"GDL NetworkA meetup with a completely different name
"I'll get back to you tomorrow, early morning"Deadline: "probably tomorrow"Tuesday, October 13

None of these are LLM hallucinations in the usual sense. The model summarized faithfully what it was given. So the question became: what exactly was it given?

Root Cause: Look at What the Model Actually Receives

The summary service built its prompt like this:

const { output } = await generateText({
  model,
  output: Output.object({ schema: SummaryOutputSchema }),
  system: systemPrompt,
  prompt: `Meeting: "${meetingTitle}"\n\nTranscript:\n${flattenedTranscript}`,
});

A title and a transcript. That's it. Three root causes follow from that line.

1. Speech recognition is bad at proper nouns, and the model can't know it. Transcription engines are trained on common words. A company name, a surname or a product name comes out phonetically: "Pie Piper", "Akmey", "Dabien". The LLM has no other source, so it repeats the mistake with confidence.

2. The model doesn't know who is in the meeting. The engine already had it all from the calendar integration: attendees with their emails, the organizer, the description. And an email domain is the most reliable spelling of a company you can get: richard@piedpiper.com works at Pied Piper. None of it reached the model.

3. The model doesn't know when the meeting happened. Without a date, "tomorrow" can only stay "tomorrow".

The first two causes don't have the same fix. Attendees can be read from the calendar. People who are only mentioned in the conversation (who recommended you, the client's manager, the meetup name) are in no database you own. For those, the model has to go and check.

Fix 1: Give the Model What the Calendar Knows

Dopamine Notetaker syncs Google and Microsoft calendars, so every recording already comes with the full meeting details: date and time zone, organizer, attendees with their emails and responses, description. The fix is to hand all of it to the model, above the transcript:

Meeting: "Pied Piper / Raviga"
Date: Monday 2026-04-06 10:30 (America/Los_Angeles)
Recorded by: Richard Hendricks <richard@piedpiper.com>
Organizer: richard@piedpiper.com
Attendees:
- monica.hall@raviga.com
- Richard Hendricks <richard@piedpiper.com>
- gavin.belson@hooli.com (declined)
Calendar description: Agenda: Series A terms
 
Transcript:
...

And one instruction, appended to every summary template, tells the model how to use it:

export const MEETING_CONTEXT_INSTRUCTION = `The meeting details above the transcript come from the calendar and the recording owner's account: trust them. The transcript comes from speech recognition: it often misspells people and companies.
 
- Spell people and companies as in the meeting details. An attendee's email domain usually names their company: jane@acme.com works at Acme, even if the transcript says "Akmey".
- Write dates as absolute dates (weekday and date), computed from the meeting date.
- Do not invent details that neither the meeting details nor the transcript support.`;

Two details matter more than they look. The date is formatted by hand (Monday 2026-04-06 10:30), because Intl's built-in formats change with the ICU version. And meeting rooms are filtered out of the attendees: Google lists them as attendees with resource: true.

Result on the real call: the company name is right, every date is absolute, and an availability that had been filed under "Decisions" moved to where it belongs. Cost: about 250 more input tokens per summary.

What it can't fix: the person who recommended me was still attached to the wrong company. He was never invited, so no email could save him.

Fix 2: Let the Model Check (Deep Research)

The second fix is an opt-in "Deep research" mode, like the one in LLM clients: before writing, an agent researches the people and companies of the meeting on the web, shows its progress live, and cites its sources.

AI SDK 7 has everything needed: provider-executed web search tools, your own tools in the same call, structured output, and a tool loop. Here is the core of it, simplified:

import { anthropic } from '@ai-sdk/anthropic';
import { isStepCount, Output, streamText, tool } from 'ai';
import { z } from 'zod';
 
const MAX_STEPS = 8;
 
const result = streamText({
  model: anthropic('claude-opus-5-5'),
  instructions: RESEARCH_INSTRUCTIONS,
  messages: [
    {
      role: 'user',
      content: `${meetingContext}\n\nTranscript:\n${transcript}`,
      // The transcript is sent again at every step: cache it
      providerOptions: { anthropic: { cacheControl: { type: 'ephemeral' } } },
    },
  ],
  tools: {
    web_search: anthropic.tools.webSearch_20250305({ maxUses: 5 }),
    reportProgress: tool({
      description: 'Show a short progress note to the user while you research.',
      inputSchema: z.object({ note: z.string() }),
      execute: async () => ({ shown: true }),
    }),
    findPastMeetings, // our own tool, scoped to the workspace in code
  },
  output: Output.object({ schema: DeepResearchSummarySchema }),
  stopWhen: isStepCount(MAX_STEPS),
  // Last step: no tools left, so the model has to write the summary
  prepareStep: ({ stepNumber }) => (stepNumber >= MAX_STEPS - 1 ? { activeTools: [], toolChoice: 'none' } : {}),
});
 
for await (const part of result.stream) {
  if (part.type === 'tool-call' && part.toolName === 'reportProgress') trace.note(part.input.note);
  if (part.type === 'tool-call' && part.toolName === 'web_search') trace.search(part.input.query);
}
 
const summary = await result.output;

The research prompt asks the model to work like an analyst preparing the follow-up: list every proper noun whose spelling or identity is uncertain, verify it ("one source is a lead, two sources that agree make a fact"), build a who's who, add context useful for the next step (flagged as research, never as something said in the meeting), and flag what it could not verify instead of guessing.

The output format is hybrid: a backbone the app can rely on (title, TL;DR, action items, decisions) plus free sections chosen by the model, then people with a source URL or isVerified: false, the list of transcript corrections, and the sources.

The happy path took an afternoon. The interesting part is everything that broke.

Gotcha 1: Structured output swallows your progress notes

I asked the model to write a short progress note before each batch of searches, to show a live trace. With Output.object, it wrote none: all of the model's text is the final JSON. The fix is a no-op tool, reportProgress, that the model calls with its note. It works with every provider, and the notes stream as tool calls, in the summary's language.

Gotcha 2: The model can run out of steps before writing anything

GPT-6 Astra reported its progress after every single search. Each tool round is a step, so it hit the 8-step limit while still researching, and the run ended with No object generated: could not parse the response. The prepareStep above removes every tool on the last allowed step: the model has no choice but to write the summary.

Gotcha 3: Anthropic's web search tool version matters, per model

@ai-sdk/anthropic ships three versions of the web search tool. The 2026 ones run searches through code execution: Claude writes Python that fires 4 to 6 searches in parallel. On Opus, that works but adds code_execution calls you have to hide from the trace. On Sonnet 5, the 2026 versions took 150 to 250 seconds and came back saying they had found nothing. webSearch_20250305 answered in 14 seconds. Same quality on Opus, faster everywhere: I use 20250305 for every Claude model.

Gotcha 4: Each provider describes its searches differently

Anthropic puts the query in the tool-call input. OpenAI's tool-call input is empty: the query is in the tool-result, under output.action, which also reports page openings. A small adapter per provider turns stream parts into trace steps.

Gotcha 5: Mistral chat models have no native search in the AI SDK

mistral.tools.webSearch() exists, but on the chat models the SDK warns provider-defined tool mistral.web_search is not supported, and structured output fails. In Dopamine Notetaker, the Deep research option will be disabled for those models, with an explanation. It will never switch to another provider silently: someone who picked Mistral may have done it on purpose.

Gotcha 6: Without prompt caching, you pay for the transcript at every step

Every step sends the whole context again: transcript plus search results. Without caching, the first Opus run used 167k input tokens for a 14k-token transcript. Adding cacheControl on the transcript message (OpenAI caches automatically) cut the cost in half. Your billing then has to price cache reads and writes like the provider does, or you charge your users twice the real cost.

The Benchmark

One real 34-minute call in French. Every model of the kit, list prices, prompt caching on, and $10 per 1,000 searches assumed for both providers (check their current pricing). One run per configuration: treat the numbers as orders of magnitude, not as a leaderboard.

Three levels of summary, same meeting

Same meeting, Claude Opus 5.5Title + transcript+ Meeting context+ Deep research
The other party's companyMisheard name keptCorrect, from the email domainCorrect, with founders and history
Who recommended meWrong companyWrong companyRight person and company
Dates"today", "this week"AbsoluteAbsolute
Transcript corrections listed--11
People with a verified source--5 of 9, the other 4 flagged as unverified
Duration-24 s64 s
Cost$0.09$0.10$0.49

The recommender is the telling one. The transcript had his first name and two misheard fragments: the name of his startup and the name of the meetup he runs. Deep research matched both and found who he was. The context fix could not: he wasn't on the invite.

The "flagged as unverified" line matters as much as the verified one. For the client's manager, the model found a plausible surname, said so, and still left her unverified because no source tied her to the company. That is the behavior you want in a summary people act on.

Which models support web search with structured output

ModelResultNote
gpt-6-lunaYes
gpt-6-astraYesNeeds the forced final step
claude-sonnet-5Yes, with webSearch_202503052026 tool versions: 150-250 s and "no results" answers
claude-opus-5-5Yes
claude-fable-5-1YesQuick probe only
mistral-small-4NoNative search not supported on chat models
mistral-medium-3-5NoSame

Deep research on the full call

ConfigurationDurationSearchesCostvs standard summary ($0.10)Quality
Opus, webSearch_2025030564 s5$0.494.9×Best: recommender found, 11 corrections, "to prepare" section
Opus, webSearch_2026031885 s11$0.494.9×Same level, searches batched through code execution
Opus, no prompt cache115 s16$0.989.8×Same level
GPT-6 Astra96 s5$0.848.4×Good on people, fewer corrections (5)
GPT-6 Luna66 s6$0.070.7×Clearly lower: recommender not found, 6 corrections

Three takeaways:

  1. Caching is not optional. It halves the cost of the same research.
  2. The cheapest model is cheap for a reason. GPT-6 Luna costs less than a standard Opus summary, but it missed the one person a human would have looked up first.
  3. "About 5× a standard summary" is a fair label for a premium model with caching. That is what the Regenerate popover will show next to the option.

Showing the Research Live

A minute of silence feels broken; a minute of visible work feels thorough. While the agent researches, the summary panel will show a trace built from the stream, like this one from the prototype:

⟳ Researching…
Checking Pied Piper and the people mentioned.
  Searched web  Pied Piper compression startup founders
  Searched web  Richard Hendricks Pied Piper
Founders confirmed. "Big Head" is said to work at Hooli: checking.
  Searched web  Nelson Bighetti startup
  Looked at past meeting  Pied Piper / Raviga (2026-03-30)
Writing the summary…
 
▸ Worked for 1m 4s · 5 searches · 1 past meeting

The steps go to a JSON column on the summary, at most once per second, so they survive a page reload and stay available once the trace collapses. The workspace-wide SSE channel only carries a "summary changed" signal, never the steps themselves: they contain names, and some workspace members may not have access to that recording. The front refetches the summary through an endpoint that checks permissions. It is the same pattern as in my SSE and RTK Query article: stream invalidation signals, not data.

Conclusion

Misspelled names in AI meeting summaries are not a model problem. They are a context problem with two layers:

  • What the system already knows (attendees, email domains, the meeting date) was simply never sent to the model. Sending it is nearly free and fixes the invited people and every date.
  • What nobody knows yet (the people and companies only mentioned) needs verification. Native web search in AI SDK 7 makes that a few dozen lines, provided you know the sharp edges: progress through a tool, a forced final step, the right tool version per model, and prompt caching.

The meeting context fix is already in Dopamine Notetaker, and comes to Goelo with its next update, since it runs on the same engine. Deep research is being built now, with the spec and the benchmark above as its starting point.


Building your own AI meeting assistant? Dopamine Notetaker gives you the whole engine, from calendar sync and meeting bots to transcription and multi-LLM summaries, so you can ship your own in a day.