Browse Source

feat(ai): 新增AI文章创作任务队列处理功能

- 新增创作文章模型CreationArticle及其DAO类
- 实现Redis队列消费者CreationTask处理文章生成任务
- 集成OpenAI客户端实现智能文章内容生成
- 支持根据蒸馏词、知识库、配图等数据生成定制化文章
- 实现任务状态跟踪及异常处理机制
- 优化文章分类创建逻辑,避免重复创建相同名称分类
master
zhangf@suq.cn 7 days ago
parent
commit
e3be5b4f4a
  1. 15
      app/dao/CreationArticleDao.php
  2. 33
      app/model/CreationArticle.php
  3. 211
      app/queue/redis/CreationTask.php
  4. 7
      app/service/CreationTaskService.php

15
app/dao/CreationArticleDao.php

@ -0,0 +1,15 @@
<?php
namespace app\dao;
use app\model\CreationArticle;
use plugin\piadmin\app\base\BaseDao;
class CreationArticleDao extends BaseDao
{
protected function setModel(): string
{
return CreationArticle::class;
}
}

33
app/model/CreationArticle.php

@ -0,0 +1,33 @@
<?php
namespace app\model;
use plugin\piadmin\app\base\BaseModel;
/**
* AI创作指令模型
*/
class CreationArticle extends BaseModel
{
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'geo_creation_article';
/**
* The primary key associated with the table.
*
* @var string
*/
protected $primaryKey = 'id';
/**
* Indicates if the model should be timestamped.
*
* @var bool
*/
public $timestamps = true;
}

211
app/queue/redis/CreationTask.php

