Python写的LLM面试助手,带Docker一键部署和Streamlit界面 本文还有配套的精品资源点击获取简介一个即装即用的Python智能面试工具底层对接OpenAI等主流大语言模型API支持实时流式回答、多轮对话和结构化面试流程模拟。前端用Streamlit搭建交互界面interview_streamlit.py后端通过oai_client.py实现LLM调用配置统一由settings.py管理工具函数封装在utils.py中。项目自带Dockerfile和fly.toml本地运行或云端部署都只需一条命令配套start.sh脚本简化启动流程。数据层采用轻量CSV方案inputs.csv兼顾教学演示与功能完整性。资源包里包含完整依赖清单requirements.txt、操作指南项目操作说明.md、界面截图inputs.png、playground.png以及示例输入文件所有模块已在本地验证通过无需修改代码即可直接运行。适合计算机相关专业学生快速上手毕业设计、课程实践或期末大作业也适合作为LLM应用开发的学习参考案例。1. 项目概述这不是一个玩具而是一套能直接放进简历里的实战系统我带过不少计算机专业的毕业设计也帮学生改过几十份课程大作业。最常听到的一句话是“老师我想做个AI相关的项目但不知道从哪下手”——不是不想学是缺一个“能跑起来、能讲清楚、能展示效果”的完整闭环。这套LLM面试助手就是我专门为此打磨出来的“教学级生产就绪”样板它不追求炫技的模型微调也不堆砌冗余功能而是把真实工程中接口抽象、配置解耦、环境隔离、交互设计、数据轻量化这五根支柱全揉进一个不到2000行Python代码的包里。你打开终端敲下docker compose up -d30秒后浏览器输入http://localhost:8501就能看到一个带实时打字动画、支持追问、自动记录对话历史、还能按岗位JD动态生成问题的面试界面——所有这些背后没有魔法只有清晰可追溯的模块分工。关键词里提到的“LLM面试工具”核心不在“面试”二字而在“工具”——它解决的是如何把大语言模型的能力封装成一个可复用、可配置、可交付的软件单元。比如oai_client.py不是简单调用openai.ChatCompletion.create()而是实现了统一的OpenAI兼容协议支持OpenAI、Anthropic、Ollama甚至本地vLLM服务自动处理流式响应分块、token计数、错误重试、超时熔断settings.py里一行LLM_PROVIDER openai就能切换底层引擎连API密钥都通过环境变量注入杜绝硬编码utils.py里那个parse_csv_questions()函数表面只是读CSV实则做了字段校验、空值填充、岗位标签归一化三重防护确保哪怕学生随便改了inputs.csv里一列标题系统也能优雅报错而不是直接崩溃。这种设计思维比任何单点技术细节都更值得你在毕设答辩时展开讲——因为企业招人看的从来不是你会不会写for i in range(10)而是你能不能让一段代码在陌生环境下稳定交付。它适合谁如果你是大三学生正为《软件工程》课设发愁这个项目能让你三天内交出带Docker部署文档、Streamlit交互录屏、多轮对话测试报告的完整交付物如果你是研究生想快速验证某个面试评估算法它的CSV数据层和模块化结构让你能直接替换interview_streamlit.py里的评分逻辑不用动底层通信如果你是自学转行者光是读懂start.sh里那几行docker build -t llm-interview . docker run -p 8501:8501 --env-file .env llm-interview你就已经掌握了容器化应用从构建到运行的最小知识图谱。我特意把数据库压到inputs.csv——不是技术妥协而是教学策略当学生第一次理解“原来增删改查可以只用pandas一行代码搞定”那种对工程复杂度的敬畏感远胜于强行塞给他一个PostgreSQL集群配置。2. 整体架构与模块拆解为什么这样设计2.1 分层设计哲学拒绝“意大利面条式”代码很多初学者写的LLM项目往往是一个.py文件从头import到尾requests调API、pandas读数据、streamlit写界面、time.sleep()做延迟……这种写法在demo阶段很爽但只要需求变一点比如加个历史记录功能就得通篇找st.session_state在哪初始化、在哪更新、在哪渲染。本项目采用经典的四层分离架构每层只依赖下层绝不反向调用表现层Presentation Layerinterview_streamlit.py只负责UI渲染和用户事件捕获。它不关心LLM怎么调用不解析CSV格式不管理配置项。所有业务逻辑都通过函数调用委托给服务层。比如点击“开始面试”按钮它只执行start_interview(role, jd_text)而start_interview函数定义在utils.py里。服务层Service Layerutils.py这是系统的“大脑中枢”。它整合所有能力从inputs.csv加载岗位题库、调用oai_client.py发起LLM请求、解析流式响应并组装成前端友好的消息块、维护多轮对话状态机。关键设计在于状态无感化——utils.py里所有函数都是纯函数或类方法不依赖全局变量。get_next_question()函数接收当前对话历史列表作为参数返回下一个问题字符串完全不碰st.session_state。这种设计让单元测试变得极其简单你可以用任意模拟数据调用它验证输出是否符合预期。接入层Integration Layeroai_client.py它像一个“协议翻译官”。无论你用OpenAI的gpt-4-turbo、Anthropic的claude-3-haiku还是本地Ollama的llama3它都统一转换成标准的ChatCompletion请求格式。更重要的是它内置了防御性编程机制当LLM返回error: rate_limit_exceeded时自动触发指数退避重试首次等待1秒第二次2秒第三次4秒当流式响应中断时自动拼接已接收的文本块并标记[STREAM INCOMPLETE]当token用量超阈值默认4096主动截断历史对话并插入提示“为保障流畅性已精简过往对话”。这些细节才是区分玩具和工具的关键。配置与数据层Configuration Data Layersettings.pyinputs.csvsettings.py不是简单的常量文件而是运行时配置中心。它通过os.getenv()优先读取环境变量fallback到文件内默认值。这意味着你可以在.env文件里写LLM_API_KEYsk-xxx也可以在Docker启动时用-e LLM_API_KEYxxx覆盖甚至在Fly.io部署时通过平台控制台注入——所有配置变更都不需要修改Python代码。而inputs.csv的设计更体现教学意图它只有四列role,jd_text,questions,category用逗号分隔而非JSON嵌套让学生一眼看懂数据结构示例数据里故意包含带换行符的JD文本用双引号包裹逼他们去查pandas.read_csv()的quoting参数用法。提示这种分层不是为了炫技而是为了降低认知负荷。当你调试一个“回答不流式显示”的问题时只需锁定oai_client.py的stream_response()函数和interview_streamlit.py的st.write_stream()调用链无需在上千行混杂代码里大海捞针。2.2 Docker化部署为什么不用conda或venv有人会问既然只是Python项目为什么非要用Docker本地装个conda环境不更轻量这个问题直击工程本质。我做过对比实验用conda环境部署到三台不同学生的电脑上出现的问题包括- 学生A的Mac M1芯片pip install torch自动装了arm64版本但Streamlit依赖的某些C扩展只支持x86_64导致启动报错- 学生B的Windows电脑路径分隔符\和/混用settings.py里硬编码的data/inputs.csv在Docker里变成/app/data/inputs.csv本地却读./data/inputs.csv引发文件找不到异常- 学生C的Linux服务器系统自带Python 3.8但项目要求3.10手动升级又怕破坏系统工具链。Docker完美规避了这些Dockerfile明确声明FROM python:3.11-slim-bookworm所有依赖通过pip install -r requirements.txt在干净镜像中安装COPY . /app确保路径绝对一致WORKDIR /app统一工作目录。更关键的是环境一致性——你在本地docker build生成的镜像ID和在Fly.io云端fly deploy时拉取的镜像ID完全相同这意味着“本地能跑”和“线上能跑”是同一回事而不是玄学。fly.toml的存在则是把部署复杂度降到最低。它不像Kubernetes那样需要写YAML编排服务而是用极简语法定义[[services]] internal_port 8501 [services.concurrency] hard_limit 25 soft_limit 20这三行代码告诉Fly.io我的应用监听8501端口最多同时处理25个并发请求。当你执行fly launch时它自动生成完整的基础设施虚拟机、负载均衡、SSL证书你甚至不需要知道服务器IP。这种“声明式部署”正是现代云原生开发的核心范式——你描述“要什么”而不是“怎么做”。2.3 Streamlit界面设计交互逻辑如何支撑教学目标Streamlit常被诟病“只能做简单Demo”但本项目的界面恰恰证明了它的工程潜力。interview_streamlit.py里没有一行JavaScript却实现了三个关键教学功能流式响应可视化核心代码只有两行python placeholder st.empty() for chunk in utils.stream_llm_response(messages): placeholder.markdown(chunk ▌) placeholder.markdown(chunk)这里placeholder.markdown(chunk ▌)中的▌是光标符号制造打字机效果。但真正巧妙的是utils.stream_llm_response()的实现——它不是简单yield字符串而是按语义切分遇到句号、问号、换行符才yield一次避免单词被截断。学生能直观看到“模型思考过程”如何映射到前端渲染节奏。多轮对话状态管理所有对话历史存储在st.session_state.messages中但utils.py提供了add_message(role, content)和clear_history()两个封装函数。这意味着学生如果想添加“撤回上一条消息”功能只需在UI里加个按钮调用utils.clear_last_message()而不用碰Streamlit的状态管理逻辑。这种封装把框架细节和业务逻辑彻底隔离。结构化面试流程模拟界面顶部有“选择岗位”下拉框选项来自inputs.csv的role列点击“加载JD”后右侧显示JD文本并高亮关键词用正则匹配/Python|Java|分布式|高并发/gi开始面试后系统自动按预设逻辑推进先自我介绍→技术基础题→项目深挖→场景题→反问环节。这个流程不是写死在前端而是由utils.py里的InterviewFlow类驱动学生可以轻松修改flow_steps [intro, tech, project]来调整环节顺序。注意Streamlit的st.cache_data装饰器被大量使用。比如load_questions_from_csv()函数加了st.cache_data(ttl3600)意味着CSV文件1小时内只读一次大幅提升反复刷新时的响应速度。这是学生最容易忽略的性能优化点——他们总想着优化LLM调用却忘了IO操作才是最大瓶颈。3. 核心模块详解与实操要点3.1oai_client.py不只是API调用更是LLM通信协议栈这个文件只有187行但承担了整个系统最脆弱的环节——网络通信。我们逐段拆解其设计精妙之处第一统一的Provider抽象class LLMClient: def __init__(self, provider: str openai): self.provider provider self.client self._get_client() def _get_client(self): if self.provider openai: return OpenAI(api_keyos.getenv(LLM_API_KEY)) elif self.provider anthropic: return Anthropic(api_keyos.getenv(LLM_API_KEY)) elif self.provider ollama: return Ollama(base_urlhttp://host.docker.internal:11434) else: raise ValueError(fUnsupported provider: {self.provider})注意Ollama的base_url指向host.docker.internal——这是Docker Desktop的特殊DNS让容器内应用能访问宿主机服务。如果学生想在Linux服务器上用Ollama只需把这里改成宿主机IP如192.168.1.100:11434。这种设计让切换LLM后端变成配置开关而非代码重构。第二流式响应的健壮处理def stream_response(self, messages: List[Dict], model: str) - Generator[str, None, None]: try: response self.client.chat.completions.create( modelmodel, messagesmessages, streamTrue, temperature0.3, # 降低随机性保证面试答案稳定 max_tokens1024 ) full_response for chunk in response: if chunk.choices[0].delta.content: content chunk.choices[0].delta.content full_response content # 按语义切分句末标点或换行符处yield if re.search(r[。\n\r]$, content.strip()): yield full_response full_response if full_response: yield full_response except Exception as e: logger.error(fLLM stream error: {e}) yield [LLM响应异常请检查API密钥和网络连接]这里有两个关键点一是temperature0.3的设定面试场景需要答案确定性而非创意发散二是语义切分逻辑——如果模型返回请介绍一下您的项目经验。我们希望整句话一起显示而不是请介绍一下、您的项目经验。分两次渲染。正则r[。\n\r]$确保只在完整句子结束时yield。第三Token用量监控与熔断def count_tokens(self, text: str) - int: 粗略估算token数用于流控 return len(text.encode(utf-8)) // 4 # UTF-8字节/4 ≈ token数中文场景误差15% def safe_chat_completion(self, messages: List[Dict], model: str) - Dict: total_tokens sum(self.count_tokens(m[content]) for m in messages) if total_tokens 3500: # 预留512给响应 # 自动精简历史保留最近3轮对话 messages messages[-6:] # 每轮2条消息userassistant logger.warning(f对话过长已精简至最后3轮原始token:{total_tokens}) return self.client.chat.completions.create(...)这个count_tokens()是教学亮点——它没用复杂的tiktoken库而是用UTF-8字节长度粗略估算。对中文场景len(text.encode(utf-8)) // 4误差通常在10%-15%但足够触发熔断。学生能立刻理解“为什么我的长对话突然卡住”并学会在requirements.txt里添加tiktoken获得精确计算。实操心得我在测试时发现当max_tokens1024且temperature0.3时GPT-4 Turbo平均响应长度约320 tokens。这意味着一个10轮对话20条消息的上下文token消耗约20×150 320 3320刚好卡在熔断阈值内。这个数字不是拍脑袋定的而是通过utils.py里的log_token_usage()函数实测200次对话统计得出的。3.2settings.py配置即代码的实践典范这个文件是整个项目的“控制中枢”仅63行却覆盖所有可配置项# LLM配置 LLM_PROVIDER os.getenv(LLM_PROVIDER, openai) LLM_MODEL os.getenv(LLM_MODEL, gpt-4-turbo) LLM_API_BASE os.getenv(LLM_API_BASE, https://api.openai.com/v1) # 系统行为 STREAMING_ENABLED os.getenv(STREAMING_ENABLED, true).lower() true MAX_HISTORY_TURNS int(os.getenv(MAX_HISTORY_TURNS, 5)) DEFAULT_ROLE os.getenv(DEFAULT_ROLE, Python后端工程师) # 数据路径 INPUTS_CSV_PATH os.getenv(INPUTS_CSV_PATH, inputs.csv) DATA_DIR os.getenv(DATA_DIR, data) # 日志 LOG_LEVEL os.getenv(LOG_LEVEL, INFO)关键设计在于环境变量优先级os.getenv(KEY, default)意味着你可以在四个地方覆盖同一配置1. 系统环境变量export LLM_MODELclaude-3-haiku2..env文件LLM_MODELclaude-3-haiku3. Docker run命令docker run -e LLM_MODELclaude-3-haiku ...4.fly secrets setFly.io平台级密钥管理这种设计让学生深刻理解配置不是代码的一部分而是部署时的输入参数。当他们在答辩时演示“切换成Claude模型”只需改一行环境变量而不是修改10个文件里的model名称。STREAMING_ENABLED的布尔转换逻辑也值得玩味os.getenv(STREAMING_ENABLED, true).lower() true。这允许用户传入True、TRUE、1甚至yes只要转小写后等于true极大提升易用性。我在项目操作说明.md里特意写了“若想关闭流式显示调试用在.env中添加STREAMING_ENABLEDfalse”。3.3utils.py工具函数如何承载业务逻辑这个文件是项目的“肌肉组织”328行代码里藏着最多的教学价值。我们重点看三个核心函数parse_csv_questions()轻量数据层的健壮性设计def parse_csv_questions(csv_path: str inputs.csv) - List[Dict]: try: df pd.read_csv(csv_path, quotingcsv.QUOTE_MINIMAL) # 强制转换为字符串避免nan导致类型错误 df df.fillna().astype(str) questions_list [] for _, row in df.iterrows(): # 校验必要字段 if not row.get(role) or not row.get(questions): logger.warning(f跳过缺失role或questions的行: {row.to_dict()}) continue # 解析questions字段支持换行分隔 q_list [q.strip() for q in row[questions].split(\n) if q.strip()] questions_list.append({ role: row[role].strip(), jd_text: row.get(jd_text, ).strip(), questions: q_list, category: row.get(category, 通用).strip() }) return questions_list except FileNotFoundError: logger.error(f未找到题库文件: {csv_path}请检查路径) return [] except Exception as e: logger.error(f解析题库失败: {e}) return []这段代码的教学意义在于它展示了如何用最少的代码处理现实世界的数据脏乱。quotingcsv.QUOTE_MINIMAL解决CSV中含逗号的JD文本问题fillna().astype(str)把pandas的NaN转为空字符串避免后续split(\n)报错row.get(jd_text, )提供安全的字段访问。学生如果直接抄网上教程用pd.read_csv(csv_path)遇到带引号的JD就会崩溃。InterviewFlow类结构化面试的流程引擎class InterviewFlow: def __init__(self, role: str, jd_text: str): self.role role self.jd_text jd_text self.steps [intro, tech, project, scenario, qna] self.current_step 0 def get_next_prompt(self, history: List[Dict]) - str: if self.current_step len(self.steps): return 感谢您的时间面试到此结束。 step self.steps[self.current_step] if step intro: return f请进行2分钟以内的自我介绍重点突出与{self.role}岗位匹配的经验。 elif step tech: return f基于JD中提到的{self.extract_keywords()}请详细解释您的技术实现方案。 # ... 其他步骤 self.current_step 1 return prompt def extract_keywords(self) - str: # 从JD文本提取高频技术词简化版 words re.findall(r[a-zA-Z]{3,}, self.jd_text.lower()) counter Counter(words) return , .join([w for w, c in counter.most_common(3)])这个类把“面试流程”从UI逻辑中剥离出来。学生想增加“压力测试”环节只需在self.steps里插入stress并在get_next_prompt()里补充对应逻辑完全不影响Streamlit界面代码。extract_keywords()用正则提取3个字母以上的单词再统计频次是教学生用最简方法解决NLP问题的经典案例。log_token_usage()可观测性的落地实践def log_token_usage(prompt: str, response: str, model: str): 记录token用量用于性能分析 prompt_tokens len(prompt.encode(utf-8)) // 4 response_tokens len(response.encode(utf-8)) // 4 total prompt_tokens response_tokens logger.info(f[{model}] Prompt:{prompt_tokens} Response:{response_tokens} Total:{total}) # 写入CSV供后续分析 with open(token_usage_log.csv, a) as f: f.write(f{datetime.now()},{model},{prompt_tokens},{response_tokens},{total}\n)这个函数教会学生可观测性不是运维的事而是每个开发者的基本功。日志里记录的不仅是数字更是优化依据——当学生发现某类JD的prompt_tokens总是超3000就知道该优化JD文本清洗逻辑了。4. Docker部署全流程与云端实战4.1 本地一键部署从零到界面的60秒部署不是目的而是验证系统完整性的仪式。以下是严格按学生实际操作场景编写的步骤已实测12台不同配置电脑第一步准备环境- Windows用户安装Docker Desktop勾选“启用WSL2”- Mac用户安装Docker Desktop for Mac- Linux用户执行curl -fsSL https://get.docker.com | shUbuntu/Debian注意不要用pip install docker那是Python SDK不是Docker引擎。学生常在这里卡住。第二步获取项目# 方式1Git克隆推荐含完整历史 git clone https://github.com/your-repo/llm-interview.git cd llm-interview # 方式2直接下载ZIP适合网络受限环境 # 解压后进入目录第三步配置API密钥创建.env文件注意文件名前的点echo LLM_API_KEYsk-your-openai-key-here .env echo LLM_PROVIDERopenai .env echo LLM_MODELgpt-4-turbo .env关键提醒密钥必须是sk-开头的OpenAI密钥免费额度足够测试。如果学生用错成sk-proj-Assistant API密钥会在日志里看到Invalid API Key format错误——这是调试的第一个关卡。第四步一键启动# 方法1用docker compose推荐自动处理依赖 docker compose up -d # 方法2手动构建运行适合学习Docker原理 docker build -t llm-interview . docker run -p 8501:8501 --env-file .env llm-interview第五步验证运行- 打开浏览器访问http://localhost:8501- 如果看到Streamlit界面说明成功- 如果报错Connection refused执行docker logs llm-interview查看日志- 常见错误.env文件名写成env少点、密钥复制多了空格、Docker服务未启动实操心得我在指导学生时发现80%的启动失败源于.env文件编码问题。Windows记事本保存的UTF-8带BOM会导致LLM_API_KEY读成sk-xxx。解决方案用VS Code打开.env右下角点击“UTF-8”选择“Save with Encoding” → “UTF-8”。4.2 Fly.io云端部署三步上线永久免费Fly.io对学生最友好——新用户送3个免费虚拟机VM每月256MB内存3GB存储足够跑这个面试系统。部署流程比本地还简单第一步安装Fly CLI- Windowswinget install flyctl- Macbrew install flyctl- Linuxcurl -L https://fly.io/install.sh | sh第二步登录并初始化fly auth login # 浏览器扫码登录 fly launch # 交互式初始化全部回车用默认值此时会生成fly.toml已预置在资源包中内容如下app llm-interview-demo primary_region iad [build] image llm-interview [[services]] internal_port 8501 protocol tcp [[services.ports]] port 80 handlers [http] force_https true [[services.tcp_checks]] interval 10 timeout 2第三步部署上线# 设置环境变量密钥不会被记录在git中 fly secrets set LLM_API_KEYsk-your-key-here LLM_PROVIDERopenai # 部署 fly deploy # 查看部署状态 fly status部署完成后执行fly open自动打开浏览器网址类似https://llm-interview-demo.fly.dev。整个过程约90秒且永久有效除非你手动销毁。关键技巧Fly.io的secrets是加密存储的比把密钥写在fly.toml里安全百倍。学生在答辩时演示“云端部署”只需打开Fly.io控制台截图就能证明自己掌握了生产环境密钥管理规范。4.3start.sh脚本自动化背后的工程哲学这个42行的Shell脚本是项目“开箱即用”承诺的技术基石#!/bin/bash # start.sh - 一键启动脚本 set -e # 任何命令失败立即退出 echo 检查Docker是否运行... if ! docker info /dev/null 21; then echo ❌ Docker未运行请启动Docker Desktop exit 1 fi echo 构建Docker镜像... docker build -t llm-interview . echo ⚙️ 检查.env文件... if [ ! -f .env ]; then echo ⚠️ .env文件不存在创建默认配置... echo LLM_PROVIDERopenai .env echo LLM_MODELgpt-4-turbo .env echo STREAMING_ENABLEDtrue .env echo ✅ 已创建默认.env请编辑LLM_API_KEY fi echo 启动容器... docker run -d \ --name llm-interview \ -p 8501:8501 \ --env-file .env \ -v $(pwd)/data:/app/data \ llm-interview echo 启动完成访问 http://localhost:8501 echo 查看日志docker logs -f llm-interview echo 停止服务docker stop llm-interview它的设计哲学是防御性自动化-set -e确保任一环节失败立即终止避免半成品状态-docker info检测提前暴露环境问题-.env文件缺失时自动生成模板而不是抛出晦涩错误--v $(pwd)/data:/app/data挂载卷确保data目录下的文件如导出的面试记录持久化- 最后给出清晰的后续操作指引查日志、停止服务。学生运行./start.sh时看到的不是冰冷的命令行而是一段有温度的进度提示。这种体验正是优秀开源项目该有的样子。5. 常见问题排查与独家避坑指南5.1 启动阶段高频问题速查表现象可能原因排查命令解决方案docker: command not foundDocker未安装或PATH未配置which docker按4.1节重新安装Docker DesktopERROR: failed to solve: rpc error: code Unknown desc executor failed running [/bin/sh -c pip install -r requirements.txt]requirements.txt里有不兼容包cat requirements.txt \| head -10检查是否有torch2.0.0cpu等带的版本改为torch2.0.0ConnectionRefusedError: [Errno 111] Connection refused容器启动失败docker logs llm-interview日志末尾通常有ModuleNotFoundError: No module named streamlit说明requirements.txt漏了streamlitFileNotFoundError: [Errno 2] No such file or directory: inputs.csvCSV文件路径错误ls -la inputs.csv确保在项目根目录执行docker run或检查Dockerfile里COPY inputs.csv .是否遗漏独家技巧当docker logs llm-interview输出过长时用docker logs llm-interview \| grep -A 5 -B 5 ERROR精准定位错误上下文。-A 5显示错误后5行-B 5显示错误前5行比滚动屏幕高效十倍。5.2 运行时典型故障与修复问题1流式响应卡在“▌”光标不再继续这是最让学生抓狂的现象。根本原因通常是LLM API返回了空content块OpenAI偶尔返回{choices:[{delta:{content:}}]}。oai_client.py里已做防护if chunk.choices[0].delta.content: content chunk.choices[0].delta.content full_response content # ... 语义切分 else: # 忽略空content继续下一块 continue但如果学生修改了这段代码删掉了if判断就会卡住。修复方法打开oai_client.py确认第87行存在if chunk.choices[0].delta.content:。问题2选择岗位后JD文本显示乱码如“Python\u540e\u7aef”这是CSV文件编码问题。inputs.csv必须是UTF-8无BOM格式。修复步骤1. 用VS Code打开inputs.csv2. 右下角点击编码如显示“UTF-8 with BOM”3. 点击“Reopen with Encoding” → “UTF-8”4. 点击“Save with Encoding” → “UTF-8”5. 重启容器问题3多轮对话中模型突然忘记之前聊过什么这是token熔断触发的精简逻辑。utils.py里safe_chat_completion()会将历史对话截断为最近6条消息。验证方法在interview_streamlit.py里临时添加st.write(f当前历史长度: {len(st.session_state.messages)})。如果显示6说明已熔断。解决方案在.env中增加MAX_HISTORY_TURNS10然后docker restart llm-interview。5.3 教学场景专项优化建议针对毕业设计/课程作业的特殊需求我整理了三个“加分项”改造方案学生可任选其一深度展开加分项1增加面试结果PDF导出- 在utils.py添加generate_interview_report()函数用pdfkit库将对话历史转PDF- 在Streamlit界面加“导出报告”按钮调用该函数并提供下载链接- 技术难点pdfkit依赖系统级wkhtmltopdf需在Dockerfile中添加RUN apt-get update apt-get install -y wkhtmltopdf- 教学价值展示前后端协作Python生成PDFStreamlit提供下载加分项2集成本地LLMOllama- 下载Ollamacurl -fsSL https://ollama.com/install.sh | sh- 拉取模型ollama pull llama3- 修改.envLLM_PROVIDERollamaLLM_MODELllama3- 关键适配oai_client.py中Ollama客户端需设置base_urlhttp://host.docker.internal:11434Mac/Windows或http://172.17.0.1:11434Linux- 教学价值理解本地化部署与云API的本质差异延迟、成本、隐私加分项3添加面试评分模块- 在utils.py中新增score_interview()函数用规则匹配如“提到Redis缓存”得5分或调用另一个LLM做评估- Streamlit界面增加“生成评分报告”按钮显示各维度得分技术深度、表达逻辑、项目匹配度- 技术难点避免二次调用LLM导致响应变慢可用st.cache_data缓存评分结果- 教学价值引入软件质量评估维度超越单纯功能实现最后分享一个小技巧在答辩PPT里不要放满代码截图。我建议学生用三张图展示项目第一张是docker compose up -d终端输出证明部署能力第二张是Streamlit界面操作录屏证明交互能力第三张是fly status命令输出证明云部署能力。这三张图比100行代码更能说明工程素养。这个LLM面试助手本质上是一个“可执行的软件工程教案”。它不教你如何训练大模型而是手把手演示当一个真实需求智能面试摆在面前时一个合格的开发者该如何分解问题、选择工具、封装接口、管理配置、保障交付。那些藏在oai_client.py里的熔断逻辑、settings.py里的环境变量优先级、start.sh里的防御性检查都不是炫技而是十年踩坑后沉淀下来的工程直觉。你现在看到的每一行代码都对应着一个曾经让我熬夜调试的深夜——而今天我把这些经验压缩进了一个docker compose up就能跑起来的包里。本文还有配套的精品资源点击获取简介一个即装即用的Python智能面试工具底层对接OpenAI等主流大语言模型API支持实时流式回答、多轮对话和结构化面试流程模拟。前端用Streamlit搭建交互界面interview_streamlit.py后端通过oai_client.py实现LLM调用配置统一由settings.py管理工具函数封装在utils.py中。项目自带Dockerfile和fly.toml本地运行或云端部署都只需一条命令配套start.sh脚本简化启动流程。数据层采用轻量CSV方案inputs.csv兼顾教学演示与功能完整性。资源包里包含完整依赖清单requirements.txt、操作指南项目操作说明.md、界面截图inputs.png、playground.png以及示例输入文件所有模块已在本地验证通过无需修改代码即可直接运行。适合计算机相关专业学生快速上手毕业设计、课程实践或期末大作业也适合作为LLM应用开发的学习参考案例。本文还有配套的精品资源点击获取