AI文本检测实战:用维基百科和大模型构建辨别游戏
2026/8/28 2:49:07 网站建设 项目流程

做一个“AI or Not”式的维基百科答题小游戏,核心并不是把两段文本并排展示给用户那么简单。真正的工程链路是:先从维基百科取到人类编辑撰写的真实词条摘要,再调用大模型生成同一主题的“AI 风格”文本,最后把两段文本随机打乱,让用户判断哪一段来自 AI,判完立刻给反馈并记录得分。

这类小游戏在 AI 应用开发里很有代表性。它不依赖高成本数据和复杂模型,只需要把维基百科 API、大模型接口和基础前后端组织起来,就能形成一个可交互、可演示、可扩展的完整产品。背后涉及的问题包括:如何区分人写文本和模型生成文本、大模型提示词如何稳定输出、接口异常如何降级、以及这类能力在内容标注和模型评测中怎么用。

适合读者:想用大模型做一个真实可玩项目的开发者,正在学习 FastAPI 或 Python 后端的前端工程师,以及对 AI 应用好奇的产品经理。全文会带你把服务完整搭起来,从环境准备到接口调试,最后给出常见问题排查和生产化建议。

1. 为什么用“维基百科文本”来做 AI 检测游戏

1.1 这个游戏到底在测什么

“AI or Not”类游戏表面上是在测用户,实际上是在测模型和文本风格。人类写的维基百科词条开篇通常追求信息密度,句子紧凑,结构稳定,习惯先给定义再展开背景。大模型生成的文本虽然表面上也有百科风格,但细节上容易出现几种可感知的特征:

  • 喜欢用“值得注意的是”“在当今时代”这类过渡句。
  • 信息密度偏低,一句话能说完的内容会扩展成三句。
  • 偶尔出现事实错误,也就是 AI 幻觉。
  • 语言非常“平均”,缺乏个人编辑带来的句式变化。

所以游戏判断的不只是内容真假,而是文本风格、信息密度和事实准确性。用户答对之后如果能看到模型原文和人类原文的差异,就天然理解了一次 AI 文本生成的特点。

1.2 维基百科作为题目源的四个优势

选择维基百科而不是自己积累语料,是因为它提供的题目源是现成、稳定且合法的。

优势说明
数据现成REST API 直接返回词条摘要,不需要自己抓网页洗数据
覆盖话题广随机词条可以覆盖技术、历史、地理、人物等多类主题
质量有保证词条经过多轮编辑,开篇摘要相对规范,适合作为“人类文本”基准
内容协议清楚维基百科内容遵循开放许可,做学习项目和产品原型时更容易说清来源

对学习项目来说,这意味着你可以把大部分精力放在题目逻辑和 AI 生成上,而不是花一整周去清洗语料。

1.3 为什么不是直接调用现成 AI 检测服务

市面上有商用 AI 检测服务,也能看到“某段文本是人类写的还是 AI 写的”的概率分数。但如果项目只用现成检测 API,你就无法控制题目生成过程,也无法解释检测结果背后的逻辑。更重要的是,检测服务通常只给一个概率,用户不知道这个概率是怎么来的。

自己实现“人类文本 + AI 生成文本”的组合,等于把整个链路拆开:哪一段是人类写的、哪一段是 AI 写的,在服务端是已知的。这样你能验证提示词对文本风格的影响,也能控制题目难度,甚至后续可以把用户答案收集成标注数据集。这个学习价值是单纯调用检测 API 给不了的。

2. 系统设计与一回合的数据结构

2.1 模块划分

整个服务按职责拆成五个模块,各自只处理一件事:

模块职责关键技术点
wikipedia_client获取真实词条摘要调用维基百科 REST API,清洗 extract
ai_generator生成 AI 风格同主题文本调用 OpenAI 兼容接口,控制提示词
quiz_service组装题目、洗牌、判题维护题目 ID、选项 ID、正解
api 层暴露 HTTP 接口FastAPI 路由,负责请求参数校验
前端展示题目、接收选择原生 HTML/CSS/JS,fetch 调用接口

