feat: 新增AI聊天服务,重构任务提示词相关逻辑

1.  新增AI聊天服务接口、实现、控制器及相关DTO、配置类
2.  调整JournalPageTaskTypeEnum枚举值修正
3.  合并任务的基础Prompt和自定义Prompt为单个Prompt字段
4.  精简任务输出DTO冗余属性
This commit is contained in:
glz
2026-06-24 15:06:40 +08:00
parent 28a0c3cfe4
commit bf333cd718
11 changed files with 300 additions and 102 deletions

View 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
};
}
}

View File

@ -1,4 +1,4 @@
using Mapster;
using Mapster;
using QYZH.InteractiveMagazine.Common.Extensions;
using QYZH.InteractiveMagazine.Infrastructure.OSS;
using QYZH.InteractiveMagazine.IService;
@ -66,8 +66,7 @@ public class JournalPageTaskService(BaseRepository<Journal> journalRepository, O
task.Expression = input.Expression;
task.Persuasiveness = input.Persuasiveness;
task.AnswerTime = input.AnswerTime;
task.BasePrompt = input.BasePrompt;
task.CustomPrompt = input.CustomPrompt;
task.Prompt = input.Prompt;
var no = input.No.Split('-').Select(int.Parse).ToArray();
@ -97,7 +96,6 @@ public class JournalPageTaskService(BaseRepository<Journal> journalRepository, O
JournalPageId = task.JournalPageId,
No = task.No,
Type = task.Type,
Task = task.Task,
Points = task.Points,
GrowthPoint = task.GrowthPoint ?? 0,
Comprehension = task.Comprehension,
@ -105,6 +103,7 @@ public class JournalPageTaskService(BaseRepository<Journal> journalRepository, O
Expression = task.Expression,
Persuasiveness = task.Persuasiveness,
AnswerTime = task.AnswerTime,
Prompt = task.Prompt
};
// 查询答案列表