软件学院项目实训-智面(职导助手)个人工作(八)

📅 2026/6/16 1:29:24
软件学院项目实训-智面(职导助手)个人工作(八)
一、目标概览这阶段我主要做了三块第一把 develop 里根目录 app/ 的 AI 代码迁进队友的 backend 工程和现有 FastAPI 路由、JWT 对齐能在浏览器里走通主链路。第二联调时修了几个后端会话逻辑上的问题比如填简历后第一轮答完直接跳第二题、追问没出来以及合并时遇到的文件编码损坏等。第三接入爬虫同学给的 FAISS 压缩包约 2.3 万条、768 维并针对「面经太多、引用重复、回答跑题」做了 RAG 检索侧的调整。主要涉及mock_interview_service.py、interview_session.py、rag/import_crawler_pack.py、rag/chunking.py、rag/hybrid.py、rag/pipeline.py、ai_core/client.py。二、开发成果详述2.1 把 AI 内核并进队友 backend队友分支 dev_frontend_1.1.0 的 backend 是 FastAPI JWT。我这边之前的 AI 在根目录 app/ 和独立 api_server 里。做法把 interview_session、ai_core、rag、题库 JSON、追问模板等放进 backend/app/HTTP 层用 mock_interview_service.py 做薄适配把 user_id 绑到 SESSIONS对外仍返回 interviewId、nextQuestion、reportId。# backend/app/api/mock_interview_service.py def create_session(user_id, job_title, resume, *, difficultyNone): sid, opening ai_create_session(job_title, resume or , difficultydifficulty) if sid in SESSIONS: SESSIONS[sid][user_id] user_id return {interviewId: sid, nextQuestion: opening}2.2 联调追问与简历换题的时序联调里先后踩过两个相关坑最终用「追问 / 换题分流」统一收口。现象 A合并初期 创建面试时填了简历第一题有效作答后还没点评追问就出现「根据你的简历我再问一题」八类追问被插队。原因 A 简历第二题触发过早与「先短评再追问」冲突。早期补丁是在换题路径加followup_delivered未完成至少一轮追问不出简历第二题。现象 B后续联调 用户说「可以问我简历的题目吗」或回答过短仍被「我们先聚焦当前题目」继续深挖固化题库八股。原因 B 首题仍走固化题库未识别「按简历换题」意图短答走 thin followup 而非换题。最终改法有简历时首题优先简历定制pick_first_question先走llm_generate_question_from_resume再题库 / 云端兜底。有效作答只走追问process_user_turn里只有should_switch_question为真时才调_pick_next_question否则走llm_chat八类追问并置followup_delivered True。换题条件user_answer.py拒绝 / 要简历题 / 回答不足以支撑追问 → 换题不再 thin followup 死磕当前题。简历双题计数会话字段resume_custom_idxcreate 时若首题已是简历题则 idx1换题路径上idx 2时优先简历定制第一次换题为简历第二题。# backend/app/interview_session.py — process_user_turn追问 vs 换题分流 if should_switch_question(text, min_submin_sub): nq, nsrc, npv _pick_next_question(s, role) all_questions.append(nq) s[qsrc], s[qpv] nsrc, npv _set_state(s, EV_SWITCH_QUESTION) # 返回换题回复不再 llm_chat 追问当前题 ... # 有效作答走八类追问 reply llm_chat(history) s[followup_delivered] True # backend/app/interview_session.py — _pick_next_question仅换题路径 def _pick_next_question(s, role): ex set(s.get(all_questions) or []) parsed s.get(resume_parsed) idx int(s.get(resume_custom_idx) or 0) if parsed and idx 2: hint ( build_resume_second_question_hint(parsed) if idx else build_resume_prompt_block(parsed, s.get(resume_text) or ) ) gen llm_generate_question_from_resume(role, hint) if gen: s[resume_custom_idx] idx 1 s[followup_delivered] False return gen, aliyun_resume, question_resume_v1 return pick_question(role, difficultys.get(difficulty), exclude_questionsex) # backend/app/ai_core/user_answer.py — 何时换题 def should_switch_question(text, *, min_subNone): if is_refusal_or_no_idea(text) or is_resume_question_request(text): return True return not is_substantial_answer(text, min_charsmin_sub or 36)另合并大文件时interview_session.py曾被弄成 UTF-16 无法 import用git show origin/develop:路径按字节写回。2.3 爬虫压缩包接入交付物 faiss_interview_dbindex.faiss clean_chunks.txt23074 条、768 维 row_data 原文备份。新增 rag/import_crawler_pack.py解析 clean_chunks.txt 生成 chunks_meta.json与 index.faiss 对齐后写入索引目录。支持对 zip 或已解压目录一键导入python -m rag.import_crawler_pack --zip 压缩包路径 python -m rag.import_crawler_pack --dir 已解压目录路径查询 embedding 的向量维度须与索引一致爬虫索引为 768 维。client.py 在未手动指定维度时会从已加载索引读取 index_dimension() 写入 API 请求避免维度不一致导致 FAISS 报错、接口 500# backend/app/ai_core/client.py — embeddings_create 内 if dim is None: try: from rag.index_store import index_dimension dim index_dimension() except Exception: dim None if dim is not None and dim 0: payload[dimensions] dim2.4 RAG接入后的检索与八股向调整接完爬虫包后发现向量索引已是 2.3 万片段但混合检索的关键词腿仍只扫 rag_corpus 里约 30 篇 md等于半打通语料里面经比例很高问技术八股却命中面经流水账同一篇文章切成多段references 里同一链接重复出现。改动如下1load_search_rows()检索时先读 chunks_meta.json再叠加精编 md。# backend/app/rag/chunking.py def load_search_rows(corpus_dir: Path) - list[dict]: from rag.index_store import index_ready, load_meta_rows rows: list[dict] [] if index_ready(): rows.extend(filter_bagua_rows(load_meta_rows())) for c in load_corpus_chunks(corpus_dir): rows.append(chunk_to_meta_row(c)) return rows2八股 vs 面经默认过滤面经噪声爬虫片段只保留 java、frontend、algorithm、devops 等技术类rag_corpus/*.md 关键词加权技术类爬虫引用数量设上限避免面经霸榜。3引用去重document_key() dedupe_hits_by_document()每篇文章只保留相关度最高的一段。# backend/app/rag/chunking.py def document_key(row: dict) - str: url str(row.get(url) or ).strip() low url.lower() if low.startswith(http) and local.corpus not in low and low not in (#, ): from urllib.parse import urlparse p urlparse(url) host_path f{p.netloc}{p.path}.rstrip(/).lower() if host_path: return host_path path str(row.get(source_path, )).replace(\\, /).lower() if path.endswith(.md): return path title str(row.get(title, )).strip().lower() return title or path or unknown def dedupe_hits_by_document(hits: list[tuple[dict, float]]) - list[tuple[dict, float]]: best: dict[str, tuple[dict, float]] {} for m, s in hits: key document_key(m) prev best.get(key) if prev is None or s prev[1]: best[key] (m, s) out list(best.values()) out.sort(keylambda x: x[1], reverseTrue) return out4pipeline 返回前再 dedupe 一次# backend/app/rag/pipeline.py hits dedupe_hits_by_document(hits) ctx _build_context_block(hits)5hybrid.py 里向量命中需有关键词分或为精编 mdPrompt 强调八股结构概念、原理、对比不要面经流水账。调完后例如问「Vue3 ref 和 reactive 区别」引用前列会回到精编 md而不是重复的面经标题。三、开发中的问题与改动优点3.1 遇到的问题1两套入口根目录 demo vs backend/main.py容易启错联调时对不上。2合并后「配置都有」但页面体感不对追问顺序是联调才暴露的。3爬虫接入 ≠ RAG 变好面经比例高反而干扰八股问答。4embedding 维度与 FAISS 索引不一致八股接口直接 500一开始不好定位。5多次启后端堆进程改代码不生效本地开发踩坑。3.2 改动带来的优点改动优点AI 进队友 backend和 JWT、/api/v1 一条线答辩演示方便followup_delivered先追问、后简历第二题和上一篇设计一致import_crawler_pack可重复导入换爬虫包不用手拷 faiss全库关键词 面经过滤 引用去重八股检索更贴题、引用列表更干净768 维自动对齐少踩 FAISS 维度错误3.3 小结这篇写的是拉队友代码之后的新工作合并、会话顺序小修、接爬虫、RAG 检索侧八股化。追问/报告/题库等核心设计见上一篇。四、总结与后续计划总结 AI 代码并入 backend修简历第二题与追问顺序爬虫 FAISS 包导入RAG 做全库打通、面经过滤、引用去重与维度对齐。后续 报告与会话落库row_data 结构化进题库检索八股 TOP20 评测留数据语料扩容后重新 import