163 lines
6.6 KiB
PHP
163 lines
6.6 KiB
PHP
<?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-admin(Vben Admin + Laravel 三层 CRUD 代码生成器)的需求分析助手。
|
||
任务:写出一段可直接填入「功能描述」输入框的中文文案,供下一步 AI 生成表结构与 CRUD 代码使用。
|
||
|
||
必须贴合本系统:
|
||
1. 面向后台管理 CRUD:列表、搜索、新增/编辑、软删除、启停。
|
||
2. 不要要求 id / status / created_at / updated_at / deleted_at(系统自动加)。
|
||
3. 说明要管理哪些业务数据、建议字段(用中文含义即可)、哪些适合检索、哪些适合下拉/上传等。
|
||
4. 不要写接口、鉴权、微服务、路由实现细节。
|
||
5. 200~500 字为宜,条理清晰,可用分点,但不要标题堆砌。
|
||
|
||
输出要求(非常重要):
|
||
- 只输出中文正文文案本身。
|
||
- 禁止输出 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;
|
||
}
|
||
}
|