{"componentChunkName":"component---src-gatsby-theme-chronoblog-templates-note-js","path":"/notes/llm-integration-vercel-ai-sdk/","result":{"data":{"mdx":{"parent":{"__typename":"File","fields":{"gitLogLatestDate":"2026-06-08 00:15:07 +0200"}},"id":"536abb2d-d302-5586-8fbb-ac7c569c6a68","excerpt":"Large language models (LLMs) understand and generate text from prompts. The  Vercel AI SDK  is a provider-agnostic layer over LLM APIs…","frontmatter":{"title":"LLM integration with Vercel AI SDK","date":"2026-06-07 19:18:00 UTC","job_ad":null,"job_ad_id":null,"job_ad_url":null,"tags":["vercel","ai","llm","node","openai"],"cover":null},"fields":{"slug":"/notes/llm-integration-vercel-ai-sdk/","readingTime":{"text":"5 min read"}},"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {\n  \"title\": \"LLM integration with Vercel AI SDK\",\n  \"date\": \"2026-06-07 19:18:00 UTC\",\n  \"tags\": [\"vercel\", \"ai\", \"llm\", \"node\", \"openai\"],\n  \"canonical_url\": \"https://sevic.dev/notes/llm-integration-vercel-ai-sdk/\"\n};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"p\", null, \"Large language models (LLMs) understand and generate text from prompts. The \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://ai-sdk.dev/\"\n  }), \"Vercel AI SDK\"), \" is a provider-agnostic layer over LLM APIs - core functions are \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"generateText\"), \", \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"streamText\"), \", and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"embed\"), \". This post uses the OpenAI provider and mirrors the patterns from the \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://sevic.dev/notes/llm-integration-openai-responses-api/\"\n  }), \"OpenAI Responses API post\"), \".\"), mdx(\"p\", null, \"For the lower-level \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"openai\"), \" npm package, see the \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://sevic.dev/notes/chatgpt-api-nodejs/\"\n  }), \"Chat Completions API\"), \" and \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://sevic.dev/notes/llm-integration-openai-responses-api/\"\n  }), \"Responses API\"), \" posts.\"), mdx(\"h3\", {\n    \"id\": \"prerequisites\"\n  }, \"Prerequisites\"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"OpenAI account\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Generated API key\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Enabled billing\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Node.js version 26\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"ai\"), \", \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"@ai-sdk/openai\"), \", and \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"zod\"), \" installed (\", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"npm i ai @ai-sdk/openai zod\"), \")\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"For Markdown output: \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"marked\"), \", \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"dompurify\"), \", and \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"jsdom\"), \" (\", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"npm i marked dompurify jsdom\"), \")\")), mdx(\"h3\", {\n    \"id\": \"client-setup\"\n  }, \"Client setup\"), mdx(\"p\", null, \"Create an OpenAI provider with your API key (read from the environment in production).\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"import { createOpenAI } from '@ai-sdk/openai';\\n\\nconst openai = createOpenAI({ apiKey: process.env.OPENAI_API_KEY });\\n\")), mdx(\"p\", null, \"The same provider can target other hosts that implement a compatible API by setting \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"baseURL\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"apiKey\"), \":\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"const openai = createOpenAI({\\n  apiKey: process.env.LLM_API_KEY,\\n  baseURL: 'https://your-gateway.example/v1',\\n});\\n\")), mdx(\"p\", null, \"Many third-party gateways support Chat Completions only. The examples below use \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"openai(model)\"), \" (Responses API path). If your provider does not support it, switch to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"openai.chat(model)\"), \" and skip the web search example.\"), mdx(\"h3\", {\n    \"id\": \"basic-integration\"\n  }, \"Basic integration\"), mdx(\"p\", null, \"Pass a string as \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"prompt\"), \" and read \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"text\"), \" from the result.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"import { generateText } from 'ai';\\nimport { createOpenAI } from '@ai-sdk/openai';\\n\\nconst openai = createOpenAI({ apiKey: process.env.OPENAI_API_KEY });\\n\\nconst { text } = await generateText({\\n  model: openai('gpt-5.5'),\\n  prompt: 'Write a one-sentence bedtime story about a unicorn.',\\n});\\n\\nconsole.log(text);\\n\")), mdx(\"h3\", {\n    \"id\": \"system-prompt\"\n  }, \"System prompt\"), mdx(\"p\", null, \"Use the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"system\"), \" parameter for stable behavior (tone, format, role). It takes precedence over casual wording in the user message.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"const { text } = await generateText({\\n  model: openai('gpt-5.5'),\\n  system: 'Reply in one short sentence. Use plain language.',\\n  prompt: 'Explain what an LLM is.',\\n});\\n\\nconsole.log(text);\\n\")), mdx(\"h3\", {\n    \"id\": \"few-shot-prompting\"\n  }, \"Few-shot prompting\"), mdx(\"p\", null, \"Pass prior turns in a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"messages\"), \" array with \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"user\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"assistant\"), \" roles, then the new user message. Keep task rules in \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"system\"), \".\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"const { text } = await generateText({\\n  model: openai('gpt-5.5'),\\n  system:\\n    'Classify sentiment as exactly one word: positive, negative, or neutral.',\\n  messages: [\\n    { role: 'user', content: 'I love this!' },\\n    { role: 'assistant', content: 'positive' },\\n    { role: 'user', content: 'This is awful.' },\\n    { role: 'assistant', content: 'negative' },\\n    { role: 'user', content: 'It is fine I guess.' },\\n  ],\\n});\\n\\nconsole.log(text);\\n\")), mdx(\"h3\", {\n    \"id\": \"streaming\"\n  }, \"Streaming\"), mdx(\"p\", null, \"Use \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"streamText\"), \" and iterate over \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"textStream\"), \" for incremental text.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"import { streamText } from 'ai';\\n\\nconst result = streamText({\\n  model: openai('gpt-5.5'),\\n  prompt: 'List three colors.',\\n});\\n\\nfor await (const part of result.textStream) {\\n  process.stdout.write(part);\\n}\\n\")), mdx(\"h3\", {\n    \"id\": \"structured-output-with-json-schema\"\n  }, \"Structured output with JSON schema\"), mdx(\"p\", null, \"Constrain the model to JSON matching your schema via \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Output.object()\"), \" and a Zod schema. The SDK validates the result.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"import { generateText, Output } from 'ai';\\nimport { z } from 'zod';\\n\\nconst { output } = await generateText({\\n  model: openai('gpt-5.5'),\\n  prompt: 'The film Inception was directed by Christopher Nolan.',\\n  output: Output.object({\\n    schema: z.object({\\n      title: z.string(),\\n      director: z.string(),\\n    }),\\n    schemaName: 'movie_summary',\\n  }),\\n});\\n\\nconsole.log(output.title, output.director);\\n\")), mdx(\"h3\", {\n    \"id\": \"markdown-output-to-html\"\n  }, \"Markdown output to HTML\"), mdx(\"p\", null, \"Ask for Markdown in \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"system\"), \", then convert \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"text\"), \" to HTML and sanitize before rendering (for example with \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"innerHTML\"), \" in the browser or when storing HTML).\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"import { marked } from 'marked';\\nimport { JSDOM } from 'jsdom';\\nimport DOMPurify from 'dompurify';\\n\\nconst purify = DOMPurify(new JSDOM('').window);\\n\\nconst { text } = await generateText({\\n  model: openai('gpt-5.5'),\\n  system: 'Reply in Markdown only. Use a heading and a short bullet list.',\\n  prompt: 'Explain what an LLM is in three bullet points.',\\n});\\n\\nconst markdown = text;\\nconst html = marked.parse(markdown);\\nconst safeHtml = purify.sanitize(html);\\n\")), mdx(\"p\", null, \"Always run \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"DOMPurify.sanitize\"), \" on model-generated HTML. The model can emit unsafe markup. Sanitization strips scripts and other dangerous content.\"), mdx(\"h3\", {\n    \"id\": \"web-search-tool\"\n  }, \"Web search tool\"), mdx(\"p\", null, \"Enable the built-in web search tool when the answer should use current information from the web.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"const result = await generateText({\\n  model: openai('gpt-5.5'),\\n  tools: { web_search: openai.tools.webSearch() },\\n  prompt: 'What was a major tech headline this week? Cite sources briefly.',\\n});\\n\\nconsole.log(result.text);\\n\")), mdx(\"p\", null, \"Web search adds latency and tool usage cost. Use a model that supports tools.\"), mdx(\"h3\", {\n    \"id\": \"embeddings\"\n  }, \"Embeddings\"), mdx(\"p\", null, \"Embeddings are numeric vectors that represent the semantic meaning of text. Use them for semantic search, clustering, and RAG.\"), mdx(\"p\", null, \"Pass a single string to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"embed\"), \" and read the vector from \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"embedding\"), \".\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"import { embed } from 'ai';\\n\\nconst { embedding } = await embed({\\n  model: openai.embedding('text-embedding-3-small'),\\n  value: 'How do I connect pgvector to PostgreSQL?',\\n});\\n\\nconsole.log(embedding.length);\\n\")), mdx(\"p\", null, \"Pass multiple strings in a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"values\"), \" array with \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"embedMany\"), \". Results are in the same order as the input.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"import { embedMany } from 'ai';\\n\\nconst chunks = [\\n  'pgvector adds vector similarity search to PostgreSQL.',\\n  'LangChain helps split long documents into retrieval-friendly chunks.',\\n  'RAG retrieves context first, then asks an LLM to answer.',\\n];\\n\\nconst { embeddings } = await embedMany({\\n  model: openai.embedding('text-embedding-3-small'),\\n  values: chunks,\\n});\\n\\nconsole.log(embeddings.length); // 3\\n\")), mdx(\"p\", null, \"For a full RAG flow with pgvector, see the \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://sevic.dev/notes/rag-openai-embeddings-pgvector-langchain/\"\n  }), \"RAG with OpenAI embeddings post\"), \".\"), mdx(\"h3\", {\n    \"id\": \"demo\"\n  }, \"Demo\"), mdx(\"p\", null, \"Runnable scripts for each section live in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"vercel-ai-sdk-demo\"), \" folder. Get access via \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://sevic.dev/demos\"\n  }), \"code demos\"), \".\"));\n}\n;\nMDXContent.isMDXComponent = true;"}},"pageContext":{"id":"536abb2d-d302-5586-8fbb-ac7c569c6a68"}},"staticQueryHashes":["1961101537","2542493696"]}