这样拆分的好处是:你可以单独测试维基百科接口是否可用,也可以单独测试模型生成的文本质量。如果以后要换成别的百科源或者别的模型,只需要替换对应模块。

2.2 一回合完整数据流

先梳理清楚一次答题从开始到结束经过哪些环节,再写代码就不容易乱。

  1. 用户点击“下一题”,前端请求GET /api/quiz
  2. 后端调用维基百科随机词条接口,拿到词条标题和人类摘要。
  3. 后端把标题和人类摘要发给大模型,要求写一段同主题百科简介。
  4. 后端把人类文本和 AI 文本打乱,生成两个选项,并记录哪个选项是 AI。
  5. 把题目 ID、词条标题、两个选项文本返回前端。
  6. 用户选择 A 或 B,前端请求POST /api/answer
  7. 后端根据题目 ID 取出正解,比较用户选择,返回是否正确和当前得分。

注意第 3 步是最耗时的一步。大模型生成通常需要 1 到 5 秒,所以接口设计时不要把“生成题目”和“判题”放在一个请求里做完而不考虑超时;实际项目中可以拆成“创建题目”和“获取结果”两步,或者先做异步生成。

2.3 关键取舍:题目实时生成还是预生成

实时生成的优点是不需要存储大量题目,每次请求都是新题;缺点是用户每次等待模型返回,而且维基百科接口和模型接口任何一个抖动都会影响体验。

预生成的优点是把耗时前置,管理员提前批量生成 100 题存在数据库里,用户打开页面秒出题;缺点是题目库会陈旧,而且生成成本固定。

学习阶段建议先做实时生成,逻辑更直观。等到产品需要稳定体验时,再加一个本地缓存:把生成过的题目存到 SQLite 或 Redis,标题相同的题目直接复用。下面代码都按实时生成的最小闭环来写。

3. 环境准备与依赖安装

3.1 运行环境要求

开发环境建议使用 Python 3.10 及以上,因为 FastAPI 和 Pydantic 的较新版本依赖新语法。操作系统不限,Windows、macOS、Linux 都可以。

依赖项如下表所示,安装时以你当前环境能拉到的最新稳定版本为准。

依赖包作用
fastapiWeb 框架,提供 API 路由和参数校验
uvicornASGI 服务器,用来启动 FastAPI
httpx发起维基百科 HTTP 请求
openai调用大模型接口的官方 SDK
python-dotenv读取 .env 环境变量文件

开发环境需要能访问维基百科的 REST 接口,以及你的模型供应商接口。生产环境还要注意网络策略、超时时间和密钥管理,这些在排错一节会提到。

3.2 项目结构与依赖文件

先建立项目目录,目录结构保持简单,方便后面扩展。

ai-or-not-quiz/ ├── app.py # FastAPI 入口 ├── quiz/ │ ├── __init__.py │ ├── wikipedia_client.py # 维基百科摘要获取 │ ├── ai_generator.py # AI 文本生成 │ ├── quiz_service.py # 题目组装与判定 │ └── models.py # Pydantic 数据模型 ├── static/ │ ├── index.html │ ├── style.css │ └── app.js ├── requirements.txt ├── .env.example └── README.md

requirements.txt 内容如下:

fastapi uvicorn[standard] httpx openai python-dotenv

安装依赖:

pip install -r requirements.txt

如果你的环境同时存在多个 Python 版本,建议先创建虚拟环境再安装:

python3 -m venv .venv source .venv/bin/activate pip install -r requirements.txt

Windows 下激活虚拟环境命令是.venv\Scripts\activate,注意区分。

3.3 环境变量配置

模型密钥不能写死在代码里,所以通过环境变量注入。新建.env文件,内容参考.env.example

LLM_API_KEY=你的模型APIKey LLM_BASE_URL=https://api.openai.com/v1 LLM_MODEL=gpt-4o-mini

如果你的大模型服务使用 OpenAI 兼容接口,LLM_BASE_URL指向对应服务地址即可。没有填写LLM_API_KEY时,程序应该能启动但生成题目的接口返回明确错误,而不是直接崩溃。

4. 实现维基百科摘要获取模块

4.1 选择随机词条接口

维基百科提供 REST API,其中/page/random/summary可以直接拿到一个随机词条的摘要。这个接口的好处是响应体已经包含标题、摘要和页面链接,不需要再解析 HTML。

实现quiz/wikipedia_client.py

import httpx WIKIPEDIA_REST_BASE = "https://en.wikipedia.org/api/rest_v1" def fetch_random_article_summary() -> dict: url = f"{WIKIPEDIA_REST_BASE}/page/random/summary" headers = {"User-Agent": "ai-or-not-quiz/0.1 (learning project)"} with httpx.Client(timeout=10.0, follow_redirects=True) as client: resp = client.get(url, headers=headers) resp.raise_for_status() data = resp.json() extract = data.get("extract", "").strip() if not extract: raise ValueError("维基百科摘要为空,请重新获取") return { "title": data.get("title", ""), "extract": extract, "pageid": data.get("pageid"), "url": data.get("content_urls", {}).get("desktop", {}).get("page"), }

关键点:

  • timeout=10.0防止请求长时间挂起。
  • User-Agent要声明项目名和用途,维基百科文档建议这样做,能降低被限流的概率。
  • extract字段就是词条开篇的纯文本摘要,默认已经很干净。

如果想按指定主题出题,可以使用按标题获取摘要的接口:

from urllib.parse import quote def fetch_article_summary_by_title(title: str) -> dict: encoded_title = quote(title.replace(" ", "_")) url = f"{WIKIPEDIA_REST_BASE}/page/summary/{encoded_title}" # 后续逻辑与 fetch_random_article_summary 一致

4.2 读取和清洗 extract 字段

extract虽然是纯文本,但直接使用时还要做三件事:

  1. 去掉首尾空格。
  2. 控制长度。有些词条摘要比较长,超过 300 个单词后,用户阅读负担会增加。
  3. 判断文本是否过短。如果摘要只有几个单词,说明这个词条可能质量较低,建议放弃并重新随机。

长度控制和异常处理可以合并成一个函数:

def normalize_extract(extract: str, max_chars: int = 600) -> str: text = extract.strip().replace("\n", " ") if len(text) < 80: raise ValueError("摘要过短,不适合作为题目") if len(text) > max_chars: text = text[:max_chars].rsplit(" ", 1)[0] + "..." return text

这里用rsplit(" ", 1)在空格处截断,避免把一个单词切成两半。

4.3 中英文词条与语言切换

基础版本使用英文维基百科,词条覆盖广,模型对英文百科风格也更熟悉。想支持中文,只需把域名换成https://zh.wikipedia.org

WIKIPEDIA_REST_BASE = "https://zh.wikipedia.org/api/rest_v1"

同时 AI 生成模块的提示词也要改成中文,否则会出现“英文模型生成英文,中文词条生成中文”的语言错位。建议把语言作为配置项,不要写死。

5. AI 同主题文本生成模块

5.1 提示词设计:让模型写出“像维基百科”的文本

提示词决定题目难度。如果提示词太弱,AI 文本一眼就能看出来;如果提示词太强,模型可能直接复制人类摘要,用户读起来像同源文本。

推荐提示词要强调三点:客观语气、独立写作、接近百科开篇的信息密度。

SYSTEM_PROMPT = """你是一名百科词条编辑。请用客观、中立的语气,写一段关于指定主题的百科简介。 要求: 1. 全文 120 到 180 个英文单词。 2. 不要使用“作为一个AI模型”“我不能”等表述。 3. 不要输出标题,直接输出正文。 4. 不要复制参考文本,而是基于主题独立写作。 5. 信息密度尽量接近维基百科的开篇段落。"""

注意,提示词里明确“不要复制参考文本”是为了保证 AI 文本和人类文本不完全重复,否则游戏会退化成“两段内容是否相同”,而不是“风格是否像 AI”。

5.2 调用 OpenAI 兼容接口

使用 openai 官方 SDK,并支持通过base_url切换到其他兼容服务,这是最常见的接入方式:

import os from openai import OpenAI client = OpenAI( api_key=os.getenv("LLM_API_KEY"), base_url=os.getenv("LLM_BASE_URL", "https://api.openai.com/v1"), ) def generate_ai_extract(title: str, human_extract: str) -> str: user_prompt = ( f"主题:{title}\n" f"参考背景(不要直接复制):\n{human_extract[:500]}\n" "请写一段独立的百科简介。" ) resp = client.chat.completions.create( model=os.getenv("LLM_MODEL", "gpt-4o-mini"), messages=[ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": user_prompt}, ], temperature=0.7, max_tokens=300, ) text = resp.choices[0].message.content.strip() if not text: raise ValueError("模型返回空文本") return text

5.3 控制输出长度和稳定性

大模型生成不是确定性的,同一提示词两次结果可能不同。为了控制题目质量,需要理解几个参数:

参数作用建议值调大影响调小影响
temperature控制随机性0.7文本更多样,可能跑题更稳定,但容易重复
max_tokens限制最大输出长度300可输出更长文本可能截断正文
top_p采样范围默认即可更发散更保守

如果题目需要“让用户更难猜”,可以尝试temperature=0.9;如果作为评测基准,建议固定temperature=0.30.5,保证结果相对稳定。实际中不要同时调大temperaturetop_p,会让输出不可控。

6. 题目组装与判定服务

6.1 题目数据模型

拿到两段文本后,把它们封装成一个题目对象。用 Pydantic 定义模型,方便接口层做响应序列化。

from pydantic import BaseModel class QuizOption(BaseModel): id: str text: str is_ai: bool class QuizQuestion(BaseModel): question_id: str title: str options: list[QuizOption] class QuizAnswer(BaseModel): question_id: str selected_id: str

is_ai字段只存在于服务端,真正返回给前端的时候要把它剥离,否则用户看一眼 JSON 就知道答案了。这里先讲清楚模型,接口层返回时再处理。

6.2 随机洗牌与答案记录

洗牌是保证游戏公平的关键。如果不洗牌,AI 文本永远固定在 A 或 B,用户连续玩几次就能猜出规律。

import random import uuid class QuizRound: def __init__(self, title: str, human_text: str, ai_text: str): self.question_id = uuid.uuid4().hex self.title = title self.options = [ {"id": "A", "text": human_text, "is_ai": False}, {"id": "B", "text": ai_text, "is_ai": True}, ] random.shuffle(self.options) self.answer_id = next( opt["id"] for opt in self.options if opt["is_ai"] ) def public_view(self) -> dict: return { "question_id": self.question_id, "title": self.title, "options": [ {"id": opt["id"], "text": opt["text"]} for opt in self.options ], } def check(self, selected_id: str) -> bool: return selected_id == self.answer_id

这里用uuid生成题目 ID,避免自增数字被用户猜测。答案只保存在内存对象里,判题时按question_id取出。

6.3 计分与会话管理

最小闭环可以用内存字典保存题目和分数,简单但不适合多进程部署:

class QuizStore: def __init__(self): self.rounds: dict[str, QuizRound] = {} self.scores: dict[str, int] = {} quiz_store = QuizStore()

生产环境需要把question_id和分数存到 Redis 或数据库,并且设置过期时间,防止内存无限增长。学习阶段用内存即可,但要清楚重启服务后所有数据会丢失。

7. FastAPI 接口实现

7.1 接口设计

接口设计要遵循一个原则:用户只拿到必要信息,服务端保留答案和分数。

接口方法请求参数返回内容
/api/quizGET题目 ID、标题、两个选项文本
/api/answerPOSTquestion_id, selected_id是否正确、正解选项 ID、当前分数
/GET前端页面

没必要把“生成题目”和“获取正解”放在同一个接口里,那样用户可以直接通过网络请求看到答案字段。

7.2 核心接口代码

实现app.py

import os from dotenv import load_dotenv from fastapi import FastAPI, HTTPException from fastapi.staticfiles import StaticFiles from quiz.ai_generator import generate_ai_extract from quiz.models import QuizAnswer, QuizQuestion from quiz.quiz_service import QuizRound, QuizStore from quiz.wikipedia_client import ( fetch_random_article_summary, normalize_extract, ) load_dotenv() app = FastAPI(title="AI or Not Quiz") store = QuizStore() app.mount("/static", StaticFiles(directory="static"), name="static") @app.get("/api/quiz") def create_quiz(): try: article = fetch_random_article_summary() human_text = normalize_extract(article["extract"]) ai_text = generate_ai_extract(article["title"], human_text) except Exception as exc: # 这里要记录完整异常到日志,给用户的提示保持简洁 raise HTTPException(status_code=503, detail=f"题目生成失败: {exc}") round_obj = QuizRound(article["title"], human_text, ai_text) store.rounds[round_obj.question_id] = round_obj return round_obj.public_view() @app.post("/api/answer") def submit_answer(answer: QuizAnswer): round_obj = store.rounds.get(answer.question_id) if round_obj is None: raise HTTPException(status_code=404, detail="题目不存在或已过期") correct = round_obj.check(answer.selected_id) if correct: store.scores["default_user"] = store.scores.get("default_user", 0) + 1 return { "correct": correct, "answer_id": round_obj.answer_id, "score": store.scores.get("default_user", 0), }

注意:generate_ai_extractfetch_random_article_summary都是同步阻塞调用,FastAPI 的异步事件循环会被阻塞。学习项目里问题不大,但并发上来后要改成async或放入线程池。这一点在最佳实践里会再强调。

7.3 请求与响应示例

创建题目:

curl http://127.0.0.1:8000/api/quiz

响应示例:

{ "question_id": "3f9a1c2e8b6d4f0a9e7c1a2b3c4d5e6f", "title": "Machine learning", "options": [ { "id": "A", "text": "Machine learning is a field ..." }, { "id": "B", "text": "Machine learning (ML) is a field of study ..." } ] }

提交答案:

curl -X POST http://127.0.0.1:8000/api/answer \ -H "Content-Type: application/json" \ -d '{"question_id": "3f9a1c2e8b6d4f0a9e7c1a2b3c4d5e6f", "selected_id": "B"}'

响应示例:

{ "correct": true, "answer_id": "B", "score": 1 }

8. 前端交互页面

8.1 页面结构

前端保持极简,负责三件事:拉取题目、展示两个选项、提交答案。static/index.html核心结构如下:

<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>AI or Not Quiz</title> <link rel="stylesheet" href="/static/style.css"> </head> <body> <main> <h1>哪一段文字是 AI 写的?</h1> <p id="title">加载中...</p> <div id="options"></div> <p id="result"></p> <button id="next" style="display:none;">下一题</button> </main> <script src="/static/app.js"></script> </body> </html>

8.2 用 fetch 调用接口

static/app.js实现加载题目和提交答案:

const titleEl = document.getElementById("title"); const optionsEl = document.getElementById("options"); const resultEl = document.getElementById("result"); const nextBtn = document.getElementById("next"); let currentQuestionId = null; async function loadQuiz() { resultEl.textContent = ""; nextBtn.style.display = "none"; optionsEl.innerHTML = ""; const resp = await fetch("/api/quiz"); const data = await resp.json(); currentQuestionId = data.question_id; titleEl.textContent = data.title; data.options.forEach((opt) => { const card = document.createElement("button"); card.className = "option"; card.textContent = opt.text; card.onclick = () => submitAnswer(opt.id); optionsEl.appendChild(card); }); } async function submitAnswer(selectedId) { const resp = await fetch("/api/answer", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ question_id: currentQuestionId, selected_id: selectedId, }), }); const data = await resp.json(); if (data.correct) { resultEl.textContent = `答对了!正解是 ${data.answer_id},当前得分 ${data.score}`; } else { resultEl.textContent = `答错了,正解是 ${data.answer_id},当前得分 ${data.score}`; } nextBtn.style.display = "block"; } nextBtn.onclick = loadQuiz; loadQuiz();

