添加项目文件。
This commit is contained in:
108
TechHelper.Client/AI/AIConfiguration.cs
Normal file
108
TechHelper.Client/AI/AIConfiguration.cs
Normal file
@@ -0,0 +1,108 @@
|
||||
namespace TechHelper.Client.AI
|
||||
{
|
||||
public static class AIConfiguration
|
||||
{
|
||||
public static string APIkey = "c62e38f2b74e47a080487a7a2fef014a.Q6Gx5HBy6Pj0OhkI";
|
||||
|
||||
public static string ExamAnsConfig = "你是一个专业的试题生成助手,请根据提供的科目、年级和主题," +
|
||||
"严格按照下方指定的 JSON 格式和内容生成规则,输出高质量的试题数据。\r\n\r\n---\r\n**输出格式要求:" +
|
||||
"**\r\n\r\n请严格输出一个 **JSON 数组**。数组的每个元素都是一个**试题对象**。\r\n\r\n```json\r\n[\r\n " +
|
||||
"{\r\n \"题号\": \"X\",\r\n \"标题\": \"XXXXXX\",\r\n \"分值\": N,\r\n \"分值问题标记\": \"(若" +
|
||||
"分值存在问题,请在此处简要说明,例如:'该题分值分配需复核';否则,此字段留空)\",\r\n \"题目引用\": \"(若" +
|
||||
"为阅读理解等引用原文的题型,请在此处补充完整原文内容,并支持换行,例如:'(一)葡萄沟(节选)\\\\n葡萄种在山坡上" +
|
||||
"的梯田里...';否则,此字段留空)\",\r\n \"子题目\": [\r\n {\r\n \"子题号\": \"X\",\r\n " +
|
||||
" \"题干\": \"XXXXXXX(独立成题,例如:'一( )大象';或拼音题:'pú táo( )';或成语填空题的单个成语:'( )心( )意')\",\r\n " +
|
||||
" \"分值\": N,\r\n \"分值问题标记\": \"(若子题分值存在问题,请在此处简要说明,例如:'该子题分值偏高';否则,此字段留空)\",\r\n " +
|
||||
" \"选项\": [\"A\", \"B\", \"C\"],\r\n \"示例答案\": \"XXXXXXX\"\r\n }\r\n ],\r\n \"子" +
|
||||
"题组\": []\r\n }\r\n]\r\n内容生成规则:\r\n\r\n强制拆分独立考察点:\r\n核心规则: 如果一个概念题干中包含多个独立的、" +
|
||||
"可分别作答或评分的部分(例如:多个量词填空、每个成语填空、每个拼音写词语),必须将其拆分为各自独立的 子题目 对象。\r\n示" +
|
||||
"例:\r\n原题干:“一( )大象 一( )大船 一( )菜叶” → 拆分为 3 个 子题目。\r\n原题干:“看拼音,写词语:pú táo( )、xiāng " +
|
||||
"jiāo( )” → 拆分为 2 个 子题目。\r\n每个拆分后的 子题目 必须有独立的 子题号 和 分值。\r\n合并重复题号:\r\n在 JSON 数组中,如果多" +
|
||||
"个题组(如阅读理解下的不同文段 (一)、(二))属于同一大题,它们可以有相同的 题号 字段值。但每个题组必须是 JSON 数组中的一个独" +
|
||||
"立对象。\r\n题目引用完整性:\r\n若有引用文本(如阅读理解的原文),必须在 题目引用 字段中补充完整原文内容。支持 \\n 进行换行。如果" +
|
||||
"无引用文本,此字段留空 \"\"。\r\n分值分配与转换:\r\n所有 子题目 的 分值 总和必须等于对应大题(QuestionGroup)的 分值。\r\n如果原始" +
|
||||
"分值以百分比表示,必须将其转换为具体的数值。\r\n选择题的每个选项的分值必须相同。\r\n标点规范:\r\n所有中文标点统一使用全角符号。\r\n长文" +
|
||||
"本换行:\r\n题目引用、题干 等长文本内容应自动换行,每行不超过 80 个字符(使用 \\n)。\r\n特殊处理要求:\r\n\r\n选择题 (选项 字段" +
|
||||
"不为空):\r\n选项 字段必须包含一个字符串数组,格式为 [\"选项A内容\", \"选项B内容\", \"选项C内容\"]。\r\n示例答案 字段必须提供一个具体" +
|
||||
"的示例答案。\r\n填空题 (选项 字段为空):\r\n示例答案 字段必须提供一个具体的示例答案(例如:“纷纷扬扬”)。\r\n阅读理解统计题(例如:句" +
|
||||
"数/小节数): 需在 题干 中明确标注单位。\r\n主旨题(若需多选): 请在 题干 描述中清晰体现其多选特性。\r\n子题组 字段: 在当前 JSON 结构中,子" +
|
||||
"题组 字段应始终保持为空数组 [],除非我另行通知需要更深层次的题组嵌套。\r\n格式验证要求(模型内部验证,确保合规性):\r\n\r\n题目引用 字" +
|
||||
"段若为空字符串 \"\",表示该题型无原文引用。\r\n分值问题标记 字段若不为空字符串 \"\",表示该分值需人工复核,模型无需自动修改分值。\r\n禁止 " +
|
||||
"子题目 与 题目引用 以外的内容混杂在 题干 或 标题 中。\r\n每个 子题目 必须独立成题,具备独立的 分值 和 题干。\r\nJSON 语法必须正确," +
|
||||
"能够通过任何在线 JSON 校验工具的验证。\r\n所有分值分配合理(子题目 总分等于大题 分值),且百分比分值已转换为具体数值。\r\n所有标" +
|
||||
"点符号 100% 使用中文全角。\r\n长文本自动换行(每行不超过 80 字符,使用 \\n)。\r\n题号 合并与 子题目 区分应通过 JSON 数组中的独立对象体现。";
|
||||
|
||||
public static string ExamToXML = "文本试卷解析请求模板 你只需要给出转换后的结果,不需要其他任何无关的输出\r\n请将我提供的" +
|
||||
"文本试卷内容,按照以下精简版 XAML 风格的标记规则进行解析和结构化输出。\r\n\r\n输出" +
|
||||
"格式要求:\r\n请以精简版 XAML 风格的文本标记形式输出,并确保以下标签和属性的正确" +
|
||||
"使用:\r\n\r\n根元素: <EP>\r\n大题: <QG Id=\"X\" T=\"标题\" S=\"分值\" SPM=\"分值问" +
|
||||
"题标记\"/>\r\nId: 大题题号(应从文本中提取)。\r\nT: 大题标题(应从文本中提取)。\r\nS: 大题总分" +
|
||||
"(应从文本中提取,百分比请转换为具体数值)。\r\nSPM: 分值问题标记(如果文本中无则为空字符串 \"\";如果有任何" +
|
||||
"分值分配上的疑问或需要复核,请在此标记)。\r\n题目引用: <QR>引用文本</QR> (如果原始文本中无引用部分,则省" +
|
||||
"略此标签)\r\n子题目列表容器: <SQs>\r\n子题目: <SQ Id=\"X\" T=\"题干\" S=\"分值\" SPM=\"分值问题标记\" SA=\"示例" +
|
||||
"答案\"/>\r\nId: 子题号(应从文本中提取,例如 \"1.1\", \"2.3a\")。\r\nT: 子题目题干。\r\n【核心解析规则】 除了明显的拼" +
|
||||
"写和成语填空类题目外,所有子题的 T 属性都必须包含完整的原始题目表述,包括所有选项、括号等,以最大程度地保留原始文本结构。\r\nS: 子题" +
|
||||
"分值(应从文本中提取)。\r\nSPM: 分值问题标记(如果文本中无则为空字符串 \"\";如果有任何分值分配上的疑问或需要复核,请在此标记)。\r\nSA: 示例" +
|
||||
"答案。\r\n对于有多个填空或判断的题目(其 T 属性包含多个空位),答案请用空格分隔,并按题干中出现的顺序排列。\r\n示例: SA=\"× √ √\" (对应多" +
|
||||
"个判断)\r\n示例: SA=\"“ 哪 ? ” 。\" (对应多个标点填空)\r\n选项列表容器: <Os> (仅用于原始文本中明确给出选项的选择题,如果无选项则省" +
|
||||
"略此标签)\r\n选项: <O V=\"选项值\"/> (选项值应从原始文本中提取)\r\n子题组列表容器: <SQGs/> (如果原始文本中无嵌套题组,则为空标签)\r\n内容解" +
|
||||
"析规则:\r\n准确识别大题与子题: 根据题号、标题和分值模式,准确识别并划分大题 (<QG>) 和其下的子题目 (<SQ>)。\r\n细致拆分独立" +
|
||||
"考察点: 将文本中所有可独立评分或作答的部分,尽可能地拆分为独立的 <SQ> 标记块。但请注意,对于单个逻辑题但包含多个填空/选项/判断点(如填空" +
|
||||
"题和判断题),请将所有相关内容合并到一个 <SQ> 的 T 属性中,并提供序列化的 SA。\r\n提取题目引用: 识别阅读理解等题型中的引用段落,并" +
|
||||
"将其完整放入 <QR> 标签中。\r\n精确提取分值: 从原文中提取大题和子题的分值,并将其转换为阿拉伯数字。如果原文是百分比,请转换为具体分数。\r\n规范标" +
|
||||
"点: 确保所有中文标点在输出中统一使用全角符号。";
|
||||
|
||||
public static string ExamToXML2 = "文本试卷解析请求模板 你只需要给出转换后的结果,不需要其他任何无关的输出 " +
|
||||
" 1. 整体结构与根元素\r\n<EP> (试卷):\r\n\r\n根元素,表示一份完整的试卷。\r\n作为最外层的容器,所有试卷内容都" +
|
||||
"将嵌套在其内部。\r\n没有属性,其直接子元素必须是 <QGs>。\r\n<QGs> (题组集合):\r\n\r\n作为 <EP> 的直接子" +
|
||||
"元素。\r\n没有属性,其内容将是一个或多个 <QG> 标签。\r\n2. 大题/题组的标记 (<QG>)\r\n<QG Id=\"X\" T=\"标题\" S=\"分值\" " +
|
||||
"SPM=\"分值问题标记\" QR=\"引用文本\"/>\r\n目的:用于标记试卷中的每一个“大题”或“题组”。\r\n属性要求:\r\nId:必填。从文本" +
|
||||
"中提取的大题题号(例如:“一”、“I”、“1.”)。\r\nT:必填。从文本中提取的大题标题(例如:“选择题”、“填空题”)。\r\nS:必" +
|
||||
"填。从文本中提取的大题总分。\r\n转换规则:如果原始文本是百分比(例如:“20%”),请转换为具体数值(例如:“20”)。\r\nSPM:可" +
|
||||
"选。\r\n如果文本中没有特殊标记,则为空字符串 \"\"。\r\n用途:用于标记在分值分配上存在疑问或需要复核的大题。\r\nQR:可选。\r\n用" +
|
||||
"途:如果原始文本中包含阅读理解、材料分析等需要引用的段落,请将完整的引用文本内容放入此属性。\r\n省略规则:如果原始文本中没有引用" +
|
||||
"部分,则整个 QR 属性应被省略(不要保留空属性或空标签)。\r\n子元素:\r\n一个 <QG> 标签内部可以包含 <SQs>(用于普通子题)或 <SQGs>(用" +
|
||||
"于嵌套题组)。两者不能同时存在。\r\n3. 子题目与选项的标记 (<SQ> & <O>)\r\n<SQs> (子题目列表容器):\r\n\r\n目的:作为 <QG> 的子元素,用" +
|
||||
"于封装一个大题下的所有独立小题。\r\n没有属性,其内容将是一个或多个 <SQ> 标签。\r\n<SQ Id=\"X\" T=\"题干\" S=\"分值\" SPM=\"分值问题标" +
|
||||
"记\" SA=\"示例答案\"/> (子题目):\r\n\r\n目的:用于标记试卷中的每一个“小题”。\r\n属性要求:\r\nId:必填。从文本中提取的子题号(例" +
|
||||
"如:“1.1”、“2.3a”、“(1)”)。\r\nT:必填。子题目的完整题干内容。\r\n核心解析规则:除了明显的单个填空(例如:“C#是一个____语言。”)和" +
|
||||
"单个判断类题目(例如:“是/否判断题。”)外,所有子题的 T 属性必须包含完整的原始题目表述,包括所有选项文字、括号、多个空位等,以最大程度" +
|
||||
"地保留原始文本结构。\r\nS:必填。从文本中提取的子题分值。\r\nSPM:可选。规则同 <QG> 中的 SPM 属性。\r\nSA:必填。小题的示例答案。\r\n多" +
|
||||
"空/多判断:对于包含多个空位或判断点的题目,答案请用空格分隔,并按照题干中出现的顺序排列。\r\n示例:SA=\"× √ √\" (对应多个判断)\r\n示" +
|
||||
"例:SA=\"“ 哪 ? ” 。\" (对应多个标点填空)\r\n示例:SA=\"北京 长城\" (对应两个填空)\r\n选择题:填写正确选项的字母或编号,例如 SA=\"A\" 或" +
|
||||
" SA=\"A,C\"。\r\n单个填空/判断题:直接填写答案,例如 SA=\"C#\" 或 SA=\"正确\"。\r\n<Os> (选项列表容器):\r\n\r\n目的:作为 <SQ> 的子" +
|
||||
"元素,用于封装选择题的选项。\r\n限制:仅用于原始文本中明确给出选项的选择题。\r\n省略规则:如果原始文本中无选项(例如:填空题、简答题)," +
|
||||
"则整个 <Os> 标签应被省略。\r\n没有属性,其内容将是一个或多个 <O> 标签。\r\n<O V=\"选项值\"/> (选项):\r\n\r\n目的:标记单个选项。\r\n属" +
|
||||
"性要求:\r\nV:必填。选项的完整内容,应从原始文本中提取,包括选项的字母/编号(例如:“A. 选项A”、“B) 选项B”)。\r\n4. 子题组的" +
|
||||
"标记 (<SQGs>)\r\n<SQGs> (子题组列表容器):\r\n目的:作为 <QG> 的子元素,用于表示嵌套的题组结构(例如:一个大题下面又包含几个小的大" +
|
||||
"题组)。\r\n省略规则:如果原始文本中无嵌套题组,则整个 <SQGs> 标签应被省略。\r\n没有属性,其内容将是一个或多个嵌套的 <QG> 元素。\r\n注意:嵌" +
|
||||
"套的 <QG> 结构与顶层的 <QG> 相同,可以递归包含 <SQs> 或 <SQGs>。\r\n5. 核心内容解析与转换规范\r\n识别与划分:\r\n优先识别大题 (<QG>),然后" +
|
||||
"识别其下的子题目 (<SQ>)。识别依据主要是题号、标题和分值模式。\r\n独立考察点:\r\n原则:将文本中所有可独立评分或作答的部分,尽可能地拆分" +
|
||||
"为独立的 <SQ> 标记块。\r\n例外:对于单个逻辑题但包含多个填空/选项/判断点(如一个句子中有多个空需要填写,或一个问题下有多个判断题),请将所" +
|
||||
"有相关内容合并到同一个 <SQ> 的 T 属性中,并提供序列化的 SA。\r\n分值提取:\r\n从原文中精确提取大题和子题的分值,并将其转换为阿拉伯数字。\r\n如果原" +
|
||||
"文是百分比,务必转换为具体分数。\r\n标点规范:\r\n确保所有中文标点在输出的XML文本中统一使用全角符号。";
|
||||
|
||||
public static string RecorrectXML = "按下面的要求校验XML文本,如果有错误修正他,没有错误则直接返回,你只需要给出修正或原来的结果,不需要其他任何无" +
|
||||
"关的输出, 要求:请检查这段 XML 代码的语法是否正确,包括标签的开闭、嵌套和属性的引号。请检查 XML 数据中是否存在逻辑错误或不一" +
|
||||
"致的地方。XML 中的数据类型是否符合预期?例如,某个字段应该是数字却包含了文本。是否存在缺失的必需字段?XML 中的数据值是否在合理的范围内?检查" +
|
||||
"属性值是否有效且完整。 XML结构为<EP> (根元素,必需) 包含 <QGs> (必需)。<QGs> (容器,必需) 包含一个或多个 <QG>。<QG> (问题组,必需) 包含" +
|
||||
"在 <QGs> 或嵌套的 <SQGs> 内部,具有 Id (必需), T (必需), S (必需), SPM (可选) 属性,可包含子元素 <QR> (可选), <SQs> (可选,包含一" +
|
||||
"个或多个 <SQ>), <SQGs> (可选,包含一个或多个嵌套的 <QG>)。<SQs> (子题目容器,必需) 包含在 <QG> 内部,包含一个或多个 <SQ>。<SQ> (子题目,必需) 包含" +
|
||||
"在 <SQs> 内部,具有 Id (必需), T (必需), S (必需), SPM (可选), SA (可选) 属性,可包含子元素 <Os> (可选,包含一个或多个 <O>)。<Os> (选项容器,必需) 包" +
|
||||
"含在 <SQ> 内部,包含一个或多个 <O>。<O> (选项,必需) 包含在 <Os> 内部,具有 V (必需) 属性。";
|
||||
|
||||
|
||||
public static string BreakQuestions = "请识别以下文本中的每一道大题。将其转换为XML格式,你只需要给出转换后的结果,不需要其他任何无关的输出,其中 <EP> 是XML的根元素。用<Q> 和</Q> 标记来包裹每一道大题。请确保标记后的文本保持原始格式和内容。";
|
||||
|
||||
|
||||
public static string ParseSignelQuestion = "请将以下提供的一道大题内容,转换为符合以下 C# 类结构的 XML 格式,你只需要给出转换后的结果,不需要其他任何无关的输出:" +
|
||||
"XML 的根元素为 <QG>。并填充以下属性:Id:对应 QuestionGroup.Id(从大题开头的题号中提取)。T:对应 QuestionGroup.Title(" +
|
||||
"从大题的标题中提取)。S:对应 QuestionGroup.Score(从大题中识别的分值)。<QR> 元素:对应 QuestionGroup.QuestionReference。**子题目" +
|
||||
"(SubQuestion)**将作为 <QG> 内部的 <SQs> 元素列表中的 <SQ> 元素。如果大题下有子题目,则将它们包裹在 <SQs> 元素中。对于每个 <SQ> 元素," +
|
||||
"填充以下属性:Id:对应 SubQuestion.SubId(从子题号中提取)。T:对应 SubQuestion.Stem(从子题目的题干中提取)。S:对应 SubQuestion.Score" +
|
||||
"(从子题目中识别的分值)。SPM:对应 SubQuestion.ScoreProblemMarker。SA:对应 SubQuestion.SampleAnswer。**选项(Option)**将作为" +
|
||||
" <SQ> 内部的 <Os> 元素列表中的 <O> 元素。如果子题目有选项,则将它们包裹在 <Os> 元素中。对于每个 <O> 元素,填充 V 属性:V:对应 Option.Value" +
|
||||
"(从选项内容中提取)。嵌套题组:如果大题内部包含其他大题,则作为 <QG> 内部的 <SQGs> 元素列表中的 <QG> 元素(即嵌套 <QG>)。" +
|
||||
"请确保生成的 XML 严格遵循上述结构和命名约定,以方便 C# XmlSerializer 进行反序列化。";
|
||||
}
|
||||
}
|
41
TechHelper.Client/AI/AIModels.cs
Normal file
41
TechHelper.Client/AI/AIModels.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using System.ComponentModel;
|
||||
using System.Reflection;
|
||||
|
||||
namespace TechHelper.Client.AI
|
||||
{
|
||||
public enum AIModelsEnum
|
||||
{
|
||||
[Description("glm-4v-flash")]
|
||||
GLM4VFlash,
|
||||
|
||||
[Description("cogview-3-flash")]
|
||||
Cogview3Flash,
|
||||
|
||||
[Description("cogvideox-flash")]
|
||||
CogVideoXFlash,
|
||||
|
||||
[Description("glm-4-flash-250414")]
|
||||
GLM4Flash25,
|
||||
|
||||
[Description("glm-z1-flash")]
|
||||
GLMZ1Flash
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static class EnumExtensions
|
||||
{
|
||||
public static string GetDescription(this Enum value)
|
||||
{
|
||||
FieldInfo field = value.GetType().GetField(value.ToString());
|
||||
DescriptionAttribute attribute = Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute)) as DescriptionAttribute;
|
||||
return attribute == null ? value.ToString() : attribute.Description;
|
||||
}
|
||||
}
|
||||
|
||||
public class AIModels
|
||||
{
|
||||
|
||||
}
|
||||
}
|
58
TechHelper.Client/AI/AiService.cs
Normal file
58
TechHelper.Client/AI/AiService.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using Entities.Contracts;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace TechHelper.Client.AI
|
||||
{
|
||||
public class AiService : IAIService
|
||||
{
|
||||
private readonly GLMZ1Client _glmClient;
|
||||
|
||||
public AiService()
|
||||
{
|
||||
_glmClient = new GLMZ1Client(AIConfiguration.APIkey);
|
||||
}
|
||||
|
||||
|
||||
public async Task<string> CallGLM(string userContent, string AnsConfig, AIModelsEnum aIModels/* = AIModelsEnum.GLMZ1Flash*/)
|
||||
{
|
||||
string model = aIModels.GetDescription();
|
||||
var request = new ChatCompletionRequest
|
||||
{
|
||||
Model = model,
|
||||
Messages = new List<Message>
|
||||
{
|
||||
new UserMessage(AnsConfig + userContent)
|
||||
}
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
var response = await _glmClient.ChatCompletionsSync(request);
|
||||
if (response?.Choices != null && response.Choices.Count > 0)
|
||||
{
|
||||
string content = response.Choices[0].Message?.Content;
|
||||
if (!string.IsNullOrEmpty(content))
|
||||
{
|
||||
// 移除 <think>...</think> 标签及其内容
|
||||
int startIndex = content.IndexOf("<think>");
|
||||
int endIndex = content.IndexOf("</think>");
|
||||
if (startIndex != -1 && endIndex != -1 && endIndex > startIndex)
|
||||
{
|
||||
content = content.Remove(startIndex, endIndex - startIndex + "</think>".Length);
|
||||
}
|
||||
return content.Trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
Console.WriteLine($"API 请求错误:{ex.Message}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"发生未知错误:{ex.Message}");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
217
TechHelper.Client/AI/GLMZ1Api.cs
Normal file
217
TechHelper.Client/AI/GLMZ1Api.cs
Normal file
@@ -0,0 +1,217 @@
|
||||
using Newtonsoft.Json;
|
||||
using System.Text;
|
||||
|
||||
namespace TechHelper.Client.AI
|
||||
{
|
||||
public class Message
|
||||
{
|
||||
[JsonProperty("role")]
|
||||
public string Role { get; set; }
|
||||
|
||||
[JsonProperty("content")]
|
||||
public string Content { get; set; }
|
||||
}
|
||||
|
||||
// 系统消息
|
||||
public class SystemMessage : Message
|
||||
{
|
||||
public SystemMessage(string content)
|
||||
{
|
||||
Role = "system";
|
||||
Content = content;
|
||||
}
|
||||
}
|
||||
|
||||
// 用户消息
|
||||
public class UserMessage : Message
|
||||
{
|
||||
public UserMessage(string content)
|
||||
{
|
||||
Role = "user";
|
||||
Content = content;
|
||||
}
|
||||
}
|
||||
|
||||
// 助手消息
|
||||
public class AssistantMessage : Message
|
||||
{
|
||||
public AssistantMessage(string content)
|
||||
{
|
||||
Role = "assistant";
|
||||
Content = content;
|
||||
}
|
||||
}
|
||||
|
||||
// GLM-Z1 Chat Completions API 请求体
|
||||
public class ChatCompletionRequest
|
||||
{
|
||||
[JsonProperty("model")]
|
||||
public string Model { get; set; }
|
||||
|
||||
[JsonProperty("messages")]
|
||||
public List<Message> Messages { get; set; }
|
||||
|
||||
[JsonProperty("request_id")]
|
||||
public string RequestId { get; set; }
|
||||
|
||||
[JsonProperty("do_sample")]
|
||||
public bool? DoSample { get; set; }
|
||||
|
||||
[JsonProperty("stream")]
|
||||
public bool? Stream { get; set; }
|
||||
|
||||
[JsonProperty("temperature")]
|
||||
public float? Temperature { get; set; }
|
||||
|
||||
[JsonProperty("top_p")]
|
||||
public float? TopP { get; set; }
|
||||
|
||||
[JsonProperty("max_tokens")]
|
||||
public int? MaxTokens { get; set; }
|
||||
|
||||
[JsonProperty("stop")]
|
||||
public List<string> Stop { get; set; }
|
||||
|
||||
[JsonProperty("user_id")]
|
||||
public string UserId { get; set; }
|
||||
}
|
||||
|
||||
// GLM-Z1 Chat Completions API 响应体
|
||||
public class ChatCompletionResponse
|
||||
{
|
||||
[JsonProperty("id")]
|
||||
public string Id { get; set; }
|
||||
|
||||
[JsonProperty("created")]
|
||||
public long Created { get; set; }
|
||||
|
||||
[JsonProperty("model")]
|
||||
public string Model { get; set; }
|
||||
|
||||
[JsonProperty("choices")]
|
||||
public List<CompletionChoice> Choices { get; set; }
|
||||
|
||||
[JsonProperty("usage")]
|
||||
public CompletionUsage Usage { get; set; }
|
||||
}
|
||||
|
||||
public class CompletionChoice
|
||||
{
|
||||
[JsonProperty("index")]
|
||||
public int Index { get; set; }
|
||||
|
||||
[JsonProperty("finish_reason")]
|
||||
public string FinishReason { get; set; }
|
||||
|
||||
[JsonProperty("message")]
|
||||
public CompletionMessage Message { get; set; }
|
||||
|
||||
// 流式响应中的delta
|
||||
[JsonProperty("delta")]
|
||||
public CompletionMessage Delta { get; set; }
|
||||
}
|
||||
|
||||
public class CompletionMessage
|
||||
{
|
||||
[JsonProperty("role")]
|
||||
public string Role { get; set; }
|
||||
|
||||
[JsonProperty("content")]
|
||||
public string Content { get; set; }
|
||||
}
|
||||
|
||||
public class CompletionUsage
|
||||
{
|
||||
[JsonProperty("prompt_tokens")]
|
||||
public int PromptTokens { get; set; }
|
||||
|
||||
[JsonProperty("completion_tokens")]
|
||||
public int CompletionTokens { get; set; }
|
||||
|
||||
[JsonProperty("total_tokens")]
|
||||
public int TotalTokens { get; set; }
|
||||
}
|
||||
|
||||
// 异步调用响应
|
||||
public class AsyncTaskStatus
|
||||
{
|
||||
[JsonProperty("request_id")]
|
||||
public string RequestId { get; set; }
|
||||
|
||||
[JsonProperty("id")]
|
||||
public string Id { get; set; }
|
||||
|
||||
[JsonProperty("model")]
|
||||
public string Model { get; set; }
|
||||
|
||||
[JsonProperty("task_status")]
|
||||
public string TaskStatus { get; set; }
|
||||
}
|
||||
|
||||
// 异步查询结果响应
|
||||
public class AsyncCompletion : AsyncTaskStatus
|
||||
{
|
||||
[JsonProperty("choices")]
|
||||
public List<CompletionChoice> Choices { get; set; }
|
||||
|
||||
[JsonProperty("usage")]
|
||||
public CompletionUsage Usage { get; set; }
|
||||
}
|
||||
|
||||
// GLM-Z1 API 客户端
|
||||
public class GLMZ1Client
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly string _apiKey;
|
||||
private const string BaseUrl = "https://open.bigmodel.cn/api/paas/v4/";
|
||||
|
||||
public GLMZ1Client(string apiKey)
|
||||
{
|
||||
_apiKey = apiKey;
|
||||
_httpClient = new HttpClient();
|
||||
_httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {_apiKey}");
|
||||
_httpClient.DefaultRequestHeaders.Add("Accept", "application/json");
|
||||
}
|
||||
|
||||
// 同步调用 Chat Completions API
|
||||
public async Task<ChatCompletionResponse> ChatCompletionsSync(ChatCompletionRequest request)
|
||||
{
|
||||
request.Stream = false; // 确保同步调用时 stream 为 false
|
||||
var jsonContent = JsonConvert.SerializeObject(request);
|
||||
var httpContent = new StringContent(jsonContent, Encoding.UTF8, "application/json");
|
||||
|
||||
var response = await _httpClient.PostAsync($"{BaseUrl}chat/completions", httpContent);
|
||||
response.EnsureSuccessStatusCode(); // 确保请求成功
|
||||
|
||||
var responseString = await response.Content.ReadAsStringAsync();
|
||||
return JsonConvert.DeserializeObject<ChatCompletionResponse>(responseString);
|
||||
}
|
||||
|
||||
// 异步调用 Chat Completions API
|
||||
public async Task<AsyncTaskStatus> ChatCompletionsAsync(ChatCompletionRequest request)
|
||||
{
|
||||
var jsonContent = JsonConvert.SerializeObject(request);
|
||||
var httpContent = new StringContent(jsonContent, Encoding.UTF8, "application/json");
|
||||
|
||||
var response = await _httpClient.PostAsync($"{BaseUrl}async/chat/completions", httpContent);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var responseString = await response.Content.ReadAsStringAsync();
|
||||
return JsonConvert.DeserializeObject<AsyncTaskStatus>(responseString);
|
||||
}
|
||||
|
||||
// 查询异步任务结果
|
||||
public async Task<AsyncCompletion> RetrieveCompletionResult(string taskId)
|
||||
{
|
||||
var response = await _httpClient.GetAsync($"{BaseUrl}async-result/{taskId}");
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var responseString = await response.Content.ReadAsStringAsync();
|
||||
return JsonConvert.DeserializeObject<AsyncCompletion>(responseString);
|
||||
}
|
||||
|
||||
// TODO: 实现流式调用,这会涉及循环读取 HttpResponseMessage.Content.ReadAsStreamAsync()
|
||||
// 并解析 SSE 事件,此处为简化暂不提供完整实现。
|
||||
// public async IAsyncEnumerable<ChatCompletionResponse> ChatCompletionsStream(ChatCompletionRequest request) { ... }
|
||||
}
|
||||
}
|
7
TechHelper.Client/AI/IAIService.cs
Normal file
7
TechHelper.Client/AI/IAIService.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace TechHelper.Client.AI
|
||||
{
|
||||
public interface IAIService
|
||||
{
|
||||
public Task<string> CallGLM(string userContent, string AnsConfig, AIModelsEnum aIModels = AIModelsEnum.GLMZ1Flash);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user