AI应用开发工程师面试题:深度技术拷打

候选人 : 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

追问

  1. 为什么需要限制并发数?
  2. 如果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)

追问

  1. 如果数据量达到千万级,如何优化?
  2. 是否考虑用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 图索引,速度快 实时性要求高

代码问题

  1. 没有指定搜索参数中的nprobe应该根据索引类型调整
  2. 没有处理加载状态检查
  3. 没有考虑分区搜索

追问

  1. 如果向量维度从768增加到1536,对性能有什么影响?
  2. 如何实现混合检索(向量+标量过滤)?

三、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?")

预期答案

  1. Embedding模型 vs LLM模型

    • Embedding模型:将文本转换为向量,用于相似度计算
    • LLM模型:生成文本,用于回答问题
  2. RetrievalQA工作流程

    复制代码
    用户问题 → Embedding编码 → 向量检索 → 获取Top-K文档 → 
    构建Prompt(问题+文档) → LLM生成 → 返回答案
  3. 自定义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)

追问

  1. LangGraph和LangChain Chain有什么区别?
  2. 如何实现人工审核节点?
  3. 如何持久化状态?

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)      │
└─────────────────────────────────────────┘

短期记忆问题

  1. 没有考虑token限制
  2. 没有重要性权重
  3. 没有时间衰减

摘要压缩实现

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)

追问

  1. 如何判断哪些信息应该存入长期记忆?
  2. 如何实现记忆的重要性评分?
  3. 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生成                                                │
│  - 答案后处理                                             │
└─────────────────────────────────────────────────────────┘

混合检索问题

  1. 没有分数归一化
  2. 没有去重
  3. 简单截断,没有融合策略

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

追问

  1. RRF中的k参数如何选择?
  2. 如何评估融合效果?
  3. 如果向量检索和关键词检索结果差异很大怎么办?

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联合编码
计算速度 快(可预计算) 慢(需实时计算)
精度 较低 较高
交互能力 无深层交互 深层语义交互
适用场景 粗排(召回) 精排(重排)

代码问题

  1. 没有批量处理优化
  2. 没有缓存机制
  3. 没有处理长文档截断

性能优化

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]]

追问

  1. ColBERT的Late Interaction机制是什么?
  2. 如何选择Reranker模型?
  3. 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查询问题

  1. 没有考虑正确率
  2. 没有过滤时间范围
  3. 性能可能有问题(缺少索引)

查找知识薄弱点

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;

追问

  1. 如何实现学习路径推荐?
  2. 知识图谱和向量数据库如何结合?
  3. 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

代码问题

  1. 没有处理环的情况
  2. 没有处理孤立节点
  3. 没有返回所有节点(如果存在环)

追问

  1. 如何检测知识图谱中的循环依赖?
  2. 如何处理大规模知识图谱的性能问题?
  3. 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

追问

  1. 如何评估不同模型在特定任务上的效果?
  2. 如何实现模型的A/B测试?
  3. 如何处理模型调用失败的情况?

6.2 Prompt工程(难度:⭐⭐⭐)

Q14: 请优化以下Prompt:

python 复制代码
# 原始Prompt
prompt = "请回答以下问题:" + question

# 问题:
# 1. 这个Prompt有什么问题?
# 2. 如何优化?
# 3. 如何评估Prompt效果?

预期答案

问题分析

  1. 没有角色定义
  2. 没有上下文
  3. 没有输出格式要求
  4. 没有约束条件

优化方案

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难点

  1. 排版复杂:表格、多栏
  2. 字体多样:手写、艺术字
  3. 图像质量:模糊、倾斜、水印
  4. 结构化提取:需要理解语义

提高准确率

  1. 图像预处理:去噪、矫正
  2. 模型优化:微调、集成
  3. 后处理:纠错、规则修正

代码问题

  1. 没有图像预处理
  2. 没有结构化提取
  3. 没有错误处理

优化方案

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

追问

  1. 如何处理表格识别?
  2. 如何处理手写内容?
  3. 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工作流问题

  1. git add . 可能添加不需要的文件
  2. commit message不规范
  3. 直接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问题

  1. 没有指定具体版本
  2. 没有利用缓存层
  3. 没有清理缓存
  4. 没有非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分 简历未体现

十、面试官总结

优势

  1. 技术栈前沿:RAG、LangGraph、知识图谱都是热门方向
  2. 项目落地:有政务级项目经验
  3. 学习能力:连续三年国奖,证明学习能力强

需验证点

  1. 技术深度:简历描述是否真实?
  2. 独立能力:是主导还是参与?
  3. 问题解决:遇到困难如何解决?

建议面试流程

  1. 一面:Python基础 + SQL + 简历项目深挖
  2. 二面:RAG架构设计 + LangGraph实战
  3. 三面:系统设计 + 综合评估

文档版本 : v1.0
生成时间 : 2024年
面试官: 资深AI应用开发工程师

相关推荐
Sunsets_Red2 小时前
乘法逆元的 exgcd 求法
c++·学习·数学·算法·c#·密码学·信息学竞赛
%KT%2 小时前
云端部署大模型+推理
人工智能
qcwl662 小时前
深入理解Linux进程与内存 学习笔记#1
笔记·学习
金士镧(厦门)新材料有限公司2 小时前
氧化镧:现代工业的重要稀土材料
人工智能·科技·安全·全文检索·生活·能源
xuhaoyu_cpp_java2 小时前
Servlet学习
java·笔记·学习
逄逄不是胖胖2 小时前
《动手学深度学习》-68多头注意力实现
人工智能·深度学习
ADHD多动联盟2 小时前
提升学生注意力涣散问题的情绪管理与学习能力策略
学习·学习方法·玩游戏
盘古信息IMS2 小时前
当注塑机开始“思考”:昊方汽车携手盘古信息&中国联通启动IMS V6数智化转型项目
大数据·人工智能·汽车
张张123y2 小时前
知识图谱从0到1:AI应用开发的核心技术
人工智能·langchain·transformer·知识图谱