真实复盘:我是如何用 Go + Eino 在 2 周内上线一个 AI 面试平台的?

很多同学觉得 AI 开发门槛高,动不动就是微调模型、GPU 算力。其实在 AI Engineering(AI 工程化) 时代,普通后端开发者才是主力军。本文将复盘我如何利用 字节跳动 Eino 框架,配合 Hertz + Milvus,在 2 周内从零打造并上线一个商业级 AI 面试 Agent 平台的全过程。文末附完整架构图与源码。

一、 需求分析:我们到底要做什么?

接到的需求很简单:做一个**“AI 面试官”**。
但作为架构师,脑子里瞬间崩出了无数个工程难题:

  1. 交互模式:是类似 ChatGPT 的纯流式对话,还是像问卷一样的填空?
  2. 上下文管理:面试通常有 10-20 轮,Token 肯定会炸,怎么做长窗口记忆
  3. 幻觉控制:AI 瞎编面试题怎么办?比如问 Java 却出了个 Go 的 GMP 模型。
  4. 延迟优化:大模型生成太慢,用户等不及怎么办?

最终确定的技术方案是:Go (高性能后端) + Eino (Agent 编排) + Milvus (RAG 知识库) + SSE (流式传输)。


二、 架构设计:Eino 带来的降维打击

如果用 Python 写,我可能会选 LangChain。但在 Go 生态里,Eino 是目前的最佳选择。
它把 Agent 的思考过程抽象成了 Graph(图)

  • Node(节点):代表一个动作,比如“查简历”、“生成问题”、“评估回答”。
  • Edge(边):代表流转逻辑,比如“如果回答正确 -> 下一题”,“如果回答模糊 -> 追问”。

2.1 核心编排逻辑(Show Me The Code)

我们定义了一个 NewSocialComprehensiveAgent(社招综合面试官)。
注意看,这里没有乱七八糟的 if-else,只有清晰的组件组装:

// backend/chatApp/agent/interview/comprehensive/social_comprehensive_agent.go

func NewSocialComprehensiveAgent(userId uint, needResumeTool bool) (adk.Agent, error) {
    // 1. 定义大模型底座(这里接入了 DeepSeek/OpenAI)
    model, err := chat.CreatOpenAiChatModel(ctx, userId)
    
    // 2. 挂载简历分析工具
    // 这是 Agent 的“眼睛”,能读取 PDF 内容
    var toolsConfig adk.ToolsConfig
    if needResumeTool {
        toolsConfig = adk.ToolsConfig{
            ToolsNodeConfig: compose.ToolsNodeConfig{
                Tools: []componenttool.BaseTool{
                    tool2.GetResumeInfoTool(),
                },
            },
        }
    }

    // 3. 组装 Agent
    baseAgent, err := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
        Name:        "SocialInterviewer",
        // System Prompt 是灵魂,定义了面试官的性格
        Instruction: SocialComprehensiveAgentInstruction, 
        Model:       model,
        ToolsConfig: toolsConfig,
        // 允许 Agent 自动思考 15 轮,确保任务完成
        MaxIterations: 15,
    })
    return baseAgent, nil
}

工程化思考:通过 ToolsConfig 的解耦,我们可以随时给面试官增加新能力(比如增加一个“LeetCode 判题工具”),而不需要修改核心对话逻辑。


三、 RAG 实战:让 AI 拥有“题库”

为了解决幻觉,我们建立了一个包含 10w+ 面试题的 Milvus 向量数据库

3.1 混合检索策略

单纯的向量搜索(Vector Search)在精确匹配上很弱。比如我想搜“Java 中级 难度”的题,向量可能会搜出“Go 高级”。
所以我们实现了 Hybrid Search

// backend/internal/eino/milvus/retrieval/retriever.go

// 1. 定义过滤条件
filter := fmt.Sprintf("language == 'Java' && difficulty == 'Medium'")

// 2. 执行检索
// Eino 框架底层封装了 Milvus SDK,调用非常丝滑
docs, err := retriever.Retrieve(ctx, query, 
    retrieval.WithFilter(filter), // 标量过滤
    retrieval.WithTopK(3),        // 只取前三
)

这样,AI 提出的每一个问题,背后都有真实的题库支撑,准确率从 60% 提升到了 99%。


四、 性能优化:如何抗住高并发?

AI 应用最大的瓶颈是 LLM 的推理延迟(首字延迟通常 > 1s)。
为了不阻塞 Go 的协程,我们设计了一套全异步架构

4.1 异步削峰架构图

1. 发送回答
2. 写入任务
3. 返回 TaskID
4. 抢占任务
5. 调用 Agent
6. 流式生成
7. 实时推送

用户前端

Hertz 网关

Redis Queue

后台 Consumer

Eino Agent

SSE 通道

代码实现(Redis Queue):

// backend/internal/mq/redis_queue.go

// 生产者:非阻塞写入
func (q *RedisQueue) Publish(ctx context.Context, topic string, msg []byte) error {
    return q.client.LPush(ctx, topic, msg).Err()
}

// 消费者:阻塞读取,节省 CPU
func (c *Consumer) Start() {
    for {
        res, _ := c.client.BRPop(ctx, 0, c.topic).Result()
        go c.handleTask(res[1]) // 并发处理任务
    }
}

通过这套架构,单机 QPS 提升了 10 倍 以上,且用户体验极其丝滑,就像在看真实的面试官打字一样。


五、 总结与资源

这 2 周的实战让我明白:AI 只是能力,工程化才是落地。
作为 Go 开发者,我们不需要去卷模型算法,只要利用好 Eino 这种优秀的编排框架,结合我们擅长的 高并发、微服务 能力,就能在 AI 时代占有一席之地。

如果你对这个项目的完整源码(含后端 Go、前端 Next.js、部署脚本)感兴趣,或者想深入学习 Eino 框架

👉 资源获取:
关注公众号【王中阳】,回复“面试吧”,即可免费获取项目架构图和部分核心源码。
私信备注“面试吧”,拉你进 Eino 技术交流群,和字节大佬一起交流。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