惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Google DeepMind News
Google DeepMind News
F
Fortinet All Blogs
阮一峰的网络日志
阮一峰的网络日志
Apple Machine Learning Research
Apple Machine Learning Research
爱范儿
爱范儿
WordPress大学
WordPress大学
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
J
Java Code Geeks
罗磊的独立博客
S
SegmentFault 最新的问题
V
V2EX
V
Visual Studio Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
美团技术团队
博客园 - 三生石上(FineUI控件)
Stack Overflow Blog
Stack Overflow Blog
Y
Y Combinator Blog
MyScale Blog
MyScale Blog
D
Docker
Google DeepMind News
Google DeepMind News
Blog — PlanetScale
Blog — PlanetScale
M
Microsoft Research Blog - Microsoft Research
Martin Fowler
Martin Fowler
S
Secure Thoughts
B
Blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Recent Announcements
Recent Announcements
MongoDB | Blog
MongoDB | Blog
C
Cisco Blogs
C
CERT Recently Published Vulnerability Notes
T
True Tiger Recordings
GbyAI
GbyAI
P
Proofpoint News Feed
P
Privacy International News Feed
Jina AI
Jina AI
The Cloudflare Blog
I
Intezer
AWS News Blog
AWS News Blog
Hacker News - Newest:
Hacker News - Newest: "LLM"
S
Security Archives - TechRepublic
NISL@THU
NISL@THU
The Register - Security
The Register - Security
Recent Commits to openclaw:main
Recent Commits to openclaw:main
P
Palo Alto Networks Blog
S
Schneier on Security
L
LINUX DO - 热门话题
C
CXSECURITY Database RSS Feed - CXSecurity.com
Security Latest
Security Latest
C
Cybersecurity and Infrastructure Security Agency CISA

DEV Community

Unity’s AI agent went public: the developers of a static analysis tool on what that means for code quality Anna's Archive publica un llms.txt para los LLMs que rastrean su catálogo Why I Built Mneme HQ: Preventing AI Agent Architectural Drift I Built a Pay-Per-Call Crypto Signal API with x402 — Heres the Architecture 🚀 “From Prompts to Autonomous Agents: What Google I/O 2026 Changed” The Power of Distributed Consensus in Autonomous SOCs Sixteen TUI components, copy-paste, no dependency The Boring Reliability Layer Every Autonomous Agent Needs Nven - Secret manager Building Multi-Tenant Row-Level Security in PostgreSQL: A Production Pattern The Hardest Part of Being a Developer Isn't Coding Building Vylo — Looking for Collaborators, Partners & Early Support I Thought Memory Fades With Time. It Actually Fades With Information. ORA-00064 오류 원인과 해결 방법 완벽 가이드 I registered an AI agent at 1 AM and something cracked open in my head Pitch: Nven - Sync secrets. Ship faster. Why y=mx+b is the heart of AI From Routines to a Crew — Building a System That Plans Its Own Work & executes it 25 React Interview Questions 2026 (With Answers) — Hooks, React 19, Concurrent Mode An open source LLM eval tool with two independent quality signals Using Dashboard Filtering to Get Customer Usage in Seconds from TBs of Data Skills, Java 17, And Theme Accents 4 Hard Lessons on Optimizing AI Coding Agents Arctype: Cross-Platform Database GUI for LLM Artifacts Your robots.txt says GPTBot is welcome. Your server says 403. Organizing How to Use AWS Glue Workflow 5 n8n Automations Every Digital Agency Should Be Running (Bill More, Work Less) Getting Started with TorchGeo — Remote Sensing with PyTorch Designing a Scalable Cross-Platform Appium Framework Google Antigravity 2.0 & Slash Commands Building a Unified Adaptive Learning Intelligence with Gemma 4, Flutter, and Multi-Model Orchestration Looking for beta testers for a £60 server management application The Disk-Pressure Incident That Taught Me to Always Set LimitRanges and Other Lessons from Mirroring EKS Locally. Why AI Should Not Write SQL Against ERP Databases Vibe coding works until it doesn't. The debt is real. Shipping at the Edge: Migrating a Coffee Subscription Platform to Cloudflare Workers Stop Tab-Switching: A Developer's Guide to Color Tools That Actually Fit the Workflow DevOps vs MLOps vs AIOps: What Changes, What Stays, and a Simple Roadmap to Get Started Run Powerful AI Coding Locally on a Normal Laptop 5 n8n Automations Every WooCommerce Store Needs (Save 10+ Hours/Week) What I Learned Building My Own AI Harness Hytale Servers Will Fail Treasure Hunts Until We Fix Our Event Handling Redux in React: Managing Global State Like a Pro Unfreezing Your GitHub Actions: Troubleshooting Stuck Deployments and Protecting Your Git Repo Statistics Unlocking Project Discoverability on GHES: A Key to Software Engineering Productivity When the Cleanup Code Becomes the Project Rockpack 8.0 - A React Scaffolder Built for the Age of AI-Assisted Development Mismanaging the Treasure Hunt Engine in Hytale Servers Will Get You Killed Stop Calling It an AI Assistant. It’s Already Managing Your Company Why Hardcoded Automations Fail AI Agents
既然 Google Interview Warmup(谷歌面试热身)已经停用,你现在应该用什么?
Ankit Singh · 2026-05-17 · via DEV Community

