feat: 新增AI聊天服务,重构任务提示词相关逻辑
1. 新增AI聊天服务接口、实现、控制器及相关DTO、配置类 2. 调整JournalPageTaskTypeEnum枚举值修正 3. 合并任务的基础Prompt和自定义Prompt为单个Prompt字段 4. 精简任务输出DTO冗余属性
This commit is contained in:
154
QYZH.InteractiveMagazine.Service/AiChatService.cs
Normal file
154
QYZH.InteractiveMagazine.Service/AiChatService.cs
Normal file
@ -0,0 +1,154 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using QYZH.InteractiveMagazine.IService;
|
||||
using QYZH.InteractiveMagazine.Models.Common;
|
||||
using QYZH.InteractiveMagazine.Models.Dto;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.Service;
|
||||
|
||||
/// <summary>
|
||||
/// AI聊天服务实现(OpenAI API规范)
|
||||
/// </summary>
|
||||
public class AiChatService(
|
||||
IHttpClientFactory httpClientFactory,
|
||||
IConfiguration configuration,
|
||||
ILogger<AiChatService> logger) : IAiChatService
|
||||
{
|
||||
/// <summary>
|
||||
/// 预设系统提示词
|
||||
/// </summary>
|
||||
private const string SystemPrompt = """
|
||||
# 提示词:AI 优化“题目评分/报告提示词”
|
||||
|
||||
你是一个专业的提示词优化专家。你的任务是**将用户输入的粗糙、口语化的“题目评分/报告提示词”重构为结构清晰、指令明确、便于 AI 直接执行的标准化提示词**。
|
||||
|
||||
用户(题目制作者)当前输入的内容可能包含:题目描述、作答要求、打分规则、评分维度、报告层级、示例等,但组织混乱、表述模糊。你需要**保留所有原始信息,不增删任何实质性规则**,仅优化其结构、逻辑层次和表达精度。
|
||||
|
||||
---
|
||||
|
||||
## 优化原则
|
||||
|
||||
1. **信息完整**:用户原文中的所有要点、示例、层级区分必须全部保留。
|
||||
2. **结构清晰**:将内容归入标准模块(如 `任务目标`、`输入格式`、`评分标准`、`报告生成要求`)。
|
||||
3. **指令明确**:将模糊表述(如“要写得好一点”)转化为可操作的判定条件(如“必须包含三层结构:解释原因 + 表达情绪 + 提出方案”)。
|
||||
4. **语言精练**:去除冗余修饰,保留关键限定词(如“仅”“必须”“至少”)。
|
||||
5. **格式统一**:最终输出为 Markdown 格式,使用标题(`#` `##` `###`)、列表(`-`)、加粗/斜体等提升可读性。
|
||||
|
||||
---
|
||||
|
||||
## 优化步骤
|
||||
|
||||
1. **解析原文**:通读用户输入,标注出:
|
||||
- 题目背景 / 情境
|
||||
- 学生作答要求
|
||||
- 评分维度(优秀/一般/薄弱)及其具体判定条件
|
||||
- 报告层级及其对应的评语模板或生成逻辑
|
||||
- 任何示例答案
|
||||
2. **重组结构**:按以下标准模板组织内容(可灵活调整章节名称):
|
||||
- `## 任务概述`(题目 + 要求)
|
||||
- `## 输入数据`(学生作答的内容)
|
||||
- `## 评分标准`(分档描述,含判定规则)
|
||||
- `## 报告生成规范`(层级名称、触发条件、评语示例/生成规则)
|
||||
- `## 注意事项`(如有特殊约束)
|
||||
3. **润色表达**:
|
||||
- 将“可能”“大概”等不确定词改为明确的“必须”“至少”;
|
||||
- 将并列条件用编号列表(1. 2. 3.)拆分;
|
||||
- 将示例单独以代码块或引用块呈现,避免与规则混淆。
|
||||
4. **保持原意**:不得添加用户未提及的新评分维度或报告层级,也不得删除任何已有规则。
|
||||
|
||||
---
|
||||
|
||||
## 输出格式要求
|
||||
|
||||
- 只输出优化后的完整提示词(即 AI 可直接使用的评分/报告 prompt),不要输出分析过程。
|
||||
- 使用 Markdown 语法,标题层级从 `#` 开始。
|
||||
- 所有原有示例必须原样保留,并标明“示例”或放在引用块中。
|
||||
|
||||
---
|
||||
|
||||
现在,请对**当前用户输入的原始提示词**执行上述优化过程,仅输出优化后的 Markdown 格式提示词。
|
||||
""";
|
||||
|
||||
/// <summary>
|
||||
/// AI聊天,根据用户需求生成提示词
|
||||
/// </summary>
|
||||
public async Task<AiChatOutput> ChatAsync(AiChatInput input)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(input.Message))
|
||||
{
|
||||
throw new BusinessException("消息内容不能为空", 400);
|
||||
}
|
||||
|
||||
var apiKey = configuration["AiChat:ApiKey"];
|
||||
var baseUrl = configuration["AiChat:BaseUrl"];
|
||||
var model = configuration["AiChat:Model"];
|
||||
var timeoutSeconds = configuration.GetValue<int>("AiChat:TimeoutSeconds");
|
||||
var maxTokens = configuration.GetValue<int>("AiChat:MaxTokens");
|
||||
var temperature = configuration.GetValue<double>("AiChat:Temperature");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(apiKey) || string.IsNullOrWhiteSpace(baseUrl) || string.IsNullOrWhiteSpace(model))
|
||||
{
|
||||
throw new BusinessException("AI聊天服务配置不完整,请检查 AiChat 配置节", 500);
|
||||
}
|
||||
|
||||
logger.LogInformation("开始调用AI聊天服务,消息长度:{Length}", input.Message.Length);
|
||||
|
||||
// 构建 OpenAI Chat Completions 请求体
|
||||
var requestBody = new
|
||||
{
|
||||
model = model,
|
||||
messages = new[]
|
||||
{
|
||||
new { role = "system", content = SystemPrompt },
|
||||
new { role = "user", content = input.Message }
|
||||
},
|
||||
max_tokens = maxTokens,
|
||||
temperature = temperature
|
||||
};
|
||||
|
||||
var jsonContent = JsonSerializer.Serialize(requestBody, new JsonSerializerOptions
|
||||
{
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
|
||||
});
|
||||
|
||||
// 创建 HttpClient 并发起请求
|
||||
var client = httpClientFactory.CreateClient();
|
||||
client.Timeout = TimeSpan.FromSeconds(timeoutSeconds > 0 ? timeoutSeconds : 300);
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, $"{baseUrl.TrimEnd('/')}/chat/completions");
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
|
||||
request.Content = new StringContent(jsonContent, Encoding.UTF8, "application/json");
|
||||
|
||||
var response = await client.SendAsync(request);
|
||||
var responseContent = await response.Content.ReadAsStringAsync();
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
logger.LogError("AI聊天服务调用失败,状态码:{StatusCode},响应:{Response}", response.StatusCode, responseContent);
|
||||
throw new BusinessException($"AI服务调用失败:{response.StatusCode}", 500);
|
||||
}
|
||||
|
||||
// 解析 OpenAI 响应
|
||||
using var doc = JsonDocument.Parse(responseContent);
|
||||
var choices = doc.RootElement.GetProperty("choices");
|
||||
if (choices.GetArrayLength() == 0)
|
||||
{
|
||||
throw new BusinessException("AI未返回有效内容", 500);
|
||||
}
|
||||
|
||||
var content = choices[0]
|
||||
.GetProperty("message")
|
||||
.GetProperty("content")
|
||||
.GetString();
|
||||
|
||||
logger.LogInformation("AI聊天服务调用成功,返回内容长度:{Length}", content?.Length ?? 0);
|
||||
|
||||
return new AiChatOutput
|
||||
{
|
||||
Content = content ?? string.Empty
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user