候选人 : xxx
应聘岗位 : AI应用开发工程师(RAG/智能体方向)
面试官视角 : 资深AI应用开发工程师
面试目的: 验证简历真实性,考察技术深度,评估实战能力
📊 简历技术栈分析
简历声称的技术能力
| 技术领域 | 声称掌握 | 需验证深度 |
|---|---|---|
| RAG系统 | 向量检索、关键词召回、精排策略 | ⭐⭐⭐⭐⭐ |
| LangChain/LangGraph | 状态机工作流、Agent构建 | ⭐⭐⭐⭐⭐ |
| 知识图谱 | 图谱推理、学习路径优化 | ⭐⭐⭐⭐ |
| Memory机制 | 三层Memory设计 | ⭐⭐⭐⭐⭐ |
| 向量数据库 | Milvus、ChromaDB | ⭐⭐⭐⭐ |
| Python/SQL | 基础能力 | ⭐⭐⭐ |
一、Python基础能力拷打
1.1 基础语法(难度:⭐⭐)
Q1: 请解释Python中的深拷贝和浅拷贝的区别,并举例说明在RAG系统中可能遇到的问题。
python
# 追问:以下代码输出什么?
import copy
class Document:
def __init__(self, content, metadata):
self.content = content
self.metadata = metadata
doc1 = Document("Hello", {"author": "张三", "tags": ["AI", "RAG"]})
doc2 = copy.copy(doc1) # 浅拷贝
doc3 = copy.deepcopy(doc1) # 深拷贝
doc2.metadata["tags"].append("LLM")
doc3.metadata["tags"].append("Vector")
print(doc1.metadata["tags"]) # 输出什么?
print(doc2.metadata["tags"]) # 输出什么?
print(doc3.metadata["tags"]) # 输出什么?
预期答案:
doc1.metadata["tags"]→["AI", "RAG", "LLM"](浅拷贝共享嵌套对象)doc2.metadata["tags"]→["AI", "RAG", "LLM"](与doc1共享)doc3.metadata["tags"]→["AI", "RAG", "Vector"](深拷贝独立)
追问:在RAG系统中,如果文档对象被多个检索器共享,浅拷贝可能导致什么问题?
1.2 异步编程(难度:⭐⭐⭐)
Q2: 你的简历提到"批量简历处理",请解释以下代码的问题并优化:
python
# 候选人可能写的代码
async def process_resumes(resume_list):
results = []
for resume in resume_list:
result = await parse_resume(resume) # 单个解析
results.append(result)
return results
# 问题:这段代码有什么性能问题?如何优化?
预期答案:
python
# 优化方案1:并发执行
async def process_resumes_v1(resume_list):
tasks = [parse_resume(resume) for resume in resume_list]
results = await asyncio.gather(*tasks)
return results
# 优化方案2:限制并发数
async def process_resumes_v2(resume_list, max_concurrent=10):
semaphore = asyncio.Semaphore(max_concurrent)
async def limited_parse(resume):
async with semaphore:
return await parse_resume(resume)
tasks = [limited_parse(resume) for resume in resume_list]
results = await asyncio.gather(*tasks)
return results
追问:
- 为什么需要限制并发数?
- 如果parse_resume内部是CPU密集型操作,asyncio还有用吗?
1.3 装饰器与上下文管理器(难度:⭐⭐⭐⭐)
Q3: 请实现一个计时装饰器,用于统计RAG检索各阶段的耗时:
python
# 要求:
# 1. 支持同步和异步函数
# 2. 支持嵌套调用时正确显示层级
# 3. 输出格式:[RAG] vector_search took 0.123s
# 请现场实现
预期答案:
python
import time
import functools
from contextvars import ContextVar
# 用于追踪调用层级
call_depth = ContextVar('call_depth', default=0)
def timing_decorator(stage_name):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
depth = call_depth.get()
call_depth.set(depth + 1)
indent = " " * depth
start = time.time()
try:
return func(*args, **kwargs)
finally:
elapsed = time.time() - start
print(f"{indent}[RAG] {stage_name} took {elapsed:.3f}s")
call_depth.set(depth)
@functools.wraps(func)
async def async_wrapper(*args, **kwargs):
depth = call_depth.get()
call_depth.set(depth + 1)
indent = " " * depth
start = time.time()
try:
return await func(*args, **kwargs)
finally:
elapsed = time.time() - start
print(f"{indent}[RAG] {stage_name} took {elapsed:.3f}s")
call_depth.set(depth)
import asyncio
if asyncio.iscoroutinefunction(func):
return async_wrapper
return wrapper
return decorator
# 使用示例
@timing_decorator("vector_search")
def vector_search(query):
time.sleep(0.1)
return ["doc1", "doc2"]
@timing_decorator("rerank")
def rerank(docs):
time.sleep(0.05)
return docs[:5]
@timing_decorator("full_retrieval")
def full_retrieval(query):
docs = vector_search(query)
return rerank(docs)
# 输出:
# [RAG] vector_search took 0.100s
# [RAG] rerank took 0.050s
# [RAG] full_retrieval took 0.150s
二、SQL与数据库能力拷打
2.1 SQL查询优化(难度:⭐⭐⭐)
Q4: 你的简历提到"错题记录、知识点标签",请设计表结构并优化以下查询:
sql
-- 需求:查询每个学生的薄弱知识点(正确率低于60%的知识点)
-- 已有表:students, questions, knowledge_points, student_answers
-- 请写出SQL并分析性能瓶颈
预期答案:
sql
-- 方案1:基础查询
SELECT
s.student_id,
s.student_name,
kp.knowledge_point_id,
kp.knowledge_point_name,
COUNT(*) as total_questions,
SUM(CASE WHEN sa.is_correct THEN 1 ELSE 0 END) as correct_count,
SUM(CASE WHEN sa.is_correct THEN 1 ELSE 0 END) * 100.0 / COUNT(*) as accuracy_rate
FROM students s
JOIN student_answers sa ON s.student_id = sa.student_id
JOIN questions q ON sa.question_id = q.question_id
JOIN question_knowledge_points qkp ON q.question_id = qkp.question_id
JOIN knowledge_points kp ON qkp.knowledge_point_id = kp.knowledge_point_id
GROUP BY s.student_id, s.student_name, kp.knowledge_point_id, kp.knowledge_point_name
HAVING SUM(CASE WHEN sa.is_correct THEN 1 ELSE 0 END) * 100.0 / COUNT(*) < 60
ORDER BY s.student_id, accuracy_rate ASC;
-- 性能分析:
-- 1. 多表JOIN可能导致性能问题
-- 2. GROUP BY + HAVING在大数据量下较慢
-- 3. 建议添加索引:
-- - student_answers(student_id, question_id)
-- - question_knowledge_points(question_id, knowledge_point_id)
追问:
- 如果数据量达到千万级,如何优化?
- 是否考虑用Redis缓存热点数据?
2.2 向量数据库(难度:⭐⭐⭐⭐)
Q5: 你使用过Milvus,请解释以下概念并回答问题:
python
# 问题1:以下索引类型有什么区别?如何选择?
# - FLAT
# - IVF_FLAT
# - IVF_PQ
# - HNSW
# 问题2:以下代码有什么问题?
from pymilvus import Collection
collection = Collection("documents")
collection.load()
# 搜索
results = collection.search(
data=[query_vector],
anns_field="embedding",
param={"metric_type": "L2", "params": {"nprobe": 10}},
limit=10
)
预期答案:
索引类型对比:
| 索引类型 | 特点 | 适用场景 |
|---|---|---|
| FLAT | 暴力搜索,精度最高 | 数据量小(<10万) |
| IVF_FLAT | 聚类分桶,精度与速度平衡 | 中等数据量(10-100万) |
| IVF_PQ | 量化压缩,内存占用低 | 大数据量、内存受限 |
| HNSW | 图索引,速度快 | 实时性要求高 |
代码问题:
- 没有指定搜索参数中的
nprobe应该根据索引类型调整 - 没有处理加载状态检查
- 没有考虑分区搜索
追问:
- 如果向量维度从768增加到1536,对性能有什么影响?
- 如何实现混合检索(向量+标量过滤)?
三、LangChain/LangGraph深度拷打
3.1 LangChain核心概念(难度:⭐⭐⭐)
Q6: 请解释LangChain中以下组件的作用和关系:
python
# 请解释以下代码的工作流程
from langchain.chains import RetrievalQA
from langchain.vectorstores import Chroma
from langchain.embeddings import OpenAIEmbeddings
from langchain.llms import OpenAI
# 1. 这里的embedding模型和LLM模型有什么区别?
# 2. RetrievalQA内部是如何工作的?
# 3. 如果要自定义prompt模板,如何修改?
vectorstore = Chroma(
embedding_function=OpenAIEmbeddings(),
persist_directory="./chroma_db"
)
qa_chain = RetrievalQA.from_chain_type(
llm=OpenAI(),
chain_type="stuff",
retriever=vectorstore.as_retriever(search_kwargs={"k": 4})
)
result = qa_chain.run("什么是RAG?")
预期答案:
-
Embedding模型 vs LLM模型:
- Embedding模型:将文本转换为向量,用于相似度计算
- LLM模型:生成文本,用于回答问题
-
RetrievalQA工作流程:
用户问题 → Embedding编码 → 向量检索 → 获取Top-K文档 → 构建Prompt(问题+文档) → LLM生成 → 返回答案 -
自定义Prompt模板:
python
from langchain.prompts import PromptTemplate
prompt_template = """你是一个专业的AI助手。请基于以下参考文档回答问题。
参考文档:
{context}
问题:{question}
请给出准确、详细的回答:"""
PROMPT = PromptTemplate(
template=prompt_template,
input_variables=["context", "question"]
)
qa_chain = RetrievalQA.from_chain_type(
llm=OpenAI(),
chain_type="stuff",
retriever=vectorstore.as_retriever(search_kwargs={"k": 4}),
chain_type_kwargs={"prompt": PROMPT}
)
3.2 LangGraph状态机(难度:⭐⭐⭐⭐⭐)
Q7: 你的简历提到"基于LangGraph设计简历处理状态机工作流",请回答:
python
# 问题:以下代码实现了什么功能?有什么问题?
from langgraph.graph import StateGraph, END
from typing import TypedDict
class ResumeState(TypedDict):
resume_text: str
parsed_data: dict
screening_result: dict
is_qualified: bool
def parse_resume(state: ResumeState) -> ResumeState:
# 解析简历
parsed = llm.invoke(f"解析以下简历:{state['resume_text']}")
state["parsed_data"] = parsed
return state
def screen_resume(state: ResumeState) -> ResumeState:
# 筛选简历
result = llm.invoke(f"筛选以下简历:{state['parsed_data']}")
state["screening_result"] = result
state["is_qualified"] = result.get("qualified", False)
return state
# 构建图
graph = StateGraph(ResumeState)
graph.add_node("parse", parse_resume)
graph.add_node("screen", screen_resume)
graph.set_entry_point("parse")
graph.add_edge("parse", "screen")
graph.add_edge("screen", END)
app = graph.compile()
# 问题:
# 1. 这个状态机有什么问题?
# 2. 如果解析失败怎么办?
# 3. 如何添加重试机制?
# 4. 如何添加条件分支(不合格直接结束)?
预期答案:
python
from langgraph.graph import StateGraph, END
from langgraph.checkpoint.memory import MemorySaver
from typing import TypedDict, Optional
import logging
class ResumeState(TypedDict):
resume_text: str
parsed_data: Optional[dict]
screening_result: Optional[dict]
is_qualified: Optional[bool]
error: Optional[str]
retry_count: int
# 1. 添加错误处理
def parse_resume(state: ResumeState) -> ResumeState:
try:
parsed = llm.invoke(f"解析以下简历:{state['resume_text']}")
state["parsed_data"] = parsed
state["error"] = None
except Exception as e:
state["error"] = str(e)
logging.error(f"解析失败: {e}")
return state
# 2. 添加重试逻辑
def should_retry(state: ResumeState) -> str:
if state.get("parsed_data"):
return "screen"
if state.get("retry_count", 0) < 3:
state["retry_count"] = state.get("retry_count", 0) + 1
return "parse"
return "error"
# 3. 条件分支
def check_qualified(state: ResumeState) -> str:
if state.get("is_qualified"):
return "save"
return "reject"
# 构建完整的状态机
graph = StateGraph(ResumeState)
# 添加节点
graph.add_node("parse", parse_resume)
graph.add_node("screen", screen_resume)
graph.add_node("save", save_to_db)
graph.add_node("reject", send_rejection)
graph.add_node("error", handle_error)
# 设置入口
graph.set_entry_point("parse")
# 添加条件边
graph.add_conditional_edges(
"parse",
should_retry,
{
"screen": "screen",
"parse": "parse", # 重试
"error": "error"
}
)
graph.add_conditional_edges(
"screen",
check_qualified,
{
"save": "save",
"reject": "reject"
}
)
# 添加结束边
graph.add_edge("save", END)
graph.add_edge("reject", END)
graph.add_edge("error", END)
# 编译(带检查点)
checkpointer = MemorySaver()
app = graph.compile(checkpointer=checkpointer)
追问:
- LangGraph和LangChain Chain有什么区别?
- 如何实现人工审核节点?
- 如何持久化状态?
3.3 Memory机制(难度:⭐⭐⭐⭐⭐)
Q8: 你的简历提到"设计教育场景三层Memory机制",请深入回答:
python
# 问题1:请解释你的三层Memory是如何设计的?
# 问题2:以下代码实现了短期记忆,有什么问题?
class ShortTermMemory:
def __init__(self, max_messages=10):
self.messages = []
self.max_messages = max_messages
def add(self, message):
self.messages.append(message)
if len(self.messages) > self.max_messages:
self.messages.pop(0)
def get_context(self):
return "\n".join(self.messages)
# 问题3:如何实现摘要压缩?请写出伪代码
# 问题4:长期记忆如何与短期记忆结合?
预期答案:
三层Memory架构:
┌─────────────────────────────────────────┐
│ 即时记忆(Immediate) │
│ 当前对话上下文(LLM窗口) │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ 短期记忆(Short-term) │
│ 会话历史(Redis/内存,TTL=1小时) │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ 长期记忆(Long-term) │
│ 用户画像/知识库(向量数据库/Neo4j) │
└─────────────────────────────────────────┘
短期记忆问题:
- 没有考虑token限制
- 没有重要性权重
- 没有时间衰减
摘要压缩实现:
python
from langchain_openai import ChatOpenAI
from langchain.prompts import ChatPromptTemplate
class SummaryMemory:
def __init__(self, llm_model="gpt-4", max_tokens=2000):
self.llm = ChatOpenAI(model=llm_model, temperature=0.3)
self.max_tokens = max_tokens
self.messages = []
self.summary = ""
def add(self, message: str):
self.messages.append(message)
# 检查是否需要压缩
if self._estimate_tokens() > self.max_tokens:
self._compress()
def _estimate_tokens(self) -> int:
# 简化估算:1 token ≈ 4 characters
total_chars = len(self.summary) + sum(len(m) for m in self.messages)
return total_chars // 4
def _compress(self):
# 保留最近5条消息
to_compress = self.messages[:-5]
recent = self.messages[-5:]
# 构建压缩Prompt
prompt = ChatPromptTemplate.from_messages([
("system", """请将以下对话历史压缩为简洁的摘要。
要求:
1. 保留关键信息:用户意图、重要决策、任务状态
2. 保留用户偏好和个性化信息
3. 省略寒暄、重复内容"""),
("human", "对话历史:\n{conversation}\n\n摘要:")
])
chain = prompt | self.llm
result = chain.invoke({
"conversation": "\n".join(to_compress)
})
# 更新摘要
self.summary = f"{self.summary}\n{result.content}" if self.summary else result.content
self.messages = recent
def get_context(self) -> str:
parts = []
if self.summary:
parts.append(f"【历史摘要】\n{self.summary}")
if self.messages:
parts.append(f"【最近对话】\n" + "\n".join(self.messages))
return "\n\n".join(parts)
追问:
- 如何判断哪些信息应该存入长期记忆?
- 如何实现记忆的重要性评分?
- MemGPT的Core Memory和Archival Memory有什么区别?
四、RAG系统深度拷打
4.1 RAG架构设计(难度:⭐⭐⭐⭐)
Q9: 你的简历提到"语义检索、关键词召回和精排策略",请回答:
python
# 问题1:请画出你设计的RAG架构图
# 问题2:以下代码实现了混合检索,有什么问题?
def hybrid_search(query, vector_store, keyword_index, alpha=0.5):
# 向量检索
vector_results = vector_store.similarity_search(query, k=10)
# 关键词检索
keyword_results = keyword_index.search(query, k=10)
# 简单合并
all_results = vector_results + keyword_results
return all_results[:10]
# 问题3:如何实现RRF融合?请写出公式和代码
预期答案:
RAG架构图:
┌─────────────────────────────────────────────────────────┐
│ 用户Query │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ Query预处理 │
│ - Query改写/扩展 │
│ - 实体识别 │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ 多路召回层 │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │向量检索 │ │关键词检索 │ │图谱检索 │ │
│ │Top-100 │ │Top-50 │ │Top-30 │ │
│ └──────────┘ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ 融合排序层 │
│ - RRF融合 │
│ - 去重合并 │
│ - 候选文档池 Top-150 │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ 精排层 │
│ - Reranker模型重排 │
│ - 业务规则增强 │
│ - 最终结果 Top-10 │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ 生成层 │
│ - Prompt构建 │
│ - LLM生成 │
│ - 答案后处理 │
└─────────────────────────────────────────────────────────┘
混合检索问题:
- 没有分数归一化
- 没有去重
- 简单截断,没有融合策略
RRF融合实现:
python
def rrf_fusion(
vector_results: List[Tuple[str, float]],
keyword_results: List[Tuple[str, float]],
k: int = 60
) -> List[Tuple[str, float]]:
"""
RRF (Reciprocal Rank Fusion) 融合
公式: RRF(d) = Σ 1 / (k + rank(d))
Args:
vector_results: [(doc_id, score), ...]
keyword_results: [(doc_id, score), ...]
k: RRF参数,通常取60
"""
rrf_scores = {}
# 向量检索结果
for rank, (doc_id, _) in enumerate(vector_results, 1):
if doc_id not in rrf_scores:
rrf_scores[doc_id] = 0
rrf_scores[doc_id] += 1 / (k + rank)
# 关键词检索结果
for rank, (doc_id, _) in enumerate(keyword_results, 1):
if doc_id not in rrf_scores:
rrf_scores[doc_id] = 0
rrf_scores[doc_id] += 1 / (k + rank)
# 排序
sorted_results = sorted(
rrf_scores.items(),
key=lambda x: x[1],
reverse=True
)
return sorted_results
追问:
- RRF中的k参数如何选择?
- 如何评估融合效果?
- 如果向量检索和关键词检索结果差异很大怎么办?
4.2 Reranker策略(难度:⭐⭐⭐⭐⭐)
Q10: 你的简历提到"精排策略优化题目匹配效果",请深入回答:
python
# 问题1:什么是Cross-Encoder和Bi-Encoder?有什么区别?
# 问题2:以下代码使用BGE-Reranker,有什么问题?
from FlagEmbedding import FlagReranker
reranker = FlagReranker('BAAI/bge-reranker-large', use_fp16=True)
def rerank(query, documents):
pairs = [[query, doc] for doc in documents]
scores = reranker.compute_score(pairs)
# 直接返回排序结果
results = list(zip(documents, scores))
results.sort(key=lambda x: x[1], reverse=True)
return results[:10]
# 问题3:如何优化Reranker的性能?
预期答案:
Cross-Encoder vs Bi-Encoder:
| 维度 | Bi-Encoder | Cross-Encoder |
|---|---|---|
| 编码方式 | Query和Doc分别编码 | Query和Doc联合编码 |
| 计算速度 | 快(可预计算) | 慢(需实时计算) |
| 精度 | 较低 | 较高 |
| 交互能力 | 无深层交互 | 深层语义交互 |
| 适用场景 | 粗排(召回) | 精排(重排) |
代码问题:
- 没有批量处理优化
- 没有缓存机制
- 没有处理长文档截断
性能优化:
python
from FlagEmbedding import FlagReranker
from functools import lru_cache
import hashlib
class OptimizedReranker:
def __init__(self, model_name='BAAI/bge-reranker-large', batch_size=32):
self.reranker = FlagReranker(model_name, use_fp16=True)
self.batch_size = batch_size
self.cache = {}
def _get_cache_key(self, query: str, doc: str) -> str:
"""生成缓存Key"""
content = f"{query}|||{doc}"
return hashlib.md5(content.encode()).hexdigest()
def rerank(
self,
query: str,
documents: List[str],
top_k: int = 10,
use_cache: bool = True
) -> List[Tuple[str, float]]:
"""
优化后的重排
优化点:
1. 批量处理
2. 缓存机制
3. 长文档截断
"""
results = []
uncached_docs = []
uncached_indices = []
# 1. 检查缓存
for i, doc in enumerate(documents):
if use_cache:
cache_key = self._get_cache_key(query, doc)
if cache_key in self.cache:
results.append((doc, self.cache[cache_key], i))
continue
uncached_docs.append(doc)
uncached_indices.append(i)
# 2. 批量处理未缓存的文档
if uncached_docs:
# 截断长文档
max_length = 512
truncated_docs = [
doc[:max_length] if len(doc) > max_length else doc
for doc in uncached_docs
]
# 批量推理
all_scores = []
for i in range(0, len(truncated_docs), self.batch_size):
batch_docs = truncated_docs[i:i + self.batch_size]
pairs = [[query, doc] for doc in batch_docs]
batch_scores = self.reranker.compute_score(
pairs,
normalize=True
)
all_scores.extend(batch_scores)
# 更新结果和缓存
for doc, score, idx in zip(uncached_docs, all_scores, uncached_indices):
results.append((doc, score, idx))
if use_cache:
cache_key = self._get_cache_key(query, doc)
self.cache[cache_key] = score
# 3. 排序并返回
results.sort(key=lambda x: x[1], reverse=True)
return [(doc, score) for doc, score, _ in results[:top_k]]
追问:
- ColBERT的Late Interaction机制是什么?
- 如何选择Reranker模型?
- Reranker的延迟如何优化?
五、知识图谱深度拷打
5.1 图谱构建(难度:⭐⭐⭐⭐)
Q11: 你的简历提到"知识图谱推理、学习路径优化",请回答:
python
# 问题1:请解释你构建的教育知识图谱包含哪些实体和关系?
# 问题2:以下Cypher查询有什么问题?
MATCH (s:Student)-[:ANSWERED]->(q:Question)-[:BELONGS_TO]->(kp:KnowledgePoint)
WHERE s.id = $student_id
RETURN kp.name, COUNT(q) as question_count
ORDER BY question_count DESC
# 问题3:如何实现"查找学生的知识薄弱点"?
预期答案:
教育知识图谱实体和关系:
实体类型:
- Student(学生)
- Question(题目)
- KnowledgePoint(知识点)
- Chapter(章节)
- Subject(学科)
关系类型:
- BELONGS_TO(属于)
- PREREQUISITE(前置依赖)
- RELATED_TO(相关)
- MASTERED(掌握)
- WEAK_IN(薄弱)
Cypher查询问题:
- 没有考虑正确率
- 没有过滤时间范围
- 性能可能有问题(缺少索引)
查找知识薄弱点:
cypher
// 查询学生的薄弱知识点(正确率低于60%)
MATCH (s:Student {id: $student_id})-[a:ANSWERED]->(q:Question)-[:BELONGS_TO]->(kp:KnowledgePoint)
WITH kp,
COUNT(q) as total_questions,
SUM(CASE WHEN a.is_correct THEN 1 ELSE 0 END) as correct_count,
SUM(CASE WHEN a.is_correct THEN 1 ELSE 0 END) * 100.0 / COUNT(q) as accuracy_rate
WHERE accuracy_rate < 60
RETURN kp.name as knowledge_point,
total_questions,
correct_count,
accuracy_rate
ORDER BY accuracy_rate ASC
LIMIT 10;
追问:
- 如何实现学习路径推荐?
- 知识图谱和向量数据库如何结合?
- GraphRAG是什么?
5.2 图算法(难度:⭐⭐⭐⭐⭐)
Q12: 你的简历提到"图论、拓扑排序",请回答:
python
# 问题1:拓扑排序在教育场景中有什么应用?
# 问题2:请实现一个基于拓扑排序的学习路径推荐算法
# 问题3:以下代码有什么问题?
from collections import defaultdict, deque
def topological_sort(graph):
in_degree = defaultdict(int)
for node in graph:
for neighbor in graph[node]:
in_degree[neighbor] += 1
queue = deque([node for node in graph if in_degree[node] == 0])
result = []
while queue:
node = queue.popleft()
result.append(node)
for neighbor in graph[node]:
in_degree[neighbor] -= 1
if in_degree[neighbor] == 0:
queue.append(neighbor)
return result
预期答案:
拓扑排序应用:
- 学习路径规划:确保先学前置知识点
- 课程安排:避免循环依赖
- 任务调度:确定执行顺序
学习路径推荐算法:
python
from collections import defaultdict, deque
from typing import List, Dict, Set
class LearningPathRecommender:
"""
基于拓扑排序的学习路径推荐
考虑因素:
1. 知识点依赖关系
2. 学生已掌握的知识点
3. 学生薄弱的知识点
"""
def __init__(self, knowledge_graph):
self.graph = knowledge_graph
def recommend_path(
self,
target_knowledge_point: str,
mastered_points: Set[str],
weak_points: Set[str]
) -> List[str]:
"""
推荐学习路径
Args:
target_knowledge_point: 目标知识点
mastered_points: 已掌握的知识点
weak_points: 薄弱的知识点
Returns:
学习路径(知识点列表)
"""
# 1. 获取目标知识点的所有前置依赖
prerequisites = self._get_all_prerequisites(target_knowledge_point)
# 2. 过滤已掌握的知识点
to_learn = prerequisites - mastered_points
# 3. 构建子图
subgraph = self._build_subgraph(to_learn)
# 4. 拓扑排序
sorted_path = self._topological_sort_with_priority(
subgraph,
weak_points
)
return sorted_path
def _get_all_prerequisites(self, kp: str) -> Set[str]:
"""获取所有前置知识点(递归)"""
visited = set()
self._dfs_prerequisites(kp, visited)
return visited
def _dfs_prerequisites(self, kp: str, visited: Set[str]):
"""DFS遍历前置知识点"""
if kp in visited:
return
visited.add(kp)
# 获取直接前置知识点
direct_prereqs = self.graph.get_prerequisites(kp)
for prereq in direct_prereqs:
self._dfs_prerequisites(prereq, visited)
def _build_subgraph(self, nodes: Set[str]) -> Dict[str, List[str]]:
"""构建子图"""
subgraph = defaultdict(list)
for node in nodes:
prereqs = self.graph.get_prerequisites(node)
for prereq in prereqs:
if prereq in nodes:
subgraph[prereq].append(node)
return subgraph
def _topological_sort_with_priority(
self,
graph: Dict[str, List[str]],
priority_nodes: Set[str]
) -> List[str]:
"""
带优先级的拓扑排序
薄弱知识点优先学习
"""
in_degree = defaultdict(int)
for node in graph:
for neighbor in graph[node]:
in_degree[neighbor] += 1
# 使用优先队列,薄弱知识点优先
import heapq
queue = []
for node in graph:
if in_degree[node] == 0:
# 薄弱知识点优先级更高(负数)
priority = -1 if node in priority_nodes else 0
heapq.heappush(queue, (priority, node))
result = []
while queue:
_, node = heapq.heappop(queue)
result.append(node)
for neighbor in graph[node]:
in_degree[neighbor] -= 1
if in_degree[neighbor] == 0:
priority = -1 if neighbor in priority_nodes else 0
heapq.heappush(queue, (priority, neighbor))
return result
代码问题:
- 没有处理环的情况
- 没有处理孤立节点
- 没有返回所有节点(如果存在环)
追问:
- 如何检测知识图谱中的循环依赖?
- 如何处理大规模知识图谱的性能问题?
- PageRank算法在知识图谱中有什么应用?
六、大模型选型与认知
6.1 模型选型(难度:⭐⭐⭐⭐)
Q13: 你的简历提到"多模型路由",请回答:
python
# 问题1:在RAG系统中,如何选择合适的Embedding模型?
# 问题2:以下模型有什么区别?适用什么场景?
# - GPT-4
# - GPT-3.5-turbo
# - Claude-3
# - 文心一言
# - 通义千问
# - DeepSeek
# 问题3:如何设计一个多模型路由系统?
预期答案:
Embedding模型选型:
| 模型 | 维度 | 特点 | 适用场景 |
|---|---|---|---|
| BGE-large-zh | 1024 | 中文效果好 | 中文RAG |
| OpenAI text-embedding-3 | 1536/3072 | 多语言 | 多语言场景 |
| Cohere embed-v3 | 1024 | 检索优化 | 检索密集型 |
LLM模型对比:
| 模型 | 优势 | 劣势 | 适用场景 |
|---|---|---|---|
| GPT-4 | 推理能力强 | 价格高 | 复杂推理 |
| GPT-3.5-turbo | 性价比高 | 推理弱 | 简单任务 |
| Claude-3 | 长上下文 | 中文弱 | 长文档 |
| 文心一言 | 中文好 | 推理弱 | 中文场景 |
| DeepSeek | 性价比高 | 生态弱 | 成本敏感 |
多模型路由设计:
python
from typing import Dict, Any
from enum import Enum
import time
class TaskType(Enum):
SIMPLE_QA = "simple_qa"
COMPLEX_REASONING = "complex_reasoning"
LONG_CONTEXT = "long_context"
CODE_GENERATION = "code_generation"
class ModelRouter:
"""
多模型路由系统
路由策略:
1. 根据任务类型选择模型
2. 根据成本预算选择模型
3. 根据延迟要求选择模型
"""
def __init__(self):
self.models = {
"gpt-4": {"cost": 0.03, "latency": 2.0, "capability": 0.95},
"gpt-3.5-turbo": {"cost": 0.002, "latency": 0.5, "capability": 0.7},
"claude-3": {"cost": 0.015, "latency": 1.5, "capability": 0.9},
"deepseek": {"cost": 0.001, "latency": 0.8, "capability": 0.75},
}
self.task_model_mapping = {
TaskType.SIMPLE_QA: ["gpt-3.5-turbo", "deepseek"],
TaskType.COMPLEX_REASONING: ["gpt-4", "claude-3"],
TaskType.LONG_CONTEXT: ["claude-3"],
TaskType.CODE_GENERATION: ["gpt-4", "deepseek"],
}
def route(
self,
task_type: TaskType,
prompt: str,
max_cost: float = None,
max_latency: float = None
) -> str:
"""
路由到最合适的模型
Args:
task_type: 任务类型
prompt: 输入提示
max_cost: 最大成本限制
max_latency: 最大延迟限制
Returns:
模型名称
"""
candidates = self.task_model_mapping.get(task_type, ["gpt-3.5-turbo"])
# 过滤满足约束的模型
valid_models = []
for model_name in candidates:
model_info = self.models[model_name]
if max_cost and model_info["cost"] > max_cost:
continue
if max_latency and model_info["latency"] > max_latency:
continue
valid_models.append((model_name, model_info))
if not valid_models:
# 没有满足约束的模型,选择最便宜的
return min(self.models.items(), key=lambda x: x[1]["cost"])[0]
# 选择能力最强的模型
valid_models.sort(key=lambda x: x[1]["capability"], reverse=True)
return valid_models[0][0]
def invoke(
self,
task_type: TaskType,
prompt: str,
**kwargs
) -> str:
"""执行推理"""
model_name = self.route(task_type, prompt, **kwargs)
# 调用对应模型
# ...
return model_name
追问:
- 如何评估不同模型在特定任务上的效果?
- 如何实现模型的A/B测试?
- 如何处理模型调用失败的情况?
6.2 Prompt工程(难度:⭐⭐⭐)
Q14: 请优化以下Prompt:
python
# 原始Prompt
prompt = "请回答以下问题:" + question
# 问题:
# 1. 这个Prompt有什么问题?
# 2. 如何优化?
# 3. 如何评估Prompt效果?
预期答案:
问题分析:
- 没有角色定义
- 没有上下文
- 没有输出格式要求
- 没有约束条件
优化方案:
python
# 优化后的Prompt
prompt = """你是一个专业的AI教育助手,擅长解答学生的学习问题。
## 任务
请回答学生的以下问题。
## 要求
1. 回答要准确、详细、有条理
2. 如果涉及知识点,请解释清楚
3. 如果问题不明确,请追问澄清
4. 回答长度控制在200-500字
## 输出格式
【核心答案】
...
【知识点】
...
【拓展建议】
...
## 学生问题
{question}
## 请回答:"""
Prompt评估方法:
python
def evaluate_prompt(prompt_template, test_cases):
"""
评估Prompt效果
Args:
prompt_template: Prompt模板
test_cases: 测试用例 [{"question": ..., "expected": ...}, ...]
"""
results = []
for case in test_cases:
prompt = prompt_template.format(question=case["question"])
response = llm.invoke(prompt)
# 评估
score = evaluate_response(response, case["expected"])
results.append({
"question": case["question"],
"response": response,
"expected": case["expected"],
"score": score
})
return {
"avg_score": sum(r["score"] for r in results) / len(results),
"details": results
}
七、机器视觉与机器学习
7.1 机器视觉(难度:⭐⭐⭐)
Q15: 你的简历提到"PaddleOCR + NLP",请回答:
python
# 问题1:OCR在简历解析中有什么难点?
# 问题2:如何提高OCR的准确率?
# 问题3:以下代码有什么问题?
from paddleocr import PaddleOCR
ocr = PaddleOCR(use_angle_cls=True, lang='ch')
def parse_resume(image_path):
result = ocr.ocr(image_path, cls=True)
# 直接返回所有文本
text = ""
for line in result:
text += line[1][0] + "\n"
return text
预期答案:
OCR难点:
- 排版复杂:表格、多栏
- 字体多样:手写、艺术字
- 图像质量:模糊、倾斜、水印
- 结构化提取:需要理解语义
提高准确率:
- 图像预处理:去噪、矫正
- 模型优化:微调、集成
- 后处理:纠错、规则修正
代码问题:
- 没有图像预处理
- 没有结构化提取
- 没有错误处理
优化方案:
python
from paddleocr import PaddleOCR
import cv2
import numpy as np
class ResumeOCRParser:
def __init__(self):
self.ocr = PaddleOCR(
use_angle_cls=True,
lang='ch',
use_gpu=True
)
def preprocess(self, image_path: str) -> np.ndarray:
"""图像预处理"""
img = cv2.imread(image_path)
# 1. 灰度化
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 2. 去噪
denoised = cv2.fastNlMeansDenoising(gray)
# 3. 二值化
_, binary = cv2.threshold(
denoised, 0, 255,
cv2.THRESH_BINARY + cv2.THRESH_OTSU
)
# 4. 倾斜矫正
coords = np.column_stack(np.where(binary > 0))
angle = cv2.minAreaRect(coords)[-1]
if angle < -45:
angle = -(90 + angle)
else:
angle = -angle
(h, w) = binary.shape[:2]
center = (w // 2, h // 2)
M = cv2.getRotationMatrix2D(center, angle, 1.0)
rotated = cv2.warpAffine(
binary, M, (w, h),
flags=cv2.INTER_CUBIC,
borderMode=cv2.BORDER_REPLICATE
)
return rotated
def parse(self, image_path: str) -> dict:
"""解析简历"""
# 1. 预处理
processed_img = self.preprocess(image_path)
# 2. OCR识别
result = self.ocr.ocr(processed_img, cls=True)
# 3. 结构化提取
text_blocks = []
for line in result:
for word_info in line:
text_blocks.append({
"text": word_info[1][0],
"confidence": word_info[1][1],
"position": word_info[0]
})
# 4. 按位置排序
text_blocks.sort(key=lambda x: (x["position"][0][1], x["position"][0][0]))
# 5. 提取结构化信息
structured_info = self._extract_structured_info(text_blocks)
return structured_info
def _extract_structured_info(self, text_blocks: list) -> dict:
"""提取结构化信息"""
# 使用规则或LLM提取
# ...
pass
追问:
- 如何处理表格识别?
- 如何处理手写内容?
- OCR和LLM如何结合?
7.2 机器学习基础(难度:⭐⭐⭐)
Q16: 请回答以下机器学习问题:
python
# 问题1:什么是过拟合?如何防止?
# 问题2:请解释以下评估指标:
# - Precision
# - Recall
# - F1-Score
# - AUC-ROC
# 问题3:在推荐系统中,如何评估模型效果?
预期答案:
过拟合与防止:
| 方法 | 说明 |
|---|---|
| 正则化 | L1/L2正则化 |
| Dropout | 随机丢弃神经元 |
| 早停 | 监控验证集 |
| 数据增强 | 增加训练数据 |
| 交叉验证 | K折交叉验证 |
评估指标:
python
# Precision = TP / (TP + FP)
# Recall = TP / (TP + FN)
# F1 = 2 * (Precision * Recall) / (Precision + Recall)
def calculate_metrics(y_true, y_pred, y_prob):
from sklearn.metrics import (
precision_score,
recall_score,
f1_score,
roc_auc_score
)
return {
"precision": precision_score(y_true, y_pred),
"recall": recall_score(y_true, y_pred),
"f1": f1_score(y_true, y_pred),
"auc_roc": roc_auc_score(y_true, y_prob)
}
推荐系统评估:
python
# 离线指标
- Hit Rate @ K
- NDCG @ K
- MAP @ K
# 在线指标
- 点击率(CTR)
- 转化率(CVR)
- 用户停留时间
八、Git与部署
8.1 Git工作流(难度:⭐⭐)
Q17: 请回答以下Git问题:
bash
# 问题1:以下Git工作流有什么问题?
git add .
git commit -m "update"
git push origin main
# 问题2:如何处理合并冲突?
# 问题3:如何回滚到上一个版本?
预期答案:
Git工作流问题:
git add .可能添加不需要的文件- commit message不规范
- 直接push到main分支
规范工作流:
bash
# 1. 创建功能分支
git checkout -b feature/rag-optimization
# 2. 添加文件(选择性)
git add src/rag/optimizer.py
git add tests/test_optimizer.py
# 3. 规范的commit message
git commit -m "feat(rag): add hybrid search with RRF fusion
- Implement RRF fusion for vector and keyword search
- Add configurable k parameter
- Add unit tests for fusion algorithm
Closes #123"
# 4. 推送到远程
git push origin feature/rag-optimization
# 5. 创建Pull Request
合并冲突处理:
bash
# 1. 拉取最新代码
git fetch origin
git rebase origin/main
# 2. 解决冲突
# 编辑冲突文件,选择保留的内容
# 3. 标记解决
git add <conflicted_file>
git rebase --continue
# 4. 推送
git push origin feature/rag-optimization
回滚操作:
bash
# 方式1:创建新commit回滚
git revert <commit_hash>
# 方式2:硬回滚(不推荐)
git reset --hard <commit_hash>
git push origin main --force
8.2 Docker部署(难度:⭐⭐⭐)
Q18: 请回答以下部署问题:
dockerfile
# 问题1:以下Dockerfile有什么问题?
FROM python:3.9
COPY . /app
WORKDIR /app
RUN pip install -r requirements.txt
CMD ["python", "app.py"]
# 问题2:如何优化镜像大小?
# 问题3:如何实现零停机部署?
预期答案:
Dockerfile问题:
- 没有指定具体版本
- 没有利用缓存层
- 没有清理缓存
- 没有非root用户
优化方案:
dockerfile
# 优化后的Dockerfile
FROM python:3.9-slim AS builder
WORKDIR /app
# 1. 先复制依赖文件,利用缓存
COPY requirements.txt .
# 2. 安装依赖
RUN pip install --no-cache-dir -r requirements.txt -t /app/libs
# 3. 第二阶段:运行镜像
FROM python:3.9-slim
WORKDIR /app
# 4. 复制依赖
COPY --from=builder /app/libs /app/libs
ENV PYTHONPATH=/app/libs
# 5. 复制应用代码
COPY src/ /app/src/
# 6. 创建非root用户
RUN useradd -m appuser && chown -R appuser:appuser /app
USER appuser
# 7. 健康检查
HEALTHCHECK --interval=30s --timeout=3s \
CMD curl -f http://localhost:8000/health || exit 1
# 8. 启动命令
CMD ["python", "-m", "uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8000"]
零停机部署:
yaml
# Kubernetes部署配置
apiVersion: apps/v1
kind: Deployment
metadata:
name: rag-api
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
spec:
containers:
- name: rag-api
image: rag-api:v2
readinessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 5
periodSeconds: 10
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 10"]
九、综合面试评分表
评分标准
| 维度 | 权重 | 评分标准 |
|---|---|---|
| Python基础 | 15% | 异步、装饰器、内存管理 |
| SQL/数据库 | 15% | 查询优化、向量数据库 |
| LangChain/LangGraph | 20% | 核心概念、状态机设计 |
| RAG系统 | 25% | 架构设计、优化策略 |
| 知识图谱 | 10% | 图算法、业务应用 |
| 大模型认知 | 10% | 模型选型、Prompt工程 |
| 工程能力 | 5% | Git、Docker、部署 |
预期评分
| 维度 | 预期得分 | 理由 |
|---|---|---|
| Python基础 | 70-80分 | 简历未体现深度,需验证 |
| SQL/数据库 | 75-85分 | 有Milvus经验 |
| LangChain/LangGraph | 80-90分 | 简历提到状态机设计 |
| RAG系统 | 85-95分 | 核心能力,简历详细 |
| 知识图谱 | 75-85分 | 有应用经验 |
| 大模型认知 | 70-80分 | 需验证选型能力 |
| 工程能力 | 70-80分 | 简历未体现 |
十、面试官总结
优势
- 技术栈前沿:RAG、LangGraph、知识图谱都是热门方向
- 项目落地:有政务级项目经验
- 学习能力:连续三年国奖,证明学习能力强
需验证点
- 技术深度:简历描述是否真实?
- 独立能力:是主导还是参与?
- 问题解决:遇到困难如何解决?
建议面试流程
- 一面:Python基础 + SQL + 简历项目深挖
- 二面:RAG架构设计 + LangGraph实战
- 三面:系统设计 + 综合评估
文档版本 : v1.0
生成时间 : 2024年
面试官: 资深AI应用开发工程师