Files
nl-admin-api/app/Service/common/ai/AiRequirementPromptService.php
2026-08-10 15:51:00 +08:00

163 lines
6.6 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace App\Service\common\ai;
use App\BaseApp\BaseService;
use App\Enum\AiGenerationSceneEnum;
use Throwable;
/**
* 需求概览 / 提示词助手:按模块名生成或优化「适配 nl-admin 代码生成器」的中文文案
* 模型只输出纯文本(不要 JSON落库时仍包一层 result_json 方便历史查询
*/
class AiRequirementPromptService extends BaseService
{
public function __construct()
{
parent::__construct();
}
/**
* 生成或优化功能描述文案(纯文本)
*
* @param array{module_name?:string,description?:string,mode?:string} $params
* mode: generate=仅模块名生成optimize=在已有描述上优化;空则自动判断
* @return array{text:string,prompt:string,overview:string,mode:string,module_name:string}
*/
public function generate(array $params): array
{
$moduleName = trim((string) ($params['module_name'] ?? $params['moduleName'] ?? ''));
$description = trim((string) ($params['description'] ?? ''));
$mode = trim((string) ($params['mode'] ?? ''));
if ($moduleName === '') {
$this->utils->errorThrow('请输入模块名');
}
if ($mode === '') {
$mode = $description !== '' ? 'optimize' : 'generate';
}
if (!in_array($mode, ['generate', 'optimize'], true)) {
$this->utils->errorThrow('mode 仅支持 generate 或 optimize');
}
if ($mode === 'optimize' && $description === '') {
$this->utils->errorThrow('优化模式请先填写当前功能描述');
}
$runtime = AiRuntimeConfigService::getInstance()->resolve();
$log = AiGenerationLogService::getInstance();
$generationId = $log->begin([
'scene' => AiGenerationSceneEnum::REQUIREMENT_PROMPT->value,
'name' => $moduleName,
'provider' => (string) ($runtime['provider'] ?? ''),
'model' => (string) ($runtime['model'] ?? ''),
'api_key_id' => (int) ($runtime['api_key_id'] ?? 0),
'admin_id' => $this->userId,
'input_snapshot' => [
'module_name' => $moduleName,
'description' => $description,
'mode' => $mode,
],
]);
try {
$agent = AiAgentFactory::getInstance()->make();
$messages = [
['role' => 'system', 'content' => $this->buildSystemPrompt()],
['role' => 'user', 'content' => $this->buildUserPrompt($moduleName, $description, $mode)],
];
$result = $agent->chatCompletions($messages, [
'temperature' => 0.35,
'max_tokens' => 2048,
]);
$text = $this->normalizePlainText($result->content);
$normalized = [
// text 为主字段prompt/overview 兼容旧前端「替换导入」
'text' => $text,
'prompt' => $text,
'overview' => $text,
'mode' => $mode,
'module_name' => $moduleName,
];
$log->markSuccess($generationId, [
'provider' => $result->provider,
'model' => $result->model,
'api_key_id' => $result->apiKeyId,
'usage' => $result->usage,
'result_json' => $normalized,
'raw_response' => $result->content,
'name' => $moduleName,
]);
return $normalized;
} catch (Throwable $e) {
$log->markFail($generationId, $e->getMessage(), [
'provider' => (string) ($runtime['provider'] ?? ''),
'model' => (string) ($runtime['model'] ?? ''),
'api_key_id' => (int) ($runtime['api_key_id'] ?? 0),
]);
throw $e;
}
}
/**
* 系统提示:只要中文功能描述文案,禁止 JSON / markdown 代码块
*/
private function buildSystemPrompt(): string
{
return <<<'PROMPT'
你是 nl-adminVben Admin + Laravel 三层 CRUD 代码生成器)的需求分析助手。
任务:写出一段可直接填入「功能描述」输入框的中文文案,供下一步 AI 生成表结构与 CRUD 代码使用。
必须贴合本系统:
1. 面向后台管理 CRUD列表、搜索、新增/编辑、软删除、启停。
2. 不要要求 id / status / created_at / updated_at / deleted_at系统自动加
3. 说明要管理哪些业务数据、建议字段(用中文含义即可)、哪些适合检索、哪些适合下拉/上传等。
4. 不要写接口、鉴权、微服务、路由实现细节。
5. 200500 字为宜,条理清晰,可用分点,但不要标题堆砌。
输出要求(非常重要):
- 只输出中文正文文案本身。
- 禁止输出 JSON、禁止用 ``` 代码块、禁止前言后语(如「好的」「如下」)。
PROMPT;
}
/**
* 用户提示:生成 vs 优化
*/
private function buildUserPrompt(string $moduleName, string $description, string $mode): string
{
if ($mode === 'optimize') {
return "请优化下面的功能描述,使其更适合 nl-admin 代码生成(保留原意,补全字段与管理动作)。\n模块名:{$moduleName}\n当前描述:\n{$description}";
}
return "请根据模块名「{$moduleName}」写一段后台管理功能描述文案(适配 nl-admin 代码生成)。";
}
/**
* 清洗模型回复为纯文案(去掉偶发的 markdown / JSON 包裹)
*/
private function normalizePlainText(string $content): string
{
$content = trim($content);
if ($content === '') {
$this->utils->errorThrow('AI 未返回内容');
}
// 去掉 ``` 包裹
if (preg_match('/```(?:\w+)?\s*([\s\S]*?)```/i', $content, $m)) {
$content = trim($m[1]);
}
// 若模型仍返回 JSON尽量抽出 prompt/text/overview
if (str_starts_with($content, '{')) {
$decoded = json_decode($content, true);
if (is_array($decoded)) {
$content = trim((string) ($decoded['text'] ?? $decoded['prompt'] ?? $decoded['overview'] ?? ''));
}
}
$content = trim($content);
// 去掉常见客套开头
$content = preg_replace('/^(好的[,。]?|如下[:]?|以下是[^\\n]*[:]?)\\s*/u', '', $content) ?? $content;
$content = trim($content);
if ($content === '') {
$this->utils->errorThrow('AI 未生成有效文案');
}
return $content;
}
}