我是如何独自构建10xInterview.com的——双后端AI路由器、SSE令牌流、用于嵌入的pgvector,以及Webhook驱动的计费。一个Google Interview Warmup的替代品。

当Google悄然退役了Interview Warmup这个虽小但有用的工具(它让你录制面试口语回答并获得AI反馈)时,我有两种反应。第一:太可惜了,我用过它。第二:我能做出更好的。

一年后,10xInterview(10xInterview交互面试) 已上线。单人构建。一个 Go 二进制文件,一个 React 单页应用,一个 Postgres 数据库,一个 Shell 脚本即可部署整个项目。

这篇文章是工程实战分享——四项架构决策,使得代码库在持续交付功能一年后依然显得小巧。如果你是独立创始人,正在构建 AI 密集型产品,以下模式是我会再次采用的。

TL;DR(太长了;) 无聊的栈,固执的路由,流式一切,单一数据库,webhooks作为事实来源。内联代码示例。


栈(故意乏味)

Backend     Go 1.23, Chi router, single binary
Database    Postgres 17 + pgvector
Frontend    React 19, Vite, TanStack Query, shadcn/ui
AI          Vertex AI (free tier) + Gemini API (Pro)
Speech      Google STT + TTS
Infra       Cloud Run x2, Cloud SQL, HTTPS LB, Secret Manager
Billing     Razorpay Subscriptions (webhook-driven)
Deploy      One idempotent shell script

进入全屏模式 退出全屏模式

没有微服务。没有事件总线。没有Kafka。没有Redis(目前)。没有用于向量的第二数据库。没有SSR。没有全局客户端存储。没有月度框架。

约束条件“必须有人在凌晨3点也能操作这个”这个原则决定了每一个选择。


决策一:双后端AI路由器(dual-backend AI Router)

问题:免费用户会瞬间耗尽昂贵的LLM API(大型语言模型API)配额。但专业用户(Pro用户)期待有意义的更好体验。如何从同一段处理代码中同时服务这两类用户?

解决方案:每个AI能力都是一个接口,对应两个实现。一Router 结构体在请求时根据上下文中的计划进行选择。

// services/agent/router.go
type Reviewer interface {
    Review(ctx context.Context, in ReviewInput) (ReviewOutput, error)
}

type ReviewerRouter struct {
    Free Reviewer  // Vertex AI, Gemini 2.5 Flash
    Paid Reviewer  // Gemini API, stronger model
}

func (r *ReviewerRouter) Review(ctx context.Context, in ReviewInput) (ReviewOutput, error) {
    if auth.PlanFromContext(ctx) == auth.PlanPro {
        return r.Paid.Review(ctx, in)
    }
    return r.Free.Review(ctx, in)
}

进入全屏模式 退出全屏模式

处理程序从不检查计划。它们永远不知道命中哪个后端。身份验证中间件将计划置于上下文中;路由器执行正确操作。