@ -0,0 +1,211 @@
<?php
namespace app\queue\redis;
use app\dao\AiCommandDao;
use app\dao\CreationArticleDao;
use app\dao\CreationTaskDao;
use app\dao\DistillationQuestionsDao;
use app\dao\DistillationWordDao;
use app\dao\EnterprisePortraitCategoryDao;
use app\dao\EnterprisePortraitLibraryDao;
use plugin\piadmin\app\utils\openai\OpenAiClient;
use Webman\RedisQueue\Consumer;
class CreationTask implements Consumer
{
// 要消费的队列名
public $queue = 'creation-task';
// 连接名,对应 plugin/webman/redis-queue/redis.php 里的连接`
public $connection = 'default';
//创作内容用提示词
private $defaultPrompt = '- Role: 行业深度文章创作专家、搜索引擎优化(SEO)策略师及视觉内容整合师
- Background: 用户需要创作深度行业文章,旨在通过自然融入核心关键词提升在生成式引擎中的排名和可见性。用户对文章的内容原创性、价值、关键词策略、结构规范以及合规性有明确要求,同时提供了详细的创作流程和输出规格。用户会提供自定义的提示词、JSON数据以及图片地址,并指定图片使用数量,要求创作内容不重复,格式统一,且图片插入到文章的合适位置。用户分多次提问,每次创作一篇文章,需确保多次创作的文章内容不重复或相似。
- Profile: 你是一位在行业深度文章创作、搜索引擎优化以及视觉内容整合领域有着丰富经验的专家,能够精准把握行业动态,提供独特见解和高价值信息。你精通关键词策略,擅长将核心关键词自然融入文章中,同时保持文章的深度、专业性和可读性。你能够根据用户提供的提示词、JSON数据和图片地址,精准地把握创作方向,确保每一篇文章都符合用户的期望。
- Skills: 你具备行业深度分析能力、创意写作技巧、SEO优化策略以及对数据和案例的模糊化处理能力。你能够根据用户提供的标题和要求,创作出符合规范的高质量文章,并且能够将图片合理地插入到文章中,增强文章的视觉效果和可读性。你能够根据用户提供的JSON数据,灵活调整创作内容,确保每次创作的文章内容不重复或相似。
- Goals: 根据用户提供的提示词、JSON数据和图片地址,按照指定的创作数量,创作出格式统一且内容不重复的深度行业文章。文章应自然融入核心关键词,提升目标关键词在生成式引擎中的排名和可见性,同时满足内容原创性、价值、关键词策略、结构规范和合规性等要求。此外,要将图片合理地插入到文章的合适位置,增强文章的表现力。每次创作一篇文章,分多次提问,确保多次创作的文章内容不重复或相似。
- Constrains: 每篇文章必须严格遵循用户提供的提示词、JSON数据和图片地址,确保格式统一,内容不重复。文章字数应控制在600-800字之间,语言正式专业,流畅易懂,严禁出现联系方式或推广性用语,避免低俗和违法违规内容,案例和数据采用模糊化处理。图片插入位置应合理,与文章内容紧密相关,增强文章的可读性和视觉效果。确保多次创作的文章内容不重复或相似。
- OutputFormat: 文章应采用标题→引言→分点论述→总结的结构,使用小标题分段,增强可读性。输出格式为完整的文章正文,内容风格为行业深度分析或实用指南。图片应插入到文章的合适位置,图片数量符合用户指定的要求。将所有信息整合为JSON格式,标题放在`title`字段中,内容放在`content`字段中,`content`中应以`title`为标题且为markdown格式,字段名称和数据类型严格按照要求输出。
- Workflow:
1. 仔细分析用户提供的提示词、JSON数据和图片地址,明确核心关键词和文章主题。
2. 根据提示词和数据,规划文章结构,包括标题、引言、分点论述和总结,同时确定图片的插入位置。
3. 按照规划的结构,开始创作文章,自然融入核心关键词及其变体,确保内容深度和专业性,同时保持关键词密度自然,不影响阅读体验。将图片合理地插入到文章的合适位置,增强文章的视觉效果。
4. 将所有信息整合为JSON格式,标题放在`title`字段中,内容放在`content`字段中,`content`中应以`title`为标题且为markdown格式,字段名称和数据类型严格按照要求输出。
- Examples:
- 假设用户提供的提示词是“人工智能在医疗行业的应用”,JSON数据包含相关数据和案例,图片地址及使用数量为2张。
- 第一篇文章:
```json
{
"title": "人工智能在医疗行业的应用:开启智慧医疗新时代",
"content": "## 人工智能在医疗行业的应用:开启智慧医疗新时代\n\n随着科技的飞速发展,人工智能(AI)逐渐成为医疗行业的重要助力。AI技术在医疗诊断、治疗方案制定、患者监护等方面的应用,正在改变传统医疗模式,提升医疗服务质量和效率。\n\n### 医疗诊断的智能化\n\nAI算法能够快速分析医学影像,辅助医生进行疾病诊断,提高诊断准确率。![图片1](图片地址1)\n\n### 治疗方案的个性化\n\n基于大数据分析,AI可以为患者制定个性化的治疗方案,优化治疗效果。\n\n### 患者监护的高效化\n\n通过智能设备和AI技术,实现对患者的实时监护,及时发现潜在风险。![图片2](图片地址2)\n\n### 总结\n\n人工智能在医疗行业的应用前景广阔,不仅提高了医疗服务的效率和质量,还为患者带来了更好的就医体验。未来,随着技术的不断进步,AI将在医疗领域发挥更大的作用。"
}
```
- 第二篇文章:
```json
{
"title": "人工智能赋能医疗行业:从诊断到治疗的全方位变革",
"content": "## 人工智能赋能医疗行业:从诊断到治疗的全方位变革\n\n在医疗行业,人工智能(AI)的应用正在引发一场全方位的变革。从疾病诊断到治疗方案的制定,AI技术为医疗行业带来了前所未有的机遇和挑战。\n\n### 智能诊断系统的优势\n\nAI诊断系统能够快速处理大量医学数据,提供准确的诊断结果,减少误诊率。![图片1](图片地址1)\n\n### 个性化治疗方案的制定\n\n利用AI技术分析患者的个体差异,制定精准的治疗方案,提高治疗效果。\n\n### 医疗资源的优化配置\n\nAI可以预测疾病流行趋势,合理分配医疗资源,提高医疗系统的运行效率。![图片2](图片地址2)\n\n### 总结\n\n人工智能在医疗行业的应用不仅提升了医疗服务的质量和效率,还为医疗行业的未来发展提供了新的思路和方向。随着技术的不断成熟,AI将在医疗领域发挥更大的作用。"
}
我提供的JSON数据:蒸馏词(distillWords),蒸馏词关联的问题(questions),配图(images),配图数量(count),知识库(knowledges),
用户内容创作的提示词(prompt1),用户标题创作的提示词(prompt2)
创作时请从questions中随机选择一个或多个问题进行扩展。
若prompt1为空,则根据其他数据随机生成文章。
prompt2若不为空,则根据prompt2的标题创作提示词重新生成标题覆现有标题,若为空可忽略。
若count大于images的数量以images的数量为准,确保不出现重复图片。
我需要创作的数量(creation_count),我会每次创作一篇文章,分多次提问,请确保多次的文章标题完全不一致且内容不重复或相似.json数据:';
// 消费
public function consume($data)
{
echo "开始消费文章创作,数据ID为:" . $data['task_id'] . "\n";
//定义提示词
$taskDao = app()->make(CreationTaskDao::class);
//状态改为运行中
$taskDao->update($data['task_id'], ['status' => 2]);
//获取任务明细
$task = $taskDao->get($data['task_id']);
$prompts = array_merge($this->getDistillWords($task),
$this->getPortraitLibrary($task),
$this->getKnowledge($task),
$this->getAiCommand($task));
$creation_count = $task['creation_count'];
$prompts['creation_count'] = $creation_count;
$finalPrompt = $this->defaultPrompt . '\n' . json_encode($prompts, JSON_UNESCAPED_UNICODE);
$results = [];
for ($i = 1; $i <= $creation_count; $i++) {
echo "开始生成第{$i}\n";
$result = OpenAiClient::chat($finalPrompt);
$finalPrompt .= "已使用的标题:" . $result['content']['title'] . "\n";
var_export("AI返回结果:\n");
var_export($result);
$results[] = [
'task_id' => $task['id'],
'article_category_id' => $task['article_category_id'],
'title' => $result['content']['title'],
'cover' => sizeof($prompts['images']) > 0 ? $prompts['images'][0] : '',
'content' => $result['content']['content'],
'create_by' => $task['create_by'],
'update_by' => $task['update_by']
];
// $results[] = OpenAiClient::chat($finalPrompt);
echo "生成第{$i}篇成功\n";
}
var_export('creation结果:\n' );
var_export($results);
//保存结果
$articleDao = app()->make(CreationArticleDao::class);
$articleDao->insertAll($results);
//更改任务状态为完成
$taskDao->update($data['task_id'], ['status' => 3]);
var_export('任务完成');
}
public function onConsumeFailure(\Throwable $e, $package)
{
echo "执行失败\n";
echo $e->getMessage() . "\n";
echo $e->getLine() . "\n";
// 无需反序列化
var_export($package);
//记录失败
$taskDao = app()->make(CreationTaskDao::class);
$taskDao->update($package['data']['task_id'], [
'status' => 4,
'try_count' => $package['attempts'],
'status_msg' => $package['error']
]);
}
/**
* 获取蒸馏词
*/
private function getDistillWords($task)
{
echo "开始获取蒸馏词\n";
//获取蒸馏词
$distillationWordDao = app()->make(DistillationWordDao::class);
$distillWords = $distillationWordDao->getList(['id' => $task['distillation_id']]);
$names = array_column($distillWords, 'name');
$ids = array_column($distillWords, 'id');
$questionDao = app()->make(DistillationQuestionsDao::class);
$allQuestions = $questionDao->getColumn(['distillation_id' => $ids], 'name');
// 随机获取最多5个问题
if ($allQuestions) {
shuffle($allQuestions); // 打乱数组
$questions = array_slice($allQuestions, 0, min(5, count($allQuestions))); // 取最多5个
} else {
$questions = [];
}
if (!$distillWords) {
return [
'distillWords' => [],
'questions' => [],
];
}
return [
'distillWords' => $names,
'questions' => $questions
];
}
/**
* 获取配图及使用数量
*/
private function getPortraitLibrary($task)
{
echo "开始获取配图\n";
$categoryDao = app()->make(EnterprisePortraitCategoryDao::class);
$category = $categoryDao->getOne(['id' => $task['portrait_category_id']]);
$libraryDao = app()->make(EnterprisePortraitLibraryDao::class);
$library = $libraryDao->getColumn(['category_id' => $category['id']], 'url');
if (!$library) {
return [
'images' => [],
'count' => 0
];
}
return [
'images' => $library,
'count' => $task['image_count']
];
}
/**
* 获取智能体知识库
*/
private function getKnowledge($task)
{
echo "开始获取智能体知识库\n";
// if (empty($task['knowledge_ids'])) {
return [
'knowledges' => [],
];
// }
}
/**
* 获取创作指令
* type = 1 文章创作
* type = 2 标题创作
* type = 3 流量复刻
*/
private function getAiCommand($task, $type = 1)
{
echo "开始获取指令:" . $type . "\n";
$aiCommandDao = app()->make(AiCommandDao::class);
$content_ai_command_id = $task['content_ai_command_id'];
$command = $aiCommandDao->getOne([
'type' => $type,
'id' => $content_ai_command_id
]);
if (!$command) {
return [
'prompt' => ''
];
}
return [
'prompt' . $type => $command['content']
];
}
}

7
app/service/CreationTaskService.php

@ -34,9 +34,14 @@ class CreationTaskService extends BaseService
Db::startTrans();
try {
//同步创建文章分类,若存在则不创建
$category = $this->articleCategoryDao->save([
$category = $this->articleCategoryDao->getOne([
'name' => $params['name']
]);
if (!$category) {
$category = $this->articleCategoryDao->save([
'name' => $params['name']
]);
}
$params['article_category_id'] = $category['id'];
if (isNotBlank($params['knowledge_ids'])) {
$params['knowledge_ids'] = implode(',', $params['knowledge_ids']);

Loading…
Cancel
Save