这里把选项渲染成按钮,点击后立即提交。更完整的版本应该在高亮选项后再提交,避免用户误触。

8.3 反馈与下一题逻辑

判题之后要同时展示“是否正确”和“正解是哪一个”,否则用户只能盲目猜。下一题按钮复用loadQuiz,重新请求新题。

学习阶段可以不考虑用户体系,分数默认记在内存里的固定 key 上。需要区分用户时,可以引入 session_id 或 JWT,把分数绑定到具体用户。

9. 运行验证与结果质量分析

9.1 启动与本地验证

启动服务:

uvicorn app:app --reload --port 8000

启动成功的标志是控制台输出类似Uvicorn running on http://127.0.0.1:8000。浏览器打开http://127.0.0.1:8000/,应该能看到页面。

建议按下面顺序做冒烟测试:

  1. 单独请求维基百科接口,确认外网可访问。
  2. 单独调用generate_ai_extract,确认模型 Key 有效。
  3. 请求/api/quiz,确认返回两个选项且文本不重复。
  4. 提交一个错误答案和一个正确答案,确认判定正确。
  5. 刷新页面连续玩 10 题,确认没有明显报错。

9.2 curl 验证接口返回

直接依赖浏览器调试有时看不到完整报错,配合 curl 更高效:

curl -i http://127.0.0.1:8000/api/quiz