这种模式带来了三个好处:

  1. 默认优雅降级。 如果GEMINI_API_KEY 未设置,付费实现是 nil,路由器会回退到免费版。Pro 用户会静默地获得更便宜的型号,而不是 500。轮换的密钥不会导致网站宕机。
  2. 使用零凭据进行本地开发。 如果 AGENT_ENABLED=false,每个代理都会成为返回固定数据的确定性桩。新贡献者克隆仓库,go run ./cmd/server,就能拥有一个无需接触 Google Cloud 即可运行的应用。
  3. 添加第三层是一个结构字段。当(如果)我添加一个不同模型的企业级(Enterprise tier)时,它是r.Enterprise = ...以及调度中的一个额外分支。无需更改处理器。 我现在大约有6个这样的路由器(router)——审查器(reviewer)、生成器(generator)、设计器(designer)、实时面试官(live interviewer)、解释器(explainer)、简历解析器(resume parser)。这种模式从第二个路由器(Router #2)开始就收回了成本。

决策2: SSE令牌流(SSE token streaming)通过一个小型进程内代理(tiny in-process broker)

问题: 答案审核的第一个版本是一个同步的 REST 调用。上传音频 → 等待 8–14 秒 → 渲染 JSON 响应。它能工作,但体验也很糟糕。

解决方案: 通过 Server-Sent Events 逐 token 流式输出 LLM 的结果。前端在生成过程中实时渲染评分和反馈。

架构有意保持最小化:

// Broker.go — ~150 lines total
type Broker struct {
    mu   sync.RWMutex
    subs map[string][]chan Event  // submissionID -> subscribers
}

func (b *Broker) Subscribe(id string) (<-chan Event, func()) {
    ch := make(chan Event, 32)
    b.mu.Lock()
    b.subs[id] = append(b.subs[id], ch)
    b.mu.Unlock()
    return ch, func() { /* unsubscribe + close */ }
}

func (b *Broker) Publish(id string, e Event) {
    b.mu.RLock()
    for _, ch := range b.subs[id] {
        select {
        case ch <- e:
        default: // drop if subscriber is slow
        }
    }
    b.mu.RUnlock()
}

进入全屏模式 退出全屏模式

HTTP 处理器:

func (h *Handler) StreamSubmission(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "text/event-stream")
    w.Header().Set("Cache-Control", "no-cache")
    flusher := w.(http.Flusher)

    events, cancel := h.broker.Subscribe(submissionID)
    defer cancel()

    for {
        select {
        case e := <-events:
            fmt.Fprintf(w, "data: %s\n\n", e.JSON())
            flusher.Flush()
            if e.Type == "complete" { return }
        case <-r.Context().Done():
            return
        }
    }
}

进入全屏模式 退出全屏模式

前端打开一个 EventSource

const es = new EventSource(`/api/v1/submissions/${id}/stream`);
es.onmessage = (e) => {
  const event = JSON.parse(e.data);
  setReview((prev) => mergeEvent(prev, event));
};

进入全屏模式 退出全屏模式

总的挂钟时间与同步版本相同。感知时间大约是一半。用户从第30个令牌开始阅读反馈,而不是等待第400个令牌。

本次用户体验升级的成本:大约200行Go代码,约40行TypeScript代码,零新增基础设施。不需要Redis、NATS或Kafka。单体应用内部的发布/订阅不需要消息队列。

注意: 这种模式之所以有效,是因为后端在会话期间是单个 Cloud Run 实例——提交和订阅者存在于同一个进程中。一旦我需要为每个用户会话扩展到多个实例,我会要么将代理换成 Redis pub/sub,要么使用粘性 Cookie 固定会话。但这不是今天的问题。


决策3:使用 Postgres + pgvector 而不是向量数据库

问题: 两个功能需要向量相似性:

  1. 问题去重 — 当管理员或AI批量生成新问题时,14种不同措辞的"什么是闭包?"不应全部进入目录。
  2. 简历感知推荐 — 给定上传的简历的嵌入表示,显示与候选人所述技能最相似的问题。 差点犯的错误: 我几乎要使用Pinecone。

实际解决方案: CREATE EXTENSION vector;questions 表上增加了一个额外列,resumes 上增加了一个额外列,完成。

-- migrations/0007_add_embeddings.sql
CREATE EXTENSION IF NOT EXISTS vector;

ALTER TABLE questions
  ADD COLUMN embedding vector(768);

