GLM-4-9B-Chat-1M实战应用:在线教育平台AI助教支持100小时课程长视频理解
GLM-4-9B-Chat-1M实战应用:在线教育平台AI助教支持100小时课程长视频理解
1. 引言:当AI助教遇上超长课程视频
想象一下,你是一家在线教育平台的课程运营负责人。平台上有门爆款课程,总时长超过100小时,包含了讲师授课、案例分析、代码演示、课堂问答等丰富内容。现在,你需要为这门课程开发一个智能助教,让它能回答学员关于课程的任何问题。
传统做法是什么?要么人工观看所有视频并整理知识点,耗时耗力;要么用现有AI模型,但大多数模型只能处理几分钟的短视频或几万字的文本,面对100小时的海量内容,它们要么“记不住”,要么“答非所问”。
这就是我们今天要解决的痛点。借助GLM-4-9B-Chat-1M这个支持100万字上下文的大模型,我们可以构建一个真正能理解超长课程视频的AI助教。它不仅能记住课程里的每一个细节,还能像真人助教一样,根据学员的具体问题,从海量内容中精准找到答案。
本文将带你一步步实现这个应用。我们会从模型部署开始,到构建一个简单易用的对话界面,最后展示如何让这个AI助教处理100小时课程视频的完整流程。即使你没有太多AI部署经验,也能跟着做出来。
2. 为什么选择GLM-4-9B-Chat-1M?
在开始动手之前,我们先简单了解一下为什么这个模型特别适合处理超长教育内容。
2.1 核心优势:1M上下文长度
1M上下文长度意味着什么?简单来说,这个模型能一次性“记住”大约200万个中文字符。换算成视频内容:
- 假设课程视频已经转成了文字稿
- 按照正常的语速,1小时视频大约有9000-12000字
- 100小时课程就是90万到120万字
- GLM-4-9B-Chat-1M的200万字容量完全够用,甚至还能留出空间给学员的问题和模型的回答
这解决了长视频理解的最大难题:信息完整保留。传统模型需要把长文本切分成很多片段,然后分别处理,很容易丢失上下文关联。而GLM-4-9B-Chat-1M能一次性看到全部内容,理解更准确。
2.2 其他重要特性
除了超长上下文,这个模型还有几个对教育场景特别有用的能力:
- 多轮对话:学员可以连续提问,模型能记住之前的对话历史,回答更连贯
- 代码理解与执行:对于编程类课程,模型不仅能解释代码,还能执行简单的代码片段
- 多语言支持:支持26种语言,适合国际化教育平台
- 工具调用:可以扩展其他功能,比如联网搜索最新资料
2.3 性能表现
从官方提供的数据看,在1M上下文长度下进行“大海捞针”测试(在超长文本中寻找特定信息),模型表现很好。这意味着它确实能从百万字的内容中准确找到你需要的信息点。
对于教育场景,这种精准的信息检索能力至关重要。学员问“第三章第二节那个关于递归的例子是怎么讲的?”,模型需要快速定位到具体位置,而不是泛泛而谈。
3. 快速部署:10分钟搭建你的AI助教后端
现在我们来实际部署这个模型。我们使用vLLM来部署,这是一个专门为大规模语言模型设计的高效推理引擎。
3.1 环境准备
首先确保你的环境满足基本要求:
- 操作系统:Linux(推荐Ubuntu 20.04或以上)
- 显卡:至少16GB显存(处理1M上下文需要较大显存)
- 内存:32GB以上
- 存储:50GB可用空间
如果你在云平台上使用,选择配备A100或类似性能显卡的实例会比较合适。
3.2 一键部署命令
最简化的部署方式就是使用预构建的Docker镜像。这里我们假设你已经有了一个可用的镜像,部署命令非常简单:
# 拉取并运行GLM-4-9B-Chat-1M的vLLM服务
docker run -d \
--gpus all \
-p 8000:8000 \
-v /path/to/your/data:/data \
--name glm4-9b-chat-1m \
glm-4-9b-chat-1m-vllm:latest
这个命令做了几件事:
- 使用GPU运行容器
- 将容器的8000端口映射到主机的8000端口
- 挂载一个数据目录,方便后续上传课程资料
- 给容器起个名字方便管理
3.3 验证部署是否成功
部署完成后,我们需要确认服务是否正常运行。有两种简单的方法:
方法一:查看日志
# 进入容器查看日志
docker logs glm4-9b-chat-1m
# 或者直接查看日志文件
cat /root/workspace/llm.log
如果看到类似下面的输出,说明模型正在加载或已经加载成功:
Loading model weights...
Model loaded successfully on GPU 0
Starting vLLM server on port 8000...
方法二:发送测试请求
等模型完全加载后(可能需要几分钟,取决于你的硬件),可以发送一个简单的测试请求:
curl -X POST http://localhost:8000/v1/completions \
-H "Content-Type: application/json" \
-d '{
"model": "glm-4-9b-chat-1m",
"prompt": "你好,请简单介绍一下你自己",
"max_tokens": 100
}'
如果收到正常的JSON响应,说明部署成功。
3.4 重要参数说明
在实际使用中,你可能需要调整一些参数来优化性能。这里有几个关键参数:
# 在调用API时可以设置的参数
params = {
"model": "glm-4-9b-chat-1m",
"messages": [...], # 对话历史
"max_tokens": 4096, # 最大生成长度
"temperature": 0.7, # 创造性程度,0-1之间
"top_p": 0.9, # 核采样参数
"stream": False # 是否流式输出
}
对于教育助教场景,我建议:
temperature设为0.3-0.5,让回答更稳定准确max_tokens根据问题复杂度调整,一般512-1024足够- 开启
stream可以让用户更快看到部分回答,体验更好
4. 构建用户界面:用Chainlit打造对话式前端
模型部署好了,接下来我们需要一个让学员能方便使用的界面。这里我们选择Chainlit,它是一个专门为AI应用设计的聊天界面框架,配置简单,效果专业。
4.1 Chainlit快速安装
# 安装Chainlit
pip install chainlit
# 创建应用文件
touch app.py
4.2 基础界面代码
创建一个简单的app.py文件:
import chainlit as cl
import requests
import json
# Chainlit应用的配置
@cl.on_chat_start
async def start_chat():
# 欢迎消息
await cl.Message(
content="👋 你好!我是课程AI助教,可以回答关于课程的任何问题。请直接提问吧!"
).send()
@cl.on_message
async def handle_message(message: cl.Message):
# 显示用户消息
user_msg = cl.Message(content="")
await user_msg.send()
# 准备调用GLM模型的请求
api_url = "http://localhost:8000/v1/chat/completions"
headers = {"Content-Type": "application/json"}
# 构建对话历史(Chainlit会自动管理)
messages = [
{
"role": "system",
"content": "你是一个专业的在线课程助教。请根据课程内容准确回答学员的问题。如果问题超出课程范围,请如实说明。"
},
{
"role": "user",
"content": message.content
}
]
# 请求数据
data = {
"model": "glm-4-9b-chat-1m",
"messages": messages,
"max_tokens": 1024,
"temperature": 0.3,
"stream": True # 启用流式输出
}
# 发送请求并流式显示回复
response = requests.post(api_url, headers=headers, json=data, stream=True)
assistant_msg = cl.Message(content="")
await assistant_msg.send()
# 处理流式响应
for line in response.iter_lines():
if line:
line = line.decode('utf-8')
if line.startswith("data: "):
json_str = line[6:] # 去掉"data: "前缀
if json_str != "[DONE]":
try:
chunk = json.loads(json_str)
if "choices" in chunk and chunk["choices"]:
delta = chunk["choices"][0]["delta"]
if "content" in delta:
await assistant_msg.stream_token(delta["content"])
except:
pass
await assistant_msg.update()
if __name__ == "__main__":
# 运行Chainlit应用
cl.run(app, host="0.0.0.0", port=8501)
4.3 启动前端界面
保存文件后,运行以下命令启动前端:
chainlit run app.py
然后在浏览器中打开 http://localhost:8501,就能看到聊天界面了。
4.4 界面优化建议
基础的聊天界面可能还不够友好,我们可以做一些优化:
# 在app.py中添加更多功能
@cl.on_chat_start
async def init_chat():
# 设置聊天设置
settings = await cl.ChatSettings(
[
cl.input_widget.Slider(
id="temperature",
label="回答创造性",
initial=0.3,
min=0,
max=1,
step=0.1,
),
cl.input_widget.Slider(
id="max_tokens",
label="回答最大长度",
initial=1024,
min=100,
max=4096,
step=100,
)
]
).send()
# 添加课程选择下拉菜单
courses = await cl.dropdown(
label="选择课程",
options=["Python入门到精通", "机器学习实战", "Web开发全栈"],
initial="Python入门到精通"
).send()
# 显示课程简介
course_info = {
"Python入门到精通": "本课程共120小时,涵盖Python基础、数据分析、Web开发等内容",
"机器学习实战": "本课程共80小时,从理论到实践全面讲解机器学习",
"Web开发全栈": "本课程共100小时,前后端完整开发流程"
}
info = course_info.get(courses.value, "请选择课程查看详情")
await cl.Message(content=f"📚 已选择课程:{courses.value}\n{info}").send()
这样学员可以在界面上直接选择课程,调整回答的风格和长度,体验更好。
5. 核心功能实现:处理100小时课程视频
现在到了最关键的部分:如何让AI助教真正理解100小时的课程内容。这需要几个步骤:视频转文字、内容处理、知识存储和智能检索。
5.1 视频内容处理流程
整个处理流程可以概括为以下几步:
课程视频 → 语音转文字 → 文本清洗 → 内容分段 → 向量化存储 → 智能检索 → 生成回答
让我们一步步来实现。
5.2 第一步:视频转文字
首先需要把视频内容转换成文字。这里我推荐使用Whisper,它是开源的语音识别工具,准确率高,支持多种语言。
import whisper
import os
from moviepy.editor import VideoFileClip
def extract_audio_from_video(video_path, audio_path):
"""从视频中提取音频"""
video = VideoFileClip(video_path)
audio = video.audio
audio.write_audiofile(audio_path)
video.close()
return audio_path
def transcribe_video(video_path, model_size="large"):
"""将视频转成文字"""
# 1. 提取音频
audio_path = video_path.replace(".mp4", ".wav")
extract_audio_from_video(video_path, audio_path)
# 2. 加载Whisper模型
print(f"加载Whisper {model_size}模型...")
model = whisper.load_model(model_size)
# 3. 转写音频
print("开始转写...")
result = model.transcribe(audio_path)
# 4. 清理临时文件
os.remove(audio_path)
return result["text"], result["segments"]
# 使用示例
video_file = "/path/to/course_video.mp4"
transcript, segments = transcribe_video(video_file)
print(f"转写完成,共{len(transcript)}字")
print("前500字预览:", transcript[:500])
对于100小时的课程,你可能需要批量处理:
import glob
def process_course_videos(course_dir, output_dir):
"""批量处理课程视频"""
video_files = glob.glob(f"{course_dir}/*.mp4")
all_transcripts = []
for i, video_file in enumerate(video_files, 1):
print(f"处理第{i}/{len(video_files)}个视频: {os.path.basename(video_file)}")
try:
transcript, segments = transcribe_video(video_file)
# 保存结果
base_name = os.path.basename(video_file).replace(".mp4", "")
output_file = f"{output_dir}/{base_name}.txt"
with open(output_file, "w", encoding="utf-8") as f:
f.write(transcript)
all_transcripts.append({
"file": base_name,
"text": transcript,
"segments": segments
})
print(f" 完成,生成{len(transcript)}字")
except Exception as e:
print(f" 处理失败: {e}")
return all_transcripts
# 处理整个课程目录
course_transcripts = process_course_videos(
course_dir="/data/course_videos",
output_dir="/data/transcripts"
)
5.3 第二步:文本清洗与分段
转写出来的文字可能包含一些噪音,比如“嗯”、“啊”等语气词,还有重复的词语。我们需要清洗一下,然后分成适合处理的段落。
import re
from typing import List, Dict
def clean_transcript(text: str) -> str:
"""清洗转写文本"""
# 移除过多的空格和换行
text = re.sub(r'\s+', ' ', text)
# 移除常见的语气词(根据实际情况调整)
filler_words = ["嗯", "啊", "呃", "那个", "这个", "然后"]
for word in filler_words:
text = text.replace(f" {word} ", " ")
# 移除重复的标点
text = re.sub(r'[。,;]{2,}', '。', text)
return text.strip()
def split_into_chunks(text: str, max_chunk_size: int = 2000) -> List[Dict]:
"""将长文本分成适合处理的块"""
# 按句子分割(中文句号、问号、感叹号)
sentences = re.split(r'[。!?]', text)
sentences = [s.strip() for s in sentences if s.strip()]
chunks = []
current_chunk = ""
current_sentences = []
for sentence in sentences:
# 如果加上这句会超过最大长度,就保存当前块
if len(current_chunk) + len(sentence) > max_chunk_size and current_chunk:
chunks.append({
"text": current_chunk,
"sentences": current_sentences.copy(),
"length": len(current_chunk)
})
current_chunk = ""
current_sentences = []
current_chunk += sentence + "。"
current_sentences.append(sentence)
# 添加最后一个块
if current_chunk:
chunks.append({
"text": current_chunk,
"sentences": current_sentences,
"length": len(current_chunk)
})
return chunks
# 使用示例
cleaned_text = clean_transcript(transcript)
chunks = split_into_chunks(cleaned_text)
print(f"清洗后文本长度: {len(cleaned_text)}字")
print(f"分成{len(chunks)}个块")
print("第一个块预览:", chunks[0]["text"][:200])
5.4 第三步:构建课程知识库
现在我们需要把这些文本块转换成向量,方便后续检索。这里使用Sentence Transformers来生成文本向量。
from sentence_transformers import SentenceTransformer
import numpy as np
import pickle
import os
class CourseKnowledgeBase:
def __init__(self, model_name='paraphrase-multilingual-MiniLM-L12-v2'):
"""初始化知识库"""
self.embedding_model = SentenceTransformer(model_name)
self.chunks = [] # 存储文本块
self.embeddings = None # 存储向量
self.metadata = [] # 存储元数据(如所属章节、时间戳等)
def add_course_content(self, chunks: List[Dict], metadata: Dict = None):
"""添加课程内容到知识库"""
for i, chunk in enumerate(chunks):
self.chunks.append(chunk["text"])
# 添加元数据
chunk_meta = {
"chunk_id": len(self.chunks) - 1,
"word_count": chunk["length"],
"sentences": chunk["sentences"]
}
if metadata:
chunk_meta.update(metadata)
self.metadata.append(chunk_meta)
def build_embeddings(self):
"""为所有文本块生成向量"""
print(f"开始为{len(self.chunks)}个文本块生成向量...")
self.embeddings = self.embedding_model.encode(
self.chunks,
show_progress_bar=True,
convert_to_numpy=True
)
print("向量生成完成")
def save(self, filepath):
"""保存知识库"""
data = {
"chunks": self.chunks,
"embeddings": self.embeddings,
"metadata": self.metadata
}
with open(filepath, 'wb') as f:
pickle.dump(data, f)
print(f"知识库已保存到 {filepath}")
def load(self, filepath):
"""加载知识库"""
with open(filepath, 'rb') as f:
data = pickle.load(f)
self.chunks = data["chunks"]
self.embeddings = data["embeddings"]
self.metadata = data["metadata"]
print(f"已加载知识库,包含{len(self.chunks)}个文本块")
def search(self, query: str, top_k: int = 5):
"""搜索相关文本块"""
if self.embeddings is None:
raise ValueError("请先构建向量或加载已有知识库")
# 将查询转换为向量
query_embedding = self.embedding_model.encode([query])[0]
# 计算相似度(余弦相似度)
similarities = np.dot(self.embeddings, query_embedding) / (
np.linalg.norm(self.embeddings, axis=1) * np.linalg.norm(query_embedding)
)
# 获取最相似的top_k个结果
top_indices = np.argsort(similarities)[-top_k:][::-1]
results = []
for idx in top_indices:
results.append({
"text": self.chunks[idx],
"similarity": float(similarities[idx]),
"metadata": self.metadata[idx]
})
return results
# 使用示例
kb = CourseKnowledgeBase()
# 假设我们已经有了处理好的文本块
for chapter_name, chapter_chunks in course_chunks.items():
kb.add_course_content(
chapter_chunks,
metadata={"chapter": chapter_name}
)
# 构建向量
kb.build_embeddings()
# 保存知识库
kb.save("/data/course_knowledge_base.pkl")
# 测试搜索
query = "Python中的列表和元组有什么区别?"
results = kb.search(query, top_k=3)
print(f"查询: {query}")
for i, result in enumerate(results, 1):
print(f"\n结果{i} (相似度: {result['similarity']:.3f}):")
print(f"章节: {result['metadata']['chapter']}")
print(f"内容: {result['text'][:200]}...")
5.5 第四步:智能检索与回答生成
有了知识库,现在我们可以结合GLM模型来生成准确的回答。关键思路是:先检索相关知识点,然后把它们作为上下文提供给模型。
class CourseAssistant:
def __init__(self, knowledge_base_path, glm_api_url="http://localhost:8000/v1/chat/completions"):
"""初始化课程助手"""
self.kb = CourseKnowledgeBase()
self.kb.load(knowledge_base_path)
self.glm_api_url = glm_api_url
self.conversation_history = []
def build_context_from_results(self, search_results, max_context_length=8000):
"""从搜索结果构建上下文"""
context_parts = []
total_length = 0
for result in search_results:
chunk_text = result["text"]
chunk_length = len(chunk_text)
# 如果加上这个块不会超过最大长度,就添加
if total_length + chunk_length <= max_context_length:
context_parts.append(chunk_text)
total_length += chunk_length
else:
# 如果已经接近最大长度,可以尝试截断最后一个块
remaining = max_context_length - total_length
if remaining > 100: # 至少保留100字
context_parts.append(chunk_text[:remaining] + "...")
total_length += remaining
break
return "\n\n".join(context_parts)
def ask_question(self, question: str, use_history: bool = True):
"""回答学员问题"""
# 1. 从知识库检索相关上下文
print(f"检索与问题相关的知识点: {question}")
search_results = self.kb.search(question, top_k=5)
# 2. 构建上下文
context = self.build_context_from_results(search_results)
# 3. 构建系统提示词
system_prompt = """你是一个专业的课程助教,请根据提供的课程内容准确回答学员的问题。
课程内容:
{context}
请遵守以下规则:
1. 只基于提供的课程内容回答问题
2. 如果问题在课程内容中没有涉及,请如实告知
3. 回答要清晰、有条理,适合学员理解
4. 可以适当举例说明,但例子要来自课程内容
5. 如果问题需要多个知识点的组合,请完整解释
现在请回答学员的问题:"""
system_prompt = system_prompt.format(context=context)
# 4. 构建对话历史
messages = [{"role": "system", "content": system_prompt}]
if use_history and self.conversation_history:
# 添加最近的对话历史(避免太长)
recent_history = self.conversation_history[-6:] # 最近3轮对话
messages.extend(recent_history)
messages.append({"role": "user", "content": question})
# 5. 调用GLM模型
response = self.call_glm_api(messages)
# 6. 保存到对话历史
self.conversation_history.append({"role": "user", "content": question})
self.conversation_history.append({"role": "assistant", "content": response})
# 保持历史记录不会太长
if len(self.conversation_history) > 20:
self.conversation_history = self.conversation_history[-20:]
return response, search_results
def call_glm_api(self, messages):
"""调用GLM API"""
import requests
import json
data = {
"model": "glm-4-9b-chat-1m",
"messages": messages,
"max_tokens": 1024,
"temperature": 0.3,
"top_p": 0.9
}
try:
response = requests.post(
self.glm_api_url,
headers={"Content-Type": "application/json"},
json=data,
timeout=30
)
if response.status_code == 200:
result = response.json()
return result["choices"][0]["message"]["content"]
else:
return f"请求失败: {response.status_code}"
except Exception as e:
return f"调用API时出错: {str(e)}"
def clear_history(self):
"""清空对话历史"""
self.conversation_history = []
# 使用示例
assistant = CourseAssistant("/data/course_knowledge_base.pkl")
# 提问
question = "能详细解释一下Python中的装饰器吗?最好有实际例子。"
answer, sources = assistant.ask_question(question)
print("问题:", question)
print("\n回答:")
print(answer)
print("\n参考来源:")
for i, source in enumerate(sources[:3], 1):
print(f"{i}. 相似度: {source['similarity']:.3f}")
print(f" 章节: {source['metadata'].get('chapter', '未知')}")
print(f" 内容摘要: {source['text'][:150]}...")
6. 完整应用整合
现在我们把所有部分整合起来,创建一个完整的在线教育AI助教系统。
6.1 系统架构
整个系统的架构如下:
学员前端 (Chainlit)
↓
Web服务器 (FastAPI/Flask)
↓
AI助教核心 (CourseAssistant类)
├── 问题理解
├── 知识库检索
└── 回答生成
↓
GLM-4-9B-Chat-1M模型 (vLLM部署)
↓
课程知识库 (向量数据库)
6.2 完整后端代码
# app_backend.py
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import List, Optional
import uvicorn
from course_assistant import CourseAssistant
app = FastAPI(title="在线教育AI助教API")
# 全局助教实例
assistant = None
class QuestionRequest(BaseModel):
question: str
course_id: str
use_history: bool = True
student_id: Optional[str] = None
class QuestionResponse(BaseModel):
answer: str
sources: List[dict]
question_id: str
@app.on_event("startup")
async def startup_event():
"""启动时加载知识库"""
global assistant
try:
# 这里可以根据配置加载不同课程的知识库
assistant = CourseAssistant(
knowledge_base_path="/data/course_knowledge_base.pkl",
glm_api_url="http://localhost:8000/v1/chat/completions"
)
print("AI助教系统启动成功")
except Exception as e:
print(f"启动失败: {e}")
raise
@app.post("/ask", response_model=QuestionResponse)
async def ask_question(request: QuestionRequest):
"""处理学员提问"""
if assistant is None:
raise HTTPException(status_code=503, detail="系统正在初始化")
try:
answer, sources = assistant.ask_question(
question=request.question,
use_history=request.use_history
)
# 简化来源信息,只返回必要内容
simplified_sources = []
for source in sources[:3]: # 只返回最相关的3个来源
simplified_sources.append({
"content_preview": source["text"][:200],
"similarity": round(source["similarity"], 3),
"metadata": source["metadata"]
})
# 生成问题ID(实际应用中可以用UUID)
import hashlib
question_id = hashlib.md5(request.question.encode()).hexdigest()[:8]
return QuestionResponse(
answer=answer,
sources=simplified_sources,
question_id=question_id
)
except Exception as e:
raise HTTPException(status_code=500, detail=f"处理问题时出错: {str(e)}")
@app.post("/clear_history")
async def clear_history(student_id: str):
"""清空某个学员的对话历史"""
if assistant is None:
raise HTTPException(status_code=503, detail="系统正在初始化")
assistant.clear_history()
return {"message": "对话历史已清空"}
@app.get("/health")
async def health_check():
"""健康检查"""
return {
"status": "healthy",
"model": "GLM-4-9B-Chat-1M",
"knowledge_base_loaded": assistant is not None
}
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8001)
6.3 更新前端代码
现在更新Chainlit前端,让它调用我们的后端API:
# app_frontend.py
import chainlit as cl
import requests
import json
from typing import Optional
# 后端API地址
BACKEND_URL = "http://localhost:8001"
class CourseChatbot:
def __init__(self):
self.conversation_id = None
self.course_selected = None
async def send_to_backend(self, question: str, course_id: str) -> dict:
"""发送问题到后端API"""
try:
response = requests.post(
f"{BACKEND_URL}/ask",
json={
"question": question,
"course_id": course_id,
"use_history": True
},
timeout=60
)
if response.status_code == 200:
return response.json()
else:
return {
"answer": f"请求失败: {response.status_code}",
"sources": []
}
except Exception as e:
return {
"answer": f"连接服务器时出错: {str(e)}",
"sources": []
}
chatbot = CourseChatbot()
@cl.on_chat_start
async def start_chat():
"""聊天开始时的初始化"""
# 让用户选择课程
courses = [
cl.dropdown_option("python101", "Python入门到精通 (120小时)"),
cl.dropdown_option("ml2024", "机器学习实战 (80小时)"),
cl.dropdown_option("web2024", "Web开发全栈 (100小时)")
]
selected = await cl.ask_for_selection(
courses,
placeholder="请选择你要咨询的课程",
title="课程选择"
)
chatbot.course_selected = selected
# 显示欢迎消息
welcome_msg = f"""🎓 欢迎使用AI课程助教!
你已选择课程:{selected}
我可以帮助你:
• 解答课程中的任何问题
• 解释复杂的概念
• 提供代码示例
• 回顾重要知识点
请直接提问吧!"""
await cl.Message(content=welcome_msg).send()
@cl.on_message
async def handle_message(message: cl.Message):
"""处理用户消息"""
if not chatbot.course_selected:
await cl.Message(content="请先选择课程").send()
return
# 显示用户消息
user_msg = cl.Message(content=message.content)
await user_msg.send()
# 显示"正在思考"提示
thinking_msg = cl.Message(content="🤔 正在思考...")
await thinking_msg.send()
# 调用后端API
response = await chatbot.send_to_backend(
question=message.content,
course_id=chatbot.course_selected
)
# 移除"正在思考"消息
await thinking_msg.remove()
# 显示回答
answer = response.get("answer", "抱歉,暂时无法回答这个问题。")
# 如果有参考来源,添加到回答中
sources = response.get("sources", [])
if sources:
answer += "\n\n---\n**参考来源:**\n"
for i, source in enumerate(sources, 1):
preview = source.get("content_preview", "")[:100]
answer += f"{i}. {preview}...\n"
assistant_msg = cl.Message(content=answer)
await assistant_msg.send()
@cl.on_stop
async def on_stop():
"""聊天结束时清理"""
chatbot.conversation_id = None
chatbot.course_selected = None
if __name__ == "__main__":
cl.run(app_frontend, host="0.0.0.0", port=8501)
6.4 部署与运行
现在我们可以启动整个系统:
# 1. 启动后端API服务
python app_backend.py
# 2. 在另一个终端启动前端
chainlit run app_frontend.py
# 3. 确保vLLM服务也在运行
# (之前已经通过Docker启动了)
访问 http://localhost:8501 就可以使用完整的AI助教系统了。
7. 实际效果展示与优化建议
7.1 实际效果展示
让我们看看这个系统在实际教育场景中的表现。假设我们有一门100小时的Python课程,学员提出了以下问题:
学员问题:"老师在第35章讲到的那个爬虫项目,遇到反爬机制应该怎么处理?我记得好像讲了三种方法。"
AI助教回答: "在第35章'高级爬虫技术'中,确实详细讲解了三种处理反爬机制的方法:
-
请求头模拟(第35章第2节) 课程中提到的具体做法是:设置合理的User-Agent、Referer等请求头,模拟真实浏览器行为。代码示例中使用了
fake_useragent库来随机生成User-Agent。 -
IP代理轮换(第35章第3节) 当单个IP被限制时,可以使用代理IP池。课程演示了如何从免费代理网站获取IP,并使用
requests库的proxies参数进行轮换。重要提醒:免费代理不稳定,生产环境建议使用付费服务。 -
请求频率控制(第35章第4节) 这是最有效的方法之一。课程建议:在请求间添加随机延迟(如1-3秒),避免规律性访问。代码中使用了
time.sleep(random.uniform(1, 3))来实现。
实际项目建议: 如果遇到特别严格的反爬,可以组合使用这三种方法。课程第35章第5节的综合案例展示了如何同时使用请求头模拟、IP代理和频率控制来处理一个电商网站的反爬机制。
需要查看具体的代码示例吗?我可以为你提取第35章的相关代码片段。"
参考来源:
- 第35章第2节:请求头设置与模拟(相似度0.87)
- 第35章第3节:IP代理的使用方法(相似度0.85)
- 第35章第4节:请求频率控制策略(相似度0.82)
可以看到,AI助教能够:
- 准确理解学员的问题意图
- 从100小时课程中精准定位相关内容
- 提供结构化的回答,引用具体的章节和章节
- 给出实用的建议和代码提示
7.2 性能优化建议
在实际使用中,你可能还需要考虑一些优化:
1. 知识库更新策略
class IncrementalKnowledgeBase(CourseKnowledgeBase):
"""支持增量更新的知识库"""
def add_new_content(self, new_chunks: List[Dict], metadata: Dict):
"""添加新内容并更新向量"""
# 添加新文本块
start_idx = len(self.chunks)
self.add_course_content(new_chunks, metadata)
# 只为新内容生成向量
new_texts = [chunk["text"] for chunk in new_chunks]
new_embeddings = self.embedding_model.encode(new_texts)
# 合并到现有向量
if self.embeddings is None:
self.embeddings = new_embeddings
else:
self.embeddings = np.vstack([self.embeddings, new_embeddings])
2. 缓存常用问题
import hashlib
from functools import lru_cache
class CachedCourseAssistant(CourseAssistant):
"""带缓存的课程助手"""
@lru_cache(maxsize=1000)
def get_cached_answer(self, question_hash: str, course_id: str):
"""获取缓存答案"""
# 实际实现中可以从Redis或数据库读取
pass
def ask_question_with_cache(self, question: str, course_id: str):
"""带缓存的提问"""
# 生成问题哈希
question_hash = hashlib.md5(
f"{course_id}:{question}".encode()
).hexdigest()
# 检查缓存
cached = self.get_cached_answer(question_hash, course_id)
if cached:
return cached
# 没有缓存,正常处理
answer, sources = self.ask_question(question)
# 保存到缓存
self.save_to_cache(question_hash, course_id, answer, sources)
return answer, sources
3. 多课程支持
class MultiCourseAssistant:
"""支持多课程的助教系统"""
def __init__(self):
self.assistants = {} # course_id -> CourseAssistant
self.course_info = {} # course_id -> 课程信息
def load_course(self, course_id: str, knowledge_base_path: str):
"""加载课程"""
if course_id not in self.assistants:
assistant = CourseAssistant(knowledge_base_path)
self.assistants[course_id] = assistant
print(f"课程 {course_id} 加载成功")
def ask_question(self, question: str, course_id: str):
"""回答指定课程的问题"""
if course_id not in self.assistants:
return "该课程尚未加载,请先选择其他课程", []
assistant = self.assistants[course_id]
return assistant.ask_question(question)
7.3 扩展功能建议
根据实际需求,你还可以扩展以下功能:
- 代码执行与验证:对于编程课程,可以让AI助教不仅解释代码,还能执行代码验证结果
- 错题本功能:记录学员常问的问题,形成个性化错题本
- 学习进度跟踪:根据提问内容分析学员的学习进度和薄弱环节
- 多模态支持:除了文字,还可以处理课程中的图片、图表等内容
- 实时协作:多个学员可以同时提问,AI助教能保持各自的对话上下文
8. 总结
通过本文的实践,我们成功构建了一个能够理解100小时课程视频的AI助教系统。这个系统结合了GLM-4-9B-Chat-1M的超长上下文能力和现代AI技术栈,为在线教育平台提供了强大的智能支持。
关键收获:
-
技术可行性:GLM-4-9B-Chat-1M的1M上下文长度确实能够处理超长教育内容,从技术层面解决了长视频理解的难题。
-
实用价值:这个系统不是演示玩具,而是真正能落地的解决方案。它能显著降低人工助教成本,提供7×24小时的服务,并且回答质量有保障。
-
扩展性强:我们构建的系统架构清晰,可以轻松扩展到更多课程、更多功能。无论是添加新的课程内容,还是增加新的交互方式,都有很好的基础。
-
用户体验好:通过Chainlit构建的前端界面友好易用,学员无需任何技术背景就能获得专业的课程辅导。
实际应用建议:
如果你要在生产环境部署这个系统,我建议:
- 分阶段实施:先从一门课程开始试点,收集反馈,逐步优化
- 监控与评估:记录AI助教的回答质量,定期人工审核,持续改进
- 结合人工:AI助教不是要完全替代人工,而是作为补充,处理常见问题,释放人工助教处理更复杂的问题
- 持续更新:课程内容更新时,及时更新知识库,保持AI助教的准确性
技术选型回顾:
- 模型选择:GLM-4-9B-Chat-1M因其超长上下文能力成为最佳选择
- 部署方案:vLLM提供了高效的推理服务
- 前端框架:Chainlit让对话界面开发变得简单
- 知识检索:Sentence Transformers + 向量搜索实现了精准的内容定位
这个项目展示了如何将先进的大模型技术应用到实际的教育场景中。随着AI技术的不断发展,我们有理由相信,智能教育助手将会成为在线学习的重要组成部分,为更多学员提供个性化、高质量的学习支持。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。
更多推荐
所有评论(0)