-i能同时看到 HTTP 状态码和响应体。如果返回 503,说明题目的某个上游服务出了问题,查看服务端日志定位是维基百科超时还是模型返回空。

9.3 判断题目质量的检查清单

“能跑”不等于“题目能玩”。每生成一题,建议按以下清单检查:

  • [ ] 两段文本长度是否接近,避免用户靠长度猜测。
  • [ ] AI 文本是否出现“作为一个 AI”“我不能”这类提示词痕迹。
  • [ ] AI 文本是否复述了人类文本的大段原句。
  • [ ] 词条标题是否过于生僻,用户完全无法判断。
  • [ ] 随机词条是否重复出现,游戏后期是否疲劳。
  • [ ] 模型返回是否偶发截断,截断后选项是否残缺。

其中“AI 文本是否复述原句”是最常出现的问题。如果复述严重,说明提示词的“不要复制参考文本”约束不够,需要加强或者在生成后用文本相似度做过滤。

10. 常见问题与排查路径

10.1 常见问题速查表

问题现象常见原因检查方式处理建议
/api/quiz 返回 503维基百科接口超时或模型 Key 无效单独 curl 维基百科接口;单独调用模型 SDK 示例检查网络、密钥、模型名;加超时和重试
两个选项文本几乎一样模型复制了参考文本对比两段文本相似度;查看提示词在提示词中加强“独立写作”约束,或过滤高相似度结果
AI 文本明显带“AI 痕迹”提示词没有禁用口语化表达查看生成文本增加禁用句式,提高 temperature
模型返回被截断max_tokens 太小查看响应里的 finish_reason调大 max_tokens,或检测截断后重新生成
刷新页面后分数丢失分数存在内存检查存储位置学习阶段可接受;生产环境改用 Redis 或数据库
请求 /api/quiz 很慢模型生成是同步阻塞调用观察日志耗时改成异步生成,或预生成题目
随机词条重复出现维基百科随机接口没有去重记录最近题目标题维护一个已用过标题的队列,临时跳过

