改造的起点:OpenWebUI 是什么
OpenWebUI 是目前最活跃的开源 LLM 聊天界面之一,提供了一套完整的功能栈:多模型支持(Ollama + OpenAI API)、文档上传与 RAG 检索、聊天历史管理、用户权限系统、插件市场。
但对于校园场景,这套功能太”重”了。普通学生不需要 Workspace、Models、Prompts、Functions、Pipelines——他们只需要一个干净的对话框。
改造的核心思路是:保留 OpenWebUI 的模型接入和知识库能力,砍掉面向开发者的 UI,换上校园场景的交互。
前端改造:SvelteKit 路由权限分离
OpenWebUI 前端基于 SvelteKit(已迁移到 Svelte 5 runes 语法)。路由结构大致如下:
src/routes/
├── (app)/ # 主应用路由
│ ├── +layout.svelte
│ ├── chat/ # 聊天界面
│ ├── workspace/ # 知识库工作区
│ ├── admin/ # 管理后台
│ └── models/ # 模型管理
└── auth/ # 登录认证
路由守卫实现
通过 SvelteKit 的 +layout.server.ts 实现服务端路由守卫:
// src/routes/(app)/+layout.server.ts
import { redirect } from '@sveltejs/kit'
export const load = async ({ url, locals }) => {
const user = locals.user
const isAdminRoute = url.pathname.startsWith('/admin')
if (isAdminRoute) {
if (!user || user.role !== 'admin') {
throw redirect(307, '/auth/admin-login')
}
}
return { user }
}
组件条件渲染
前端导航和侧边栏通过用户角色控制可见性:
<!-- 普通用户只看到极简导航 -->
{#if user?.role === 'admin'}
<Sidebar>
<NavItem href="/chat">对话</NavItem>
<NavItem href="/workspace">知识库</NavItem>
<NavItem href="/admin">管理后台</NavItem>
</Sidebar>
{:else}
<!-- 访客/学生:无侧边栏,仅顶部最小导航 -->
<MinimalHeader>
<span>ZCST Campus AI</span>
<AdminLoginLink />
</MinimalHeader>
{/if}
改造原则:不删除代码,而是用条件渲染隐藏。这样后续升级 OpenWebUI 时,只需合并路由和布局文件,不会丢失功能。
后端鉴权:FastAPI 中间件
前端隐藏只是第一层。后端必须独立鉴权——不能假设”用户看不到按钮就调不到 API”:
# backend/open_webui/routers/admin.py
from fastapi import Depends, HTTPException
async def admin_required(user = Depends(get_current_user)):
if user.role != "admin":
raise HTTPException(status_code=403, detail="Admin access required")
return user
@router.get("/admin/stats")
async def get_stats(user = Depends(admin_required)):
return get_system_stats()
核心原则:管理员路由 = 前端隐藏 + 后端鉴权。两者缺一不可。
临时会话设计
校园场景和通用聊天工具有个关键差异:不是所有用户都需要被记住。访客(家长、考生、外来人员)应该用完即走;学生日常提问也不需要长期保存上下文。
# backend/open_webui/routers/chats.py
@router.post("/chat/guest")
async def guest_chat(request: ChatRequest):
session_id = str(uuid.uuid4())
context = await rag_search(request.message, top_k=5)
response = await llm.chat(
messages=[{
"role": "system",
"content": f"你是珠海科技学院的校园助手。参考以下资料回答:\n{format_context(context)}"
}, {
"role": "user",
"content": request.message
}]
)
return {
"session_id": session_id,
"answer": response,
"sources": [c.metadata for c in context]
}
本地模型部署:Ollama
对于校园场景,Ollama 本地部署有几个优势:
- 零 API 费用:不需要按 token 付费,对学生和学校预算友好
- 数据不外传:所有提问和文档都留在本地服务器上
- 离线可用:不依赖外部网络
Docker Compose 一键部署:
# docker-compose.yaml
services:
zcst-campus-ai:
build: .
ports:
- "3000:8080"
environment:
- OLLAMA_BASE_URL=http://ollama:11434
depends_on:
- ollama
ollama:
image: ollama/ollama:latest
volumes:
- ollama_data:/root/.ollama
command: >
sh -c "ollama serve & sleep 5 && ollama pull qwen2.5:7b && wait"
volumes:
ollama_data:
模型选型建议
| 模型 | 参数量 | 最低显存 | 校园场景建议 |
|---|---|---|---|
| qwen2.5 | 7B | 8GB | 通用问答,中文表现好 |
| qwen2.5 | 14B | 16GB | 更复杂的推理 |
| llama3.1 | 8B | 8GB | 备选,英文稍优 |
| deepseek-r1 | 8B | 8GB | 需要推理链路的场景 |
如果服务器资源有限,7B 模型配合 RAG 知识库已能满足大部分校园问答需求。RAG 弥补了小模型在特定领域知识上的不足。
RAG 知识库构建
校园知识库的文档来源多样:PDF 通知、公众号文章(HTML)、新生手册(Word)、招生简章(网页)。OpenWebUI 内置了文档解析和向量化能力,需要做的是组织文档结构:
knowledge_base/
├── 招生就业/
│ ├── 2026年招生简章.pdf
│ └── 历年分数线.pdf
├── 生活服务/
│ ├── 食堂窗口指南.md
│ ├── 宿舍管理规定.pdf
│ └── 校园交通指南.md
├── 学习支持/
│ ├── 图书馆使用指南.pdf
│ └── 创新创业基地入驻流程.md
└── 办事指南/
├── 学生证补办流程.md
└── 校园卡充值方式.md
分目录管理的好处是:RAG 检索时可以按目录过滤,学生问”食堂”相关问题时优先检索生活服务下的文档,避免被创新创业、招生等内容干扰。
安全清单
校园场景的 AI 助手面临几个特殊风险:
| 风险 | 措施 |
|---|---|
| Prompt injection(越狱) | System prompt 中强调”仅回答校园相关问题”,拒绝执行代码、角色扮演 |
| 知识库投毒 | 仅管理员可上传文档,上传后需要审核才能索引 |
| Cookie/Token 泄露 | 访客 token 设短过期时间(30分钟),不具管理员权限 |
| 敏感信息外泄 | 学校内部文件不上传知识库,仅索引已公开的校园资料 |
| 模型幻觉 | 所有回答标注知识来源,不确定的信息明确告知”建议咨询相关部门” |
对我自己的启发
做这个改造最大的收获,反而不是技术上的。
从 OpenWebUI 到 ZCST Campus AI,本质上做的是减法——不是加功能,而是把不需要的藏起来;不是搞万能 AI,而是让它专注做好一件事。SvelteKit 的路由守卫、FastAPI 的中间件鉴权、Docker Compose 的一键部署——这些技术手段本身不复杂,难的是做出”砍什么、留什么”的判断。
之前做始影琯朗的时候,设计思路是”加”——十个角色、多层记忆、消息总线,恨不得把所有能力都塞进去。而校园助手让我学会了”减”——一个学生打开页面,不需要知道什么是 RAG、什么是向量检索、什么是模型路由。他只需要问”食堂几点开门”,然后得到一个靠谱的答案。
这大概是做产品和技术最大的区别:技术让我们能做什么,产品让我们知道不做什么。
如果你也在做类似的校园或者企业内部 AI 助手改造,希望这篇文章能提供一些参考。
