English | 中文
只需一个装饰器,为你的大语言模型(LLM)装上“System 2”大脑。
在部署生成式 AI 的“最后一公里”,模型幻觉总是最终的大 Boss。像 LangChain 这样的大型框架会引入过多的模板代码和复杂性,而直接调用原始 API 又没有任何安全保障。
FactLite 是一个为生产环境而生的、超轻量级的 Python 微框架,专为解决这一痛点而设计。它从顶级的 “智能体(Agentic)Reflexion”架构 中汲取灵感,通过一个自动化的、自我修正的评估循环来增强你现有的 LLM 调用,而无需你重构代码库。
- ✨ 零侵入式设计: 只需一个
@verify装饰器,就能为任何函数添加事实核查与自我修正能力。完全无需重写你现有的业务逻辑。 - ⚡️ 原生异步 & 并发安全: 从底层设计上就支持
async/await。评估过程在一个独立的线程中运行,以防止阻塞你的主事件循环,这使其完美适用于像 FastAPI 这样的高性能 Web 后端。 - 🤖 智能体工作流: 实现了一个自动化的 生成 -> 评估 -> 反思 循环。你的 LLM 会被迫审视并迭代改进它自己的答案,直到满足你设定的质量标准。
- 🧩 可扩展 & 插件化:
- 自带“裁判”: 你可以使用内置的
LLMJudge,也可以通过CustomJudge创建自己的验证逻辑(例如:正则表达式、数据库查询、类型检查)。 - 自定义失败策略: 通过自定义
FallbackAction,你可以精确定义失败后的行为——是抛出异常,返回一条安全无害的消息,还是触发一个 webhook。
- 自带“裁判”: 你可以使用内置的
- 🌐 框架无关: FactLite 不关心你如何调用 LLM。无论你用的是
openai的 SDK、anthropic的客户端,还是一个简单的requests.post去调用本地模型,只要它是一个返回字符串的 Python 函数,FactLite 就能为它保驾护航。
pip install FactLite -i https://pypi.tuna.tsinghua.edu.cn/simple/看看将你的代码从一个普通的 API 调用升级为一个能自我修正的智能体有多么简单。
之前:一个标准的、毫无保护的 LLM 调用。
import openai
client = openai.OpenAI(api_key="你的密钥")
def ask_ai(question: str):
response = client.chat.completions.create(
model="gpt-3.5-turbo",
messages=[{"role": "user", "content": question}]
)
return response.choices[0].message.content
# 这可能会返回一个事实错误的答案,而你永远不会知道。
print(ask_ai("李白是宋朝的皇帝吗?"))之后:只需一行代码,即可受到 FactLite 的保护。
import openai
from FactLite import verify, rules, action
client = openai.OpenAI(api_key="你的密钥")
# 配置一个强大的“裁判”和你的 API 密钥
config = verify.config(
rules=rules.LLMJudge(model="gpt-4o-mini", api_key="你的密钥"), # 使用 gpt-4o-mini 作为“裁判”
max_retries=1 # 最多重试 1 次
)
@verify(config=config, user_prompt="question") # 加上这个装饰器
def ask_ai(question: str):
response = client.chat.completions.create(
model="gpt-3.5-turbo",
messages=[{"role": "user", "content": question}]
)
return response.choices[0].message.content
# 现在,函数会在返回结果前自动修正答案。
print(ask_ai("李白是宋朝的皇帝吗?"))你将在控制台看到如下输出:
10:30:05 - [FactLite] - Generating initial answer...
10:30:08 - [FactLite] - Evaluating answer quality...
10:30:12 - [FactLite] - ❌ Hallucination or error detected: The answer incorrectly states that Li Bai was related to the Song Dynasty. He was a poet from the Tang Dynasty.
10:30:12 - [FactLite] - Triggering reflection and rewrite, attempt 1...
10:30:16 - [FactLite] - Evaluating answer quality...
10:30:19 - [FactLite] - ✅ Correction successful, returning the verified answer!
不,李白不是宋朝的皇帝。他是一位生活在唐朝(公元701-762年)的著名诗人。
使用正则表达式来执行内容规则,例如禁止特定词汇或要求特定模式。
@verify(
rules=rules.RegexValidator(
banned_words=["竞品", "竞争对手", "Google"],
required_pattern=[r"我们的产品"],
banned_words_file="path/to/banned_words.txt"
),
user_prompt="prompt"
)
def product_promotion(prompt: str):
# ... 你的 LLM 调用逻辑
passRegexValidator 参数说明:
banned_words:要禁止的词汇或短语列表required_pattern:必须存在的正则表达式模式列表banned_words_file:包含禁止词汇的 TXT 文件路径(每行一个词汇)
确保 AI 回复符合长度要求,可选择是否包含标点符号。
@verify(
rules=rules.LengthValidator(
min_length=50,
max_length=500,
include_punctuation=True
),
user_prompt="prompt"
)
def generate_response(prompt: str):
# ... 你的 LLM 调用逻辑
passLengthValidator 参数说明:
min_length:回复的最小长度max_length:回复的最大长度include_punctuation:是否在长度计算中包含标点符号(默认:True)
确保 LLM 返回有效的 JSON 格式,并且包含所有必要的键。
@verify(
rules=rules.JSONValidator(
required_keys=["name", "price", "description"]
),
user_prompt="prompt"
)
def generate_product_json(prompt: str):
# ... 你的 LLM 调用逻辑
passJSONValidator 参数说明:
required_keys:JSON 输出中必须存在的键列表
使用 OpenAI 的 Moderation API 检测不安全内容,如仇恨言论、暴力和成人内容。
@verify(
rules=rules.ModerationJudge(),
user_prompt="prompt"
)
def generate_content(prompt: str):
# ... 你的 LLM 调用逻辑
passModerationJudge 参数说明:
api_key:OpenAI API 密钥(默认为全局openai.api_key)
检测类别:
hate:表达、煽动或促进基于种族、性别、种族、宗教、国籍、性取向、残疾或种姓的仇恨内容hate/threatening:威胁对个人或群体使用暴力的内容self-harm:促进或描述自杀、自残或饮食障碍的内容sexual:包含成人主题或性内容的内容sexual/minors:包含涉及未成年人的性内容violence:描述或促进暴力的内容violence/graphic:描述极端或图形暴力的内容
FactLite 会自动检测并支持 async 异步函数。
from openai import AsyncOpenAI
async_client = AsyncOpenAI(api_key="你的密钥")
@verify(config=config, user_prompt="question")
async def ask_ai_async(question: str):
response = await async_client.chat.completions.create(...)
return response.choices[0].message.content
# 运行它
import asyncio
asyncio.run(ask_ai_async("给我讲讲唐朝的历史。"))除了基于 LLM 的检查,你还可以强制执行任何你能想到的本地业务逻辑。
def company_policy_judge(prompt, answer):
# 规则1:不允许过短的回答
if len(answer) < 50:
return {"is_pass": False, "feedback": "回答太短了,请再详细一点。"}
# 规则2:不能提及竞争对手
if "谷歌" in answer:
return {"is_pass": False, "feedback": "请不要提及竞争对手的名字。"}
return {"is_pass": True, "feedback": ""}
@verify(rules=rules.CustomJudge(eval_func=company_policy_judge), user_prompt="prompt")
def ask_support_bot(prompt: str):
# ... 你的 LLM 调用逻辑
pass利用网络搜索来验证答案是否符合最新信息,非常适合时效性强或快速变化的话题。
@verify(
rules=rules.Web_LLMJudge(
model="gpt-4o-mini",
max_results=3, # 使用的搜索结果数量
backend="duckduckgo" # 搜索后端
),
user_prompt="question"
)
def ask_ai_about_current_events(question: str):
# ... 你的 LLM 调用逻辑
passWeb_LLMJudge 参数说明:
model:用于评估的 OpenAI 模型max_results:使用的搜索结果数量(默认:3)backend:搜索后端,支持 "duckduckgo"、"bing"、"google"(默认:"duckduckgo")proxy:可选的搜索代理api_key:可选的 OpenAI API 密钥(默认为全局openai.api_key)base_url:可选的 OpenAI API 基础 URL
顺序执行多个验证器,创建复杂的验证工作流程。
@verify(
rules=[
rules.RegexValidator(
banned_words=["竞品", "竞争对手"],
required_pattern=[r"我们的产品"]
),
rules.LengthValidator(
min_length=50,
max_length=500
),
rules.ModerationJudge()
],
user_prompt="prompt"
)
def generate_marketing_content(prompt: str):
# ... 你的 LLM 调用逻辑
pass规则链工作原理:
- 验证器按照列表中的顺序执行
- 如果任何一个验证器失败,链条立即停止
- 如果一个验证器返回
no_retry=True,整个过程停止,不进行重试 - 只有当所有验证器都通过时,才返回答案
规则链的优势:
- 效率:如果任何验证失败,提前停止
- 灵活性:组合不同类型的验证
- 模块化:在不同链条中重用验证器
- 逻辑清晰:易于理解的验证流程
当一个答案在所有重试后仍然失败时,精确定义接下来会发生什么。
from FactLite import action
@verify(
...,
on_fail=action.ReturnSafeMessage("很抱歉,我现在无法对该问题提供一个确切的答案。")
)
def ask_sensitive_question(...):
pass
@verify(..., on_fail=action.RaiseError())
def ask_critical_question(...):
passFactLite 的 @verify 装饰器将你的函数包装在一个简单而强大的控制循环中:
- 生成 (Generate): 调用你的原始函数以生成一个初步的答案草稿。
- 评估 (Evaluate): 调用配置好的
rules(例如LLMJudge) 来评估草稿的质量。 - 反思与重试 (Reflect & Retry):
- 如果评估通过,答案将直接返回给用户。
- 如果评估失败,反馈意见会与原始提示词结合,形成一个“反思提示词”,迫使 LLM 纠正自己的错误。然后从第 1 步重新开始,直到达到
max_retries上限。
- 兜底 (Fallback): 如果所有重试都失败了,将执行配置好的
on_fail兜底操作。
欢迎参与贡献!无论是提交一个新的规则、一个新的兜底操作,还是一个性能改进,都欢迎你提出 Issue 或提交 Pull Request。
本项目的封面设计由 @apanzinc 提供支持。
本项目基于 MIT 许可证。详情请参阅 LICENSE 文件。