为什么我在 Laravel AI SDK 之上构建了业务内容层
Source: Dev.to
在业务应用中直接调用原始 LLM 的问题
下面是使用 laravel/ai 直接生成付款提醒的示例:
$ai = app(\Laravel\Ai\Contracts\Ai::class);
$response = $ai->text(
"You are a professional business assistant. Generate a payment reminder
email for a client. The invoice number is #1042. It is 30 days overdue.
The amount is 1500 EUR. The client name is Jean Martin.
Our company is Acme Corp.
Return a JSON object with: subject, message, call_to_action.
Do not invent any information not provided above.
Use a firm but professional tone.
Language: French."
);
// Now parse the response...
// Hope it returned valid JSON...
// Hope it didn't invent an amount...
// Hope the tone is right...它可以工作……但也会出问题。
为什么这种做法在生产环境中会失效
- 硬编码上下文 – 提示中包含所有数据,缺乏可复用的结构。
- 输出解析脆弱 – 模型有时会返回普通文本而不是 JSON。
- 防止幻觉仅是建议 – 模型仍可能捏造数据。
- 语气、语言、受众 – 必须为每一次调用手动指定。
- 没有日志 – 你无法记录生成内容、时间或对应的租户。
所有这些都会在每个项目和每个功能中被重复重写。
我想要的效果
一个处理业务关注点的层,这样我只需要描述 什么 我想要的:
$response = BusinessAssistant::generate(new AssistantRequestData(
task: AssistantTask::Email,
preset: 'payment_reminder',
goal: 'Invoice #1042, 30 days overdue, second reminder',
language: 'fr',
tone: Tone::Direct,
context: [
'company' => true,
'customer' => ['id' => 42],
'billing' => ['invoice_id' => 99],
],
));
echo $response->subject; // "Rappel : Facture #1042 en attente de règlement"
echo $response->message; // Full professional email body
echo $response->call_to_action; // "Procéder au règlement"无需编写提示。无需解析输出。无需设置幻觉防护。
架构
引擎是一个 合同管道。每一步都是一个接口,因此您可以在不触及其他部分的情况下替换任意层。
AssistantRequestData
│
▼
RequestValidator — validates input fields
│
▼
PresetRepository — resolves the matching preset
│
▼
ContextResolver — calls your ContextProviders
│
▼
ContextSanitizer — strips sensitive keys, limits depth
│
▼
PromptBuilder — assembles system + user prompts
│
▼
TextGenerator — calls laravel/ai SDK
│
▼
OutputNormalizer — parses structured JSON response
│
▼
GenerationLogger — records to assistant_generations (best‑effort)
│
▼
AssistantResponseData关键设计决策: 引擎永远不知道您的数据模型 —— 它接收普通数组,您决定放入什么。
ContextProviders — 你的数据库与引擎之间的桥梁
与其在提示中硬编码数据,你可以编写简单的提供者类:
final class CustomerContextProvider implements ContextProvider
{
public function key(): string
{
return 'customer';
}
public function provide(AssistantRequestData $request, array $input = []): array
{
$customer = Customer::find($input['id']);
return [
'name' => $customer->full_name,
'email' => $customer->email,
'plan' => $customer->plan,
'since' => $customer->created_at->format('Y'),
];
}
}在配置文件中注册一次提供者;每次生成调用都会自动注入正确的数据。
引擎会对结果进行清理,剥离任何敏感键,确保在进入提示之前已被过滤。
防幻觉 — 结构化强制
在商业应用中,AI 最大的问题是模型 inventes 数据。
例子:一个付款提醒错误地捏造金额(€2,340 而不是实际的 €1,500)。
解决方案: 通过设计让幻觉不可能出现。如果上下文中不存在该数据,模型就无法使用它。三层机制强制实现:
- ContextSanitizer – 删除任何不应暴露的内容。
- 预设系统提示约束 – 限制模型可以输出的内容。
- OutputNormalizer 验证 – 确保最终的 JSON 符合预期的模式。
14 个预设 — 包含内容
| Preset | Description |
|---|---|
email | 专业商务邮件 |
reply | 情境化客户回复 |
payment_reminder | 逾期发票提醒 |
appointment_confirmation | 预约详情 + 确认 |
support_reply | 富有同理心的支持回复 |
follow_up | 会后跟进 |
reminder | 通用提醒 |
announcement | 公司/产品公告 |
promotion | 商业促销邮件 |
social_post | 可发布的社交媒体帖子 |
internal_note | 内部团队备忘录 |
summary | 文档或交互摘要 |
customer_onboarding | 欢迎/入职沟通 |
feedback_request | 请求客户反馈 |
(随着产品演进,欢迎随时添加更多预设。)
TL;DR
- 将原始
laravel/ai调用包装在 pipeline 中,以处理验证、预设解析、上下文注入、清理、提示构建、生成、规范化以及日志记录。 - 使用 ContextProviders 以可复用、可测试的方式从数据库获取数据。
- 通过绝不暴露未明确提供的数据来强制执行 anti‑hallucination。
- 为每种常见业务生成定义 presets,以保持提示的 DRY(不要重复自己)和可维护性。
有了这种结构,你就不必在每个项目中重复编写相同的脚手架,并能获得可靠、可审计的 AI 驱动助手,以满足所有业务逻辑生成需求。
概览
stomer_summary– 为客服提供的客户简报rewrite– 文本改写
实际示例
SaaS 计费 – 第二次付款提醒
BusinessAssistant::generate(new AssistantRequestData(
task: AssistantTask::Email,
preset: 'payment_reminder',
goal: 'Second reminder. Invoice INV-2024-0112, 30 days overdue.',
tone: Tone::Direct,
language: 'fr',
context: [
'company' => true,
'customer' => ['id' => 14],
'billing' => ['invoice_id' => 5501],
],
));驾校 – 课程确认
BusinessAssistant::generate(new AssistantRequestData(
task: AssistantTask::Email,
preset: 'appointment_confirmation',
goal: 'Confirm driving lesson. Remind student to bring permit.',
tone: Tone::Friendly,
language: 'fr',
context: [
'company' => true,
'customer' => ['id' => 88],
'appointment' => ['lesson_id' => 334],
],
));CRM 支持 – 计费争议
BusinessAssistant::generate(new AssistantRequestData(
task: AssistantTask::Reply,
preset: 'support_reply',
goal: 'Client was billed twice. Acknowledge, apologize, confirm investigation.',
tone: Tone::Reassuring,
language: 'en',
context: [
'company' => true,
'customer' => ['id' => 42],
'documents' => ['ticket_id' => 1091],
],
));提供商灵活性
只需在 .env 中更改一次,即可切换提供商——无需修改代码:
# Anthropic Claude
BUSINESS_ASSISTANT_PROVIDER=anthropic
BUSINESS_ASSISTANT_MODEL=claude-haiku-4-5-20251001
# OpenAI
BUSINESS_ASSISTANT_PROVIDER=openai
BUSINESS_ASSISTANT_MODEL=gpt-4o-mini
# Local Ollama
BUSINESS_ASSISTANT_PROVIDER=ollama
BUSINESS_ASSISTANT_MODEL=qwen2.5:3b多租户支持
内置于多租户 ERP 中,租户作用域为一等公民:
$request = new AssistantRequestData(
task: AssistantTask::Email,
goal: '...',
userIdentifier: (string) auth()->id(),
tenantIdentifier: (string) $tenant->id,
);每一次生成都会记录到 assistant_generations,并带有租户和用户标识——可用于配额跟踪和租户级别的报告。
在不调用 LLM 的情况下调试
预览完整提示而不进行 API 调用:
$prompt = BusinessAssistant::preview($request);
echo $prompt->systemPrompt; // full system instructions
echo $prompt->userPrompt; // user prompt with injected context安装
composer require fsdev/laravel-business-assistant
php artisan vendor:publish --tag=business-assistant-config
php artisan migrate
php artisan business-assistant:doctor
php artisan business-assistant:demo对此保持诚实
- 商业软件包(非 MIT)。
- 终身发布 – Laravel 12,
laravel/ai~0.2.6。 - 一次性购买,无法保证更新。
- 不是
laravel/ai或 Prism 的替代品——它位于其之上,处理它们刻意不涉及的业务层。
payhip.com/b/TkFob — €49 个人版 / €149 机构版
欢迎在评论中提出关于架构或预设系统的问题。
由 Fsdev 构建 — tematahotoa.tini@gmail.com