CREATE INDEX questions_embedding_idx
  ON questions
  USING hnsw (embedding vector_cosine_ops);

进入全屏模式 退出全屏模式

插入前的去重检查:

SELECT id, title, 1 - (embedding <=> $1) AS similarity
FROM questions
WHERE topic_id = $2
ORDER BY embedding <=> $1
LIMIT 1;
-- reject if similarity > 0.92

进入全屏模式 退出全屏模式

推荐查询:

SELECT q.id, q.title, q.topic_id
FROM questions q
JOIN topics t ON t.id = q.topic_id
WHERE t.id = ANY($2)  -- relevant topics from resume
ORDER BY q.embedding <=> $1  -- resume embedding
LIMIT 20;

进入全屏模式 退出全屏模式

一个数据库。一个备份。一个需要监控的对象。一个连接池。

反对pgvector的论点通常是“它无法扩展到N个向量以上”。对于10x面试(10xInterview)的工作负载——目前约有5万个问题嵌入,增长缓慢——这个上限在几年内还不会达到。当它不再是最佳选择时,将其替换为一个文件中的局部重构。可选择性得以保留。

如果你的向量数量在100万以下,并且正在考虑使用独立的向量数据库:请先试试pgvector。你可能永远不需要迁移。


决策4:Webhooks是计费的单一事实来源

问题:计费错误是最糟糕的错误。用户付款了,系统却没有察觉,支持工单堆积如山。信任荡然无存。

规则:结账端点从不 将用户标记为 Pro。只有 Webhooks 能做到。

POST /api/v1/billing/checkout   →  Mints Razorpay subscription, returns IDs.
                                   Does NOT change user.plan.

POST /webhooks/razorpay         →  Razorpay-initiated. HMAC verified.
                                   Inserts to payment_events.
                                   Updates user.plan ONLY if insert succeeded.

进入全屏模式 退出全屏模式

完整的 Webhook 处理程序:

func (h *Handler) RazorpayWebhook(w http.ResponseWriter, r *http.Request) {
    body, _ := io.ReadAll(r.Body)
    sig := r.Header.Get("X-Razorpay-Signature")

    if !verifyHMAC(body, sig, h.cfg.RazorpayWebhookSecret) {
        http.Error(w, "bad signature", 401)
        return
    }

    var evt RazorpayEvent
    if err := json.Unmarshal(body, &evt); err != nil {
        http.Error(w, "bad json", 400); return
    }

    // The idempotency key
    err := h.db.InsertPaymentEvent(r.Context(), PaymentEvent{
        ProviderEventID: evt.ID,    // UNIQUE constraint
        Type:            evt.Event,
        Payload:         body,
    })
    if errors.Is(err, ErrDuplicate) {
        w.WriteHeader(200)  // already processed; no-op
        return
    }
    if err != nil {
        http.Error(w, "db error", 500); return
    }

    switch evt.Event {
    case "subscription.activated", "subscription.charged":
        h.db.UpgradeUser(r.Context(), evt.Subscription.UserID, "pro", evt.Subscription.EndAt)
    case "subscription.cancelled", "subscription.halted":
        // Don't downgrade immediately. Let it expire naturally.
        h.db.MarkCancelled(r.Context(), evt.Subscription.UserID)
    }

    w.WriteHeader(200)
}

进入全屏模式 退出全屏模式

这为您提供四个属性:

  1. 幂等性由构造保证。 Razorpay 可以重试同一 Webhook 最多 10 次。唯一约束 onprovider_event_id 意味着第一个胜出,其余无操作。无需应用程序级别的去重逻辑。
  2. 无需Cookie认证的端点。 Webhook路由挂载在根目录,位于 Cookie中间件之外。Razorpay不携带Cookie,攻击者也无法伪造——HMAC就是认证。
  3. 免费审计追踪。 每笔收到的支付事件都在payment_events。争议、退款、“为什么扣费”工单——一条 SQL 查询即可回答所有问题。
  4. 无需夜间定时任务(cron)。到期降级由认证中间件(auth middleware)处理:当 Pro 用户的pro_until在收到请求时已过期,中间件会在请求处理过程中即时降级其权限。状态始终正确,因为状态总是主动校验(checked),而非被动扫描(swept)一个操作提示: Razorpay 订阅计划仅支持 INR,没有支持工单流程来启用多币种。因此,定价页面针对非印度访客(通过浏览器时区检测)显示 USD 仅为展示性换算,实际收取 INR。小细节。节省了大量困惑的支持工单。

