使用 AI SDK 构建 Angular AI 应用:Chat、Completion 与结构化对象生成的完整实战指南
【免费下载链接】aiThe AI Toolkit for TypeScript. From the creators of Next.js, the AI SDK is a free open-source library for building AI-powered applications and agents项目地址: https://gitcode.com/GitHub_Trending/ai/ai
本指南基于当前仓库中的 examples/angular 示例应用,系统讲解如何在 Angular 应用中集成 AI SDK(@ai-sdk/angular+ai),通过 Express 后端串联 AI Gateway,实现实时聊天、文本补全与结构化对象生成三种典型交互模式。读完本文,你将掌握Chat、Completion、StructuredObject三个 Angular 服务类的用法、服务端流式响应管线以及前后端代理配置,可直接照搬到自己的 Angular 项目中。
示例应用概览:Angular 前端 + Express 后端
examples/angular 是一个小型的 Angular 应用,核心目标是"跑通" AI SDK 的 UI 包在 Angular 生态中的完整链路:Angular 负责交互界面,Express 负责代理模型请求,AI Gateway 作为默认 Provider 提供模型能力。
从 package.json 可以看到完整技术栈:
- Angular 20:使用 standalone 组件、
ReactiveFormsModule与新的@switch/@for控制流语法,并启用了zoneless变更检测(见 app.config.ts 中的provideZonelessChangeDetection()); - Express.js 5:作为本地 API 服务端,提供
/api/chat、/api/completion、/api/analyze三个端点; - AI SDK 核心包:
ai(服务端流式工具)与@ai-sdk/angular(Angular UI 绑定),两者均为 workspace 内部版本; - AI Gateway(默认 Provider):示例默认通过 AI Gateway 调用模型,模型 ID 形如
openai/gpt-5.6; - Zod:用于结构化对象生成的 schema 定义。
应用 UI 由 app.component.ts 以三个 Tab 组织,分别对应三个独立组件:ChatComponent(聊天)、CompletionComponent(补全)、StructuredObjectComponent(结构化对象)。
环境准备与启动
原文档给出了完整的启动流程,这里原样保留并补充说明:
# 安装依赖(仓库使用 pnpm workspace 管理) pnpm install # 创建 .env 文件并写入 AI Gateway API Key echo "AI_GATEWAY_API_KEY=your_key_here" > .env # 或者使用 OIDC 认证方式(二选一) # echo "VERCEL_OIDC_TOKEN=your_token_here" > .env # 启动应用 pnpm start其中pnpm start由 package.json 中的concurrently脚本驱动,同时并行启动两个进程:
- Angular 开发服务器:
ng serve --proxy-config proxy.conf.json,默认监听localhost:4200; - Express 后端:
tsx src/server.ts,默认监听localhost:3000。
浏览器访问localhost:4200即可看到带三个 Tab 的界面。.env文件会被服务端的dotenv/config自动加载(见 server.ts 顶部import 'dotenv/config'),AI SDK 与 AI Gateway 会自动读取AI_GATEWAY_API_KEY或VERCEL_OIDC_TOKEN完成鉴权。
三种核心交互模式:Chat、Completion 与 StructuredObject
@ai-sdk/angular提供与框架深度集成的服务类,分别对应 AI SDK 的三种能力:多轮对话、单次文本补全、按 schema 生成结构化数据。
模式一:实时聊天(Chat)
chat.component.ts 演示了最完整的聊天场景,核心只有一行:
public chat: Chat = new Chat({});Chat实例自动管理消息数组、加载状态与流式状态。发送消息时通过sendMessage传入用户文本,并在第二个参数中附带自定义body:
this.chat.sendMessage( { text: userInput, }, { body: { selectedModel: 'openai/gpt-5.6', // 动态选择模型 }, }, );body中的selectedModel会随请求发送到服务端,由服务端解析并覆盖默认模型——这就是在运行时切换模型的机制。
消息渲染在 chat.component.html 中完成,该模板几乎覆盖了 AI SDK UI 消息协议的全部 part 类型:
- text:普通文本,
part.state === 'streaming'时显示打字光标; - reasoning:推理过程文本,用
<details>折叠展示; - tool-*:工具调用,通过
isToolUIPart类型守卫判断,展示工具名、状态、输入与输出 JSON; - data-*:自定义数据 part,通过
part.type.startsWith('data-')判断; - file / source-url / source-document:文件与引用来源展示。
组件同时提供了停止生成(chat.stop())、等待状态(chat.status === 'submitted')与错误状态的处理,构成一个完整的生产级聊天界面骨架。
模式二:文本补全(Completion)
completion.component.ts 演示单次文本生成,使用Completion类:
public completion = new Completion({ api: '/api/completion', streamProtocol: 'text', // 使用纯文本流协议 onFinish: (prompt, completion) => { console.log('Completed:', { prompt, completion }); }, });关键点在于streamProtocol: 'text':服务端返回的是纯文本流(text stream)而非完整的 UI 消息协议,前端通过completion.complete(input)触发生成,completion.loading控制按钮状态,completion.stop()中断生成,结果以<pre>原样展示。
模式三:结构化对象生成(StructuredObject)
structured-object.component.ts 演示"输入一段内容、输出结构化 JSON"的场景。首先用 Zod 定义输出 schema:
const schema = z.object({ title: z.string(), summary: z.string(), tags: z.array(z.string()), sentiment: z.enum(['positive', 'negative', 'neutral']), });再交给StructuredObject:
structuredObject = new StructuredObject({ api: '/api/analyze', schema, onFinish: ({ object, error }) => { if (error) { console.error('Schema validation failed:', error); } else { console.log('Generated object:', object); } }, });提交后structuredObject.object即持有校验通过的强类型对象,模板中直接绑定object.title、object.tags.join(', ')等字段。onFinish回调中error非空即代表 schema 校验失败,这保证了前端拿到的永远是合法结构。
服务端实现:一条流式响应管线串起三种能力
所有前端请求最终都汇聚到 server.ts。这段代码是 AI SDK 服务端用法的浓缩示例,值得逐行拆解。
统一的请求入口:streamText + UIMessageStream
/api/chat端点是最复杂的:
app.post('/api/chat', async (req: Request, res: Response) => { const { messages, selectedModel } = req.body; const modelId = typeof selectedModel === 'string' && selectedModel.length > 0 ? selectedModel : defaultModel; const result = streamText({ model: modelId, messages: await convertToModelMessages(messages ?? []), stopWhen: isStepCount(5), providerOptions: { openai: { reasoningEffort: 'low', reasoningSummary: 'detailed', } satisfies OpenAILanguageModelResponsesOptions, }, tools: { getWeatherInformation: { description: 'Get the weather in a given city.', inputSchema: z.object({ city: z.string() }), execute: async ({ city }: { city: string }) => { // 模拟天气查询,随机返回一种天气状况 const conditions = ['sunny', 'cloudy', 'rainy', 'snowy', 'windy']; return `${city}: ${conditions[Math.floor(Math.random() * conditions.length)]}`; }, }, }, }); pipeUIMessageStreamToResponse({ response: res, stream: toUIMessageStream({ stream: result.stream, sendReasoning: true, onError: error => error instanceof Error ? error.message : String(error), }), }); });几个值得注意的实现细节:
- 模型解析:
selectedModel支持从请求体动态传入,空值时回退到defaultModel(openai/gpt-5.6); - 历史消息转换:
convertToModelMessages将前端 UI 消息协议转换为模型可消费的消息格式,这是前后端协议解耦的关键; - 多步推理上限:
stopWhen: isStepCount(5)限制 agent 最多执行 5 步工具循环,防止无限调用; - 推理流透传:
toUIMessageStream开启sendReasoning: true,配合 providerOptions 中的reasoningEffort: 'low'与reasoningSummary: 'detailed',将模型的推理过程一并流式下发(仅支持推理的模型生效); - 服务端工具:
getWeatherInformation是一个服务端执行的 fake tool,输入用 Zod schema 约束,执行函数模拟 500ms 延迟后随机返回天气,前端通过isToolUIPart识别并渲染调用过程; - 响应管线:
toUIMessageStream将result.stream包装为 UI 消息流,再由pipeUIMessageStreamToResponse直接写入 HTTP 响应——全程流式,前端逐 token 渲染。
补全与结构化对象的精简管线
/api/completion与/api/analyze则使用更轻量的纯文本流管线:
// 补全:直接以 prompt 生成文本 const result = streamText({ model: defaultModel, prompt }); pipeTextStreamToResponse({ response: res, stream: toTextStream({ stream: result.stream }), }); // 结构化对象:用 Output.object 约束输出 const result = streamText({ model: defaultModel, output: Output.object({ schema: z.object({ title: z.string(), summary: z.string(), tags: z.array(z.string()), sentiment: z.enum(['positive', 'negative', 'neutral']), }), }), prompt: `Analyze this content: ${prompt}`, });注意两个细节:
Output.object是服务端的结构化输出机制,它保证模型输出满足 schema;而前端的StructuredObject类同样持有 schema,形成前后端双重校验;- server.ts 使用了
express.json({ strict: false }),其注释说明这是为了允许/api/analyze接收 JSON 原始值(如纯字符串)作为请求体——服务端会先把请求体序列化为prompt再交给模型。
前后端代理与模型选择
Angular 开发服务器默认不跨域,proxy.conf.json 将/api前缀的请求代理到 Express 后端:
{ "/api": { "target": "http://localhost:3000", "secure": false, "changeOrigin": true } }因此前端组件中的api: '/api/completion'等相对路径可以直接命中后端端口 3000,无需处理 CORS。
关于模型选择,原文档的说明在此补全为完整结论:
- 默认模型:定义在 server.ts 的
defaultModel = 'openai/gpt-5.6',同时前端 chat.component.ts 的sendMessage也硬编码了selectedModel: 'openai/gpt-5.6'; - 动态切换:修改
chat.component.ts中的selectedModel参数即可切换模型; - 模型 ID 格式:使用 AI Gateway 模型 ID,例如
openai/gpt-5.4、openai/gpt-5.6,格式为provider/model-name。
从示例到生产:可借鉴的工程要点
综合整个 examples/angular 目录,这个示例虽然小巧,但浓缩了 Angular + AI SDK 应用的关键工程决策:
- zoneless 变更检测:AI 流式场景高频更新 UI,
provideZonelessChangeDetection()(app.config.ts)避免 Zone.js 带来的性能开销,是 Angular 20 下的推荐做法; - 协议分层:前端消费 UI 消息协议(
toUIMessageStream),后端内部使用模型消息(convertToModelMessages),两层协议由ai包自动转换,业务代码无需关心格式细节; - 类型安全贯穿前后端:工具输入用 Zod、结构化输出用
Output.object+ Zod、前端渲染用isToolUIPart类型守卫,整个工具调用与对象生成链路全程有类型保障; - agent 循环可控:
stopWhen: isStepCount(5)明确限制了多步工具调用的上限,避免失控循环; - 一应用三模式:同一份 Express 后端同时服务聊天、补全、结构化对象三种场景,展示了
streamText一条 API 的三种用法(messages/prompt/output参数组合)。
源码速查
- 应用入口与 Tab 容器:app.component.ts、app.config.ts
- 聊天组件:chat.component.ts、chat.component.html
- 补全组件:completion.component.ts
- 结构化对象组件:structured-object.component.ts
- 服务端实现:server.ts
- 构建与依赖:package.json、angular.json
- 开发代理配置:proxy.conf.json
【免费下载链接】aiThe AI Toolkit for TypeScript. From the creators of Next.js, the AI SDK is a free open-source library for building AI-powered applications and agents项目地址: https://gitcode.com/GitHub_Trending/ai/ai
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考