10.2 从现象到根因的排查顺序

遇到问题不要先看最后一行堆栈,按下面顺序排查:

  1. 确认请求本身没问题,路径和参数正确。
  2. 确认上游依赖可用:维基百科接口、模型接口。
  3. 确认环境变量已加载,.env文件路径正确。
  4. 查看服务端完整日志,识别是网络报错、鉴权报错还是模型响应格式问题。
  5. 用最小脚本单独复现问题,隔离模块。
  6. 如果只是偶发问题,考虑超时和重试策略,而不是改提示词。

例如“题目生成失败”是一个非常粗的报错,必须把原始异常记录到日志里。给用户的 detail 保持简洁,但服务端日志要能定位到是httpx.ConnectTimeout还是openai.AuthenticationError

11. 生产化最佳实践与扩展方向

11.1 工程化建议清单

学习项目跑通后,如果要把它做成真正可用的服务,建议对照下面的清单补强:

  • [ ] 模型 Key 统一从环境变量或密钥管理系统读取,不要提交到代码仓库。
  • [ ] 维基百科和模型接口都设置超时和重试,并区分“不可重试”的错误类型。
  • [ ] 题目和答案不要混在同一个存储里,答案字段不返回给前端。
  • [ ] 用异步方式处理耗时操作,避免阻塞 FastAPI 事件循环。
  • [ ] 建立基本日志体系,记录请求耗时、上游错误、生成文本长度。
  • [ ] 对生成文本做基础过滤,检测空文本、敏感词、超长文本。
  • [ ] 为已生成的题目增加缓存,减少重复请求模型。
  • [ ] 增加简单限流,防止随机词条接口和模型接口被高频调用。

其中“答案字段不返回给前端”是这类产品最容易踩的坑。前端调试时可以查看网络响应,如果响应里带is_ai: true,玩家直接看开发者工具就能作弊。

11.2 可以扩展的方向

这个项目可以从一个答题游戏扩展成多个方向的工程实践:

  • 标注平台:把“用户判断是否正确”收集起来,形成人工标注数据集,用于训练或评估文本分类器。
  • 模型评测工具:固定一批维基百科词条,让不同模型生成同主题文本,比较哪个模型更容易被用户识别为 AI。
  • AI Agent 能力测试:把“生成百科简介”封装成一个 Agent 工具调用,观察不同提示词策略下的效果差异。
  • AI 幻觉观察器:对生成文本做事实校验,标记出与维基百科原文不一致的地方,帮助理解大模型的幻觉问题。
  • 前后端功能增强:加入用户登录、排行榜、题目历史、多语言支持。

如果希望把生成和判题做成更通用的能力,可以借鉴 Spring AI 这类框架的模块化思路,把模型供应商、提示词模板和任务链路解耦。小项目不需要一步到位,但模块边界一开始就要清楚。

11.3 一点学习建议

把“AI or Not”做完,真正值得记住的不是代码本身,而是几个判断:

第一,AI 应用不能只关心“模型能不能生成”,更要关心生成结果如何进入业务链路、如何验证、如何兜底。第二,两段文本对比是一个非常直观的评测方式,它比“AI 检测率 90%”这类数字更能说明问题。第三,一个最小闭环比一个宏伟架构更有学习价值,先把 500 行代码跑通,再去考虑异步、缓存、Kubernetes 这些生产化能力。

如果自己已经能独立完成这个项目,下一步可以试着不依赖维基百科,换成自己的文档库或博客语料,做一套“识别 AI 改写内容”的内部工具。你会发现,核心难点从“接 API”变成了“定义什么才算 AI 风格”,那才是这个领域真正值得深挖的地方。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询