Browse Source
feat(ai): 新增AI文章创作任务队列处理功能
feat(ai): 新增AI文章创作任务队列处理功能
- 新增创作文章模型CreationArticle及其DAO类 - 实现Redis队列消费者CreationTask处理文章生成任务 - 集成OpenAI客户端实现智能文章内容生成 - 支持根据蒸馏词、知识库、配图等数据生成定制化文章 - 实现任务状态跟踪及异常处理机制 - 优化文章分类创建逻辑,避免重复创建相同名称分类master
4 changed files with 265 additions and 1 deletions
-
15app/dao/CreationArticleDao.php
-
33app/model/CreationArticle.php
-
211app/queue/redis/CreationTask.php
-
7app/service/CreationTaskService.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; |
|||
} |
|||
|
|||
} |
|||
@ -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; |
|||
|
|||
} |
|||
@ -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算法能够快速分析医学影像,辅助医生进行疾病诊断,提高诊断准确率。\n\n### 治疗方案的个性化\n\n基于大数据分析,AI可以为患者制定个性化的治疗方案,优化治疗效果。\n\n### 患者监护的高效化\n\n通过智能设备和AI技术,实现对患者的实时监护,及时发现潜在风险。\n\n### 总结\n\n人工智能在医疗行业的应用前景广阔,不仅提高了医疗服务的效率和质量,还为患者带来了更好的就医体验。未来,随着技术的不断进步,AI将在医疗领域发挥更大的作用。" |
|||
} |
|||
``` |
|||
- 第二篇文章: |
|||
```json |
|||
{ |
|||
"title": "人工智能赋能医疗行业:从诊断到治疗的全方位变革", |
|||
"content": "## 人工智能赋能医疗行业:从诊断到治疗的全方位变革\n\n在医疗行业,人工智能(AI)的应用正在引发一场全方位的变革。从疾病诊断到治疗方案的制定,AI技术为医疗行业带来了前所未有的机遇和挑战。\n\n### 智能诊断系统的优势\n\nAI诊断系统能够快速处理大量医学数据,提供准确的诊断结果,减少误诊率。\n\n### 个性化治疗方案的制定\n\n利用AI技术分析患者的个体差异,制定精准的治疗方案,提高治疗效果。\n\n### 医疗资源的优化配置\n\nAI可以预测疾病流行趋势,合理分配医疗资源,提高医疗系统的运行效率。\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'] |
|||
]; |
|||
} |
|||
} |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue