Yanir-R/gemini-portfolio

GitHub: Yanir-R/gemini-portfolio

一个带文档感知 AI 助手的个人作品集网站,基于 Gemini API 实现严格的上下文接地回答,并采用多层云原生安全架构抵御提示注入。

Stars: 0 | Forks: 0

# Gemini AI 个人作品集 一个带有 AI 助手的个人作品集网站:前端基于 React + TypeScript 部署在 Cloudflare Pages 上,后端使用 FastAPI 部署在 Google Cloud Run 上,通过 Google 的 Gemini API 基于本地 Markdown 进行解答。 ## 架构 | 层级 | 运行环境 | 原因 | | --- | --- | --- | | Frontend | Cloudflare Pages(静态,全球 CDN) | 它是一个静态 bundle;从容器中提供服务相当于把 CDN 的活干得更糟 | | API 前门 | Cloudflare Worker (`edge/`) | 将 API 置于 Cloudflare 的速率限制和机器人处理之后,并添加后端所需的共享 secret | | Backend | Google Cloud Run(缩容至零) | 需要 Python,调用 Gemini,发送 SMTP | | Secrets | Google Secret Manager | 绝不作为明文 Cloud Run 环境变量传递 | | CI 认证 | Workload Identity Federation | 无密钥——任何地方都不存在 service-account JSON | 浏览器从不直接调用 Cloud Run:它调用 `api.`,然后 Worker 带上共享 secret 转发到 `run.app` URL,后端会直接拒绝 任何没有携带该 secret 的请求。参见 [edge/README.md](edge/README.md)。 ## 前置条件 - Node.js >= 22.22.0(react-router v8 的最低版本要求)以及 npm >= 10.8.2 - Python 3.12(3.9 已 EOL,无法安装当前的依赖项) - 来自 [Google AI Studio](https://aistudio.google.com/apikey) 的 Gemini API key - 启用了结算功能的 Google Cloud 项目——即使在免费额度内,Cloud Build 和 Artifact Registry 也无法在没有启用结算的情况下运行 - Cloudflare 账号(免费)用于前端(Pages)和 API 前门 ## 快速开始 ``` git clone https://github.com/Yanir-R/gemini-portfolio.git cd gemini-portfolio ``` ### 前端 ``` cd frontend npm install npm run dev # http://localhost:3000 ``` | 脚本 | 作用 | | --- | --- | | `npm run dev` | 在端口 3000 上运行 Vite dev server | | `npm run typecheck` | `tsc --noEmit` | | `npm run lint` | ESLint | | `npm run format` / `format:check` | Prettier | | `npm run build` | 先进行类型检查,然后构建——构建过程绝不允许遗留类型错误 | ### 后端 ``` cd backend python3.12 -m venv venv source venv/bin/activate # Windows: .\venv\Scripts\activate pip install -r requirements.txt uvicorn main:app --reload --port 8000 ``` 仅运行一个进程。速率限制器(见下文)将计数器保留在进程内,因此多个 worker 会导致限额上限成倍增加。 ## 配置 ### 前端 公开的构建值已提交在 **`frontend/site.config.ts`** 中:站点 URL、后端 URL 以及可选的头像 URL。如果是通过 Fork 进行开发?请修改这三项。 它们都不是密钥——这三者都可以从已部署的站点中读取—— 提交它们正是保持部署一致性的关键:Vite 会在构建时 将它们内联,因此保存在仓库变量中的值只有在下一次 构建时才会生效,部署后修改并不会产生任何作用。 对于本地开发,同名的环境变量会覆盖对应的值: ``` # frontend/.env.development VITE_BACKEND_URL=http://localhost:8000 VITE_SITE_URL=http://localhost:3000 ``` 如果 `url` 或 `backendUrl` 解析为空,**生产构建将失败**,而 不是发布一个指向 localhost 的 bundle。`VITE_ALLOW_UNCONFIGURED_BUILD=true` 用于显式跳过此检查,专门用于不带配置的构建; 切勿在打算部署的构建中设置此项。切勿在标记中硬编码域名—— `index.html` 使用了 `%VITE_SITE_URL%` 占位符,由构建过程进行替换。 ### 后端 ``` # backend/.env (已 gitignore — 切勿提交此项) GEMINI_API_KEY=... EMAIL_ADDRESS=your_gmail@gmail.com EMAIL_PASSWORD=... # Gmail App Password, not your account password YOUR_EMAIL=where_contact_mail_lands@gmail.com # 可选 ALLOWED_ORIGINS=https://your-frontend.example # comma-separated, added to the CORS allowlist FRONTEND_PROD_URL=https://your-frontend.example ORIGIN_SHARED_SECRET=... # must match the edge Worker's EDGE_SECRET ``` `ORIGIN_SHARED_SECRET` 未设置意味着“不强制执行”,这正是让 Worker 能够在后端开始要求它之前先行部署的原因。一旦设置, 每个不携带匹配 header 的请求都会收到 403 错误——包括直接 调用 `run.app` URL。在本地开发时请将其留空。 ### 速率限制 `/chat-with-files` 和 `/api/contact` 无需认证,且每次调用都会消耗金钱或配额,因此两者均强制执行按客户端和全局窗口的限制。默认值: ``` RATE_LIMIT_CHAT_PER_IP_PER_DAY=10 RATE_LIMIT_CHAT_GLOBAL_PER_DAY=12 RATE_LIMIT_CHAT_WINDOW_SECONDS=86400 RATE_LIMIT_CONTACT_PER_IP_PER_MINUTE=3 RATE_LIMIT_CONTACT_GLOBAL_PER_MINUTE=15 ``` 这两个限制器使用不同的窗口,因为它们解决不同的问题。Contact 是一种速率——某人允许发送的频率。Chat 是一种预算:Gemini 的免费额度每个模型每天仅授予 20 个请求,因此按分钟的窗口每天会重置 1,440 次,根本无法限制任何有意义的消耗。 全局窗口是成本保障:仅靠基于 IP 的限制很容易被 IP 欺骗或僵尸网络击溃。参见 `backend/rate_limit.py`。 ## 部署 这两个工作流都拆分为一个 `verify` 任务(在 pull request 上运行,不需要云凭据)和一个受 `github.event_name == 'push'` 控制的 `deploy` 任务。部署绝不会在 pull request 时运行——两者都针对固定的服务名称,因此 PR 部署会用未经审查的代码覆盖共享环境。 ### 所需的 GitHub 配置 Secrets: | 名称 | 说明 | | --- | --- | | `GCP_DEV_PROJECT_ID` | 目标 GCP 项目 | | `GCP_SA_EMAIL` | 部署 service account | | `GCP_WORKLOAD_IDENTITY_PROVIDER` | WIF provider 资源路径 | | `EMAIL_ADDRESS`, `YOUR_EMAIL` | SMTP 发件人 / 收件人 | | `CLOUDFLARE_API_TOKEN` | 权限范围:Account → Cloudflare Pages → Edit | | `CLOUDFLARE_ACCOUNT_ID` | Cloudflare 账户 | Variables: | 名称 | 说明 | | --- | --- | | `CLOUDFLARE_PAGES_PROJECT` | Pages 项目名称 | | `CLOUDFLARE_PAGES_ENABLED` | 设为 `true` 即激活 Pages 部署——请**最后**设置此项 | | `GCP_RUNTIME_SA` | Cloud Run 容器运行时使用的身份,与部署账号区分开 | | `ALLOWED_ORIGINS` | 后端的额外 CORS 源 | 前端的 URL 不在此列表中:出于上述原因,它们存在于 `frontend/site.config.ts` 中。 这里刻意**没有 `GCP_SA_KEY`**。身份验证使用 Workload Identity Federation,并且 OIDC provider 带有将其限制为该代码库的属性条件,因此永远不会创建或存储长期有效的 JSON 密钥。 `GEMINI_API_KEY`、`EMAIL_PASSWORD` 和 `ORIGIN_SHARED_SECRET` **不是**用于后端部署的 GitHub secrets——它们存放在 Secret Manager 中,并通过 `--set-secrets` 进行附加,从而使其远离 Cloud Run 的修订版本元数据。 `edge/` 中的 Worker 是手动部署的(`npx wrangler deploy`),而不是通过 CI 部署, 其 `EDGE_SECRET` 仅存在于 Cloudflare 中。 ## API | Endpoint | 用途 | | --- | --- | | `GET /` · `GET /health` | 健康检查 | | `GET /api/chat/status` | 查询 chat 是否有可提供解答的语料库 | | `GET /api/content/{file_name}` | 读取 Markdown 文档(路径被限制在 profile 目录内) | | `GET /api/projects` · `GET /api/projects/{slug}` | 项目数据 | | `POST /chat-with-files` | 上下文感知对话——受速率限制 | | `POST /api/contact` | 联系邮件——受速率限制 | ## 上下文层 聊天机器人对 Yanir 的了解,以及它被允许回答的方式,都保存在三个文件中: | 文件 | 职责 | | --- | --- | | `backend/docs/profile/` | 源内容——已发布,请参阅下方的说明 | | `backend/context.py` | 组装并缓存语料库;报告其 token 成本 | | `backend/prompt.py` | 行为契约:grounding、语气、边界 | 语料库规模很小(约 3.6k 个 token),因此每次请求都会发送全部内容, 并基于文件的 mtime 进行缓存,而不是每次调用时重新读取。`context.py` 在加载时 会记录 token 估算值,并在超过审查阈值时发出警告——即到了 需要开始考虑针对每个问题进行内容筛选的阶段。 **Grounding 正是 prompt 层存在的原因。** 聊天机器人会在招聘人员 阅读的页面上以 Yanir 的第一人称进行回答,因此虚构的雇主或日期就是 归咎于真实个人的虚假声明。模型被指示只能基于 profile 进行回答, 在遇到语料库未涵盖的问题时应拒绝回答并提供邮件跟进, 并且要将语料库和访客消息都视为数据而非指令。 ``` backend/docs/ ├── profile/ # markdown the chat answers from — published ├── projects/ # per-project markdown, drives /api/projects and the chat ├── writing/ # published articles, drives /api/writing and the chat └── templates/ # placeholders for forks; never sent to the model ``` ### 修改聊天机器人的回答内容 prompt 和 profile 的更改会针对一组黄金问题集进行验证——包括 grounded 回答、 语料库未涵盖的问题、错误前提、注入和提取——这些问题集 保存在此仓库之外,并在发布更改之前手动运行验证。`backend/tests/` 涵盖了可以在离线状态下断言的内容,并在每次 pull request 的 CI 中运行。 ## 延伸阅读 - [前端 README](frontend/README.md) - [后端 README](backend/README.md) - [Edge Worker README](edge/README.md) - [工作流 README](.github/workflows/README.md) ## 贡献 Fork、创建分支、提交、发起 PR。CI 会在每次 pull request 时运行类型检查、lint、格式检查、构建以及依赖项审计;所有步骤都必须通过。 ## 许可证 [MIT 许可证](LICENSE)
标签:AI助手, AV绕过, Cloudflare, DLL 劫持, FastAPI, Gemini, MITRE ATT&CK, React, Serverless, Syscalls, 个人主页, 大语言模型, 程序员工具, 自动化攻击, 逆向工具