构建我的 AI 代理:技术实现详解

构建我的 AI 代理:技术实现详解

从零开始,用 Vercel AI SDK + Astro + Cloudflare Workers 构建一个能回答我简历问题的 AI 代理。

你可能注意到了网站右下角的聊天按钮。点开它,你可以和”我”对话——准确地说,是和一个了解我简历的 AI 代理对话。

这篇文章将详细介绍这个 AI 代理的技术实现。

为什么要做这个?

传统简历是静态的。访客只能被动阅读,无法主动提问。而 AI 代理可以:

  • 交互式体验:访客可以直接问”你会什么技术?”
  • 个性化回答:根据提问角度组织信息
  • 技术展示:本身就证明了我的 AI 工程能力

技术栈选择

组件技术理由
框架AstroSSR 支持 + 静态优先
前端React + Vercel AI SDK流式渲染 + 类型安全
后端Astro API Route与前端同项目,部署简单
部署Cloudflare Workers边缘计算,全球低延迟
模型NVIDIA NIM API兼容 OpenAI 接口

系统架构

整个流程分三步:

  1. ChatDialog (React) → 用户输入消息,调用 useChat 发送请求
  2. /api/chat (Astro API) → 接收请求,调用 streamText 生成流式响应
  3. NVIDIA NIM (LLM) → 返回 AI 回复,逐字流式传输回前端

后端实现:流式响应

核心代码在 src/pages/api/chat.ts

import { createOpenAI } from '@ai-sdk/openai';
import { streamText, convertToModelMessages } from 'ai';
import { OPENAI_API_KEY, OPENAI_BASE_URL, OPENAI_MODEL } from 'astro:env/server';

const SYSTEM_PROMPT = `你是 Leon Liu(刘良权)的 AI 代理...`;

export const POST: APIRoute = async ({ request }) => {
    const { messages } = await request.json();
    
    const openai = createOpenAI({
        baseURL: OPENAI_BASE_URL,
        apiKey: OPENAI_API_KEY,
    });

    const result = streamText({
        model: openai.chat(OPENAI_MODEL),
        system: SYSTEM_PROMPT,
        messages: await convertToModelMessages(messages),
    });

    return result.toUIMessageStreamResponse({
        sendReasoning: true,
    });
};

关键设计决策

  1. 使用 astro:env:类型安全的环境变量,本地 .env 和 Cloudflare Secrets 自动兼容
  2. 流式响应streamText + toUIMessageStreamResponse 实现打字机效果
  3. System Prompt 即知识库:把完整简历嵌入 Prompt,无需 RAG

前端实现:实时渲染

src/components/Chatbot/ChatDialog.tsx 使用 Vercel AI SDK 的 useChat Hook:

import { useChat } from '@ai-sdk/react';
import { DefaultChatTransport } from 'ai';

const { messages, sendMessage, status } = useChat({
    transport: new DefaultChatTransport({
        api: '/api/chat',
    }),
});

Markdown 渲染

AI 回复支持完整 Markdown,包括代码高亮:

import Markdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import CodeBlock from './CodeBlock';

<Markdown
    remarkPlugins={[remarkGfm]}
    components={{
        code({ className, children }) {
            const match = /language-(\w+)/.exec(className || '');
            return match ? (
                <CodeBlock language={match[1]}>{children}</CodeBlock>
            ) : (
                <code>{children}</code>
            );
        },
    }}
>
    {response}
</Markdown>

思考过程展示

部分模型(如 DeepSeek)会输出 <think> 标签包裹的思考过程。我做了特殊处理:

const parseThinkContent = (text: string) => {
    const thinkMatch = text.match(/<think>([\s\S]*?)<\/think>/);
    if (thinkMatch) {
        return {
            thinking: thinkMatch[1].trim(),
            response: text.replace(/<think>[\s\S]*?<\/think>/, '').trim(),
        };
    }
    return { thinking: '', response: text };
};

思考内容会显示在 <details> 折叠块中,让用户可以查看 AI 的推理过程。

Neobrutalism 样式

聊天界面遵循网站的 Neobrutalism 设计语言:

.chat-dialog {
    border: 3px solid #000;
    box-shadow: 8px 8px 0 #000;
    border-radius: 16px;
}

.chat-message-content {
    border: 3px solid #000;
    box-shadow: 4px 4px 0 #000;
    border-radius: 8px;
}

粗边框 + 硬阴影 + 鲜艳色彩 = Neobrutalism。

部署到 Cloudflare

使用 @astrojs/cloudflare adapter:

// astro.config.mjs
export default defineConfig({
    output: 'server',
    adapter: cloudflare({
        imageService: 'compile',
    }),
});

环境变量在 Cloudflare Dashboard 配置为 Secrets,不写入代码仓库。

经验总结

  1. Vercel AI SDK 很好用:统一了前后端接口,流式渲染开箱即用
  2. System Prompt 即 RAG:对于简历这种小规模知识,直接嵌入 Prompt 比建向量库更简单
  3. 边缘部署很重要:Cloudflare Workers 让全球访客都有低延迟体验
  4. 设计即展示:聊天界面本身就是作品展示的一部分

下一步

  • 添加对话历史持久化(Cloudflare KV)
  • 集成更多数据源(GitHub 项目、博客文章)
  • 多语言支持

有问题?直接点右下角问”我”吧 😉

关于作者

Leon Liu

Leon Liu

Full Stack & AI Engineer

分享关于 AI Agents、云原生以及 Vibe Coding 的见解。