Files
nl-admin-api/app/Service/common/ai/AiRequirementPromptService.php

163 lines
6.6 KiB
PHP
Raw Normal View History

2026-08-10 15:51:00 +08:00
<?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;
}
}