部署历程

PROJECT_ID=my-prj DOMAIN=10xinterview.com ADMIN_EMAILS=me@x.com \
  GOOGLE_CLIENT_ID=GOOGLE_CLIENT_SECRET=\
  ./deploy.sh

进入全屏模式 退出全屏模式

这是一个全新的 GCP 项目,在 约 12 分钟内即可进行实时部署。deploy.sh提供:

  • 启用 pgvector 的 Cloud SQL 实例
  • 两个 Cloud Run 服务(api + web)
  • 使用 Google 托管证书的 HTTPS 负载均衡器
  • 用于嵌入回填 (Embeddings backfill) 的 Cloud Run 作业
  • 每个凭证的 Secret Manager 条目 每个步骤使用describe-or-create. 重新运行是安全的。脚本中途失败只需再次运行即可恢复。

Razorpay和Gemini API密钥已挂载只有在它们的秘密存在之后——首次部署时没有它们是可以的。AI功能降级为免费层行为;计费端点返回503 Service Unavailable直到你添加密钥。

幂等部署 + 优雅降级 = 一个你可以安心睡一整晚的单人SaaS服务。


我会做出不同的选择

一年后,如果重新开始,我会改变三件事:

  1. 更早采用sqlc。 我从手写代码开始,database/sql 大约在第四个月迁移到sqlc。如果一开始就使用sqlc,代码库会更干净。
  2. 在两个层级上使用相同的嵌入模型。我曾短暂尝试为Pro用户使用更强的嵌入模型。召回率的差异不值得成本,而且双嵌入的记账工作简直是噩梦。现在两个层级使用相同的Vertex嵌入模型。
  3. 跳过设计系统实验。我尝试了Tailwind UI,然后是Park UI,最后是shadcn/ui。本应从shadcn/ui开始的。浪费了两个周末。

如果你曾经使用过谷歌的面试热身(Interview Warmup)

顺便说一下,因为我在Reddit上经常看到这个问题。

谷歌退役了面试热身(Interview Warmup)——这个来自与谷歌一起成长(Grow with Google)的实验性工具,让你录制答案并获得人工智能反馈。很多人喜欢它。它已经没有了。

10倍面试(10xInterview)不是克隆。映射如下:

  • Warmup所做的事情: 记录口头回答 → 获得对提及的见解、词汇和要点的分析。
  • 10倍面试(10xInterview)重叠的部分: 记录口头回答 → 获得0–100分的评分和具体建议,实时流式传输。
  • 10倍面试(10xInterview)新增的功能:按主题和技能策划的问题库,包含汇总最终报告的模拟面试,一个能够进行自适应追问的实时AI面试官,基于简历的问题推荐,以及按需生成带有Mermaid图表(Mermaid diagrams)的解释。
  • 目前尚未实现的功能:STAR格式的行为评分(已列入近期路线图)。与Google无关联,只是填补了他们留下的空白。

免费套餐涵盖了核心的“录制并获取评分”循环,但每周有使用限制。专业套餐解锁了实时互动面试官——这是Warmup从未提供过的更具压力的测试。


试试看

10xinterview.com

使用Google登录。录制一个答案。免费套餐无需付费即可使用完整的题库、评分答案和模拟面试。

如果你发现这篇文章中有你本会做出不同选择的架构决策,我想听听你的想法。直接回复这里,或者从网站获取我的邮箱。


采用 Go、React、Postgres 和 Google Cloud 构建。整个后端小到可以在一个下午读完。上面展示的路由模式、SSE 代理、pgvector 查询和 webhook 处理器是承重部分——其他都是胶水代码。

请识别以下文本的语言,并将其翻译成 简体中文:*如果这有用的话,一个❤️可以帮助帖子接触到更多的开发者。本系列的下一篇文章将深入探讨SSE代理模式(SSE broker pattern)——用Go实现它,无需外部依赖,我遇到的边界情况,以及何时应该使用fo。