From 38e4f5a0ab4ae9d199e798c9ea559d63ab3797cd Mon Sep 17 00:00:00 2001 From: SanaeFox <36219542+Hoshinonyaruko@users.noreply.github.com> Date: Mon, 13 May 2024 14:12:53 +0800 Subject: [PATCH] Beta111 (#109) * beta1 * beta2 * beta3 * beta4 * beta5 * beta6 * beta7 * beta8 * beta9 * beta10 * beta11 * beta12 * beta13 * beta14 * beta15 * beta16 * beta16 * beta19 * beta20 * beta21 * beta22 * beta23 * beta24 * beta25 * beta27 * beta28 * beta29 * beta30 * beta31 * beta33 * beta34 * beta35 * beta36 * beta37 * beta38 * beta39 * beta40 * beta41 * beta42 * beta43 * beta44 * beta45 * beta45 * beta46 * beat48 * beta49 * beta50 * beta51 * beta52 * beta53 * beta54 * beta55 * beta57 * beta58 * beta59 * beta61 * beta62 * beta63 * beta63 * beta64 * beta65 * beta66 * beta67 * beta70 * beta71 * beta72 * beta72 * beta74 * beta75 * beta76 * beta77 * beta78 * beta79 * beta80 * beta81 * beta82 * beta83 * beta85 * beta86 * beta87 * beta88 * beta89 * beta90 * beta91 * beta92 * beta93 * beta94 * beta94 * beta96 * beta97 * beta98 * beta99 * beta100 * beta101 * beta102 * beta104 * beta105 * beta106 * beta107 * beta108 * beta109 * beta110 * beta111 --- applogic/chatgpt.go | 1 + applogic/gensokyo.go | 2 +- applogic/tongyiqianwen.go | 609 ++++++++++++++++++++++++++++++++++++ config/config.go | 197 ++++++++++++ main.go | 4 + readme.md | 23 +- structs/struct.go | 40 +++ template/config_template.go | 27 +- 8 files changed, 900 insertions(+), 3 deletions(-) create mode 100644 applogic/tongyiqianwen.go diff --git a/applogic/chatgpt.go b/applogic/chatgpt.go index ee4e33c..e36f0ef 100644 --- a/applogic/chatgpt.go +++ b/applogic/chatgpt.go @@ -470,6 +470,7 @@ func (app *App) ChatHandlerChatgpt(w http.ResponseWriter, r *http.Request) { } tempResponseJSON, _ := json.Marshal(tempResponseMap) fmtf.Fprintf(w, "data: %s\n\n", string(tempResponseJSON)) + fmt.Printf("测试返回:%v\n", string(tempResponseJSON)) flusher.Flush() } } diff --git a/applogic/gensokyo.go b/applogic/gensokyo.go index 3f283ba..292fd4e 100644 --- a/applogic/gensokyo.go +++ b/applogic/gensokyo.go @@ -618,7 +618,7 @@ func (app *App) GensokyoHandler(w http.ResponseWriter, r *http.Request) { } } } else { - //流的最后一次是完整结束的 + // 流的最后一次是完整结束的 fmtf.Printf("A完整信息: %s(sse完整结束)\n", response) } diff --git a/applogic/tongyiqianwen.go b/applogic/tongyiqianwen.go new file mode 100644 index 0000000..5853109 --- /dev/null +++ b/applogic/tongyiqianwen.go @@ -0,0 +1,609 @@ +package applogic + +import ( + "bufio" + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "strings" + "sync" + + "github.com/google/uuid" + "github.com/hoshinonyaruko/gensokyo-llm/config" + "github.com/hoshinonyaruko/gensokyo-llm/fmtf" + "github.com/hoshinonyaruko/gensokyo-llm/prompt" + "github.com/hoshinonyaruko/gensokyo-llm/structs" + "github.com/hoshinonyaruko/gensokyo-llm/utils" +) + +// 用于存储每个conversationId的最后一条消息内容 +var ( + // lastResponses 存储每个真实 conversationId 的最后响应文本 + lastResponsesTyqw sync.Map + lastCompleteResponsesTyqw sync.Map // 存储每个conversationId的完整累积信息 + mutexTyqw sync.Mutex +) + +func (app *App) ChatHandlerTyqw(w http.ResponseWriter, r *http.Request) { + if r.Method != "POST" { + http.Error(w, "Only POST method is allowed", http.StatusMethodNotAllowed) + return + } + + // 获取访问者的IP地址 + ip := r.RemoteAddr // 注意:这可能包含端口号 + ip = strings.Split(ip, ":")[0] // 去除端口号,仅保留IP地址 + + // 获取IP白名单 + whiteList := config.IPWhiteList() + + // 检查IP是否在白名单中 + if !utils.Contains(whiteList, ip) { + http.Error(w, "Access denied", http.StatusInternalServerError) + return + } + + var msg structs.Message + err := json.NewDecoder(r.Body).Decode(&msg) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + // 读取URL参数 "prompt" + promptstr := r.URL.Query().Get("prompt") + if promptstr != "" { + // prompt 参数存在,可以根据需要进一步处理或记录 + fmtf.Printf("Received prompt parameter: %s\n", promptstr) + } + + msg.Role = "user" + //颠倒用户输入 + if config.GetReverseUserPrompt() { + msg.Text = utils.ReverseString(msg.Text) + } + + if msg.ConversationID == "" { + msg.ConversationID = utils.GenerateUUID() + app.createConversation(msg.ConversationID) + } + + userMessageID, err := app.addMessage(msg) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + var history []structs.Message + + //根据是否有prompt参数 选择是否载入config.yml的prompt还是prompts文件夹的 + if promptstr == "" { + // 获取系统提示词 + systemPromptContent := config.SystemPrompt() + if systemPromptContent != "0" { + systemPrompt := structs.Message{ + Text: systemPromptContent, + Role: "system", + } + // 将系统提示词添加到历史信息的开始 + history = append([]structs.Message{systemPrompt}, history...) + } + + // 分别获取FirstQ&A, SecondQ&A, ThirdQ&A + pairs := []struct { + Q string + A string + RoleQ string // 问题的角色 + RoleA string // 答案的角色 + }{ + {config.GetFirstQ(), config.GetFirstA(), "user", "assistant"}, + {config.GetSecondQ(), config.GetSecondA(), "user", "assistant"}, + {config.GetThirdQ(), config.GetThirdA(), "user", "assistant"}, + } + + // 检查每一对Q&A是否均不为空,并追加到历史信息中 + for _, pair := range pairs { + if pair.Q != "" && pair.A != "" { + qMessage := structs.Message{ + Text: pair.Q, + Role: pair.RoleQ, + } + aMessage := structs.Message{ + Text: pair.A, + Role: pair.RoleA, + } + + // 注意追加的顺序,确保问题在答案之前 + history = append(history, qMessage, aMessage) + } + } + } else { + // 默认执行 正常提示词顺序 + if !config.GetEnhancedQA(promptstr) { + history, err = prompt.GetMessagesFromFilename(promptstr) + if err != nil { + fmtf.Printf("prompt.GetMessagesFromFilename error: %v\n", err) + } + } else { + // 只获取系统提示词 + systemMessage, err := prompt.FindFirstSystemMessage(history) + if err != nil { + fmt.Println("Error:", err) + } else { + // 如果找到system消息,将其添加到历史数组中 + history = append(history, systemMessage) + fmt.Println("Added system message back to history.") + } + } + } + + // 获取历史信息 + if msg.ParentMessageID != "" { + userhistory, err := app.getHistory(msg.ConversationID, msg.ParentMessageID) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + // 截断历史信息 + userHistory := truncateHistoryTyqw(userhistory, msg.Text, promptstr) + + if promptstr != "" { + // 注意追加的顺序,确保问题在系统提示词之后 + // 使用...操作符来展开userhistory切片并追加到history切片 + // 获取系统级预埋的系统自定义QA对 + systemHistory, err := prompt.GetMessagesExcludingSystem(promptstr) + if err != nil { + fmtf.Printf("Error getting system history: %v,promptstr[%v]\n", err, promptstr) + return + } + + // 处理增强QA逻辑 + if config.GetEnhancedQA(promptstr) { + // 确保系统历史与用户或助手历史数量一致,如果不足,则补足空的历史记录 + // 因为最后一个成员让给当前QA,所以-1 + if len(systemHistory)-2 > len(userHistory) { + difference := len(systemHistory) - len(userHistory) + for i := 0; i < difference; i++ { + userHistory = append(userHistory, structs.Message{Text: "", Role: "user"}) + userHistory = append(userHistory, structs.Message{Text: "", Role: "assistant"}) + } + } + + // 如果系统历史中只有一个成员,跳过覆盖逻辑,留给后续处理 + if len(systemHistory) > 1 { + // 将系统历史(除最后2个成员外)附加到相应的用户或助手历史上,采用倒序方式处理最近的记录 + for i := 0; i < len(systemHistory)-2; i++ { + sysMsg := systemHistory[i] + index := len(userHistory) - len(systemHistory) + i + if index >= 0 && index < len(userHistory) && (userHistory[index].Role == "user" || userHistory[index].Role == "assistant") { + userHistory[index].Text += fmt.Sprintf(" (%s)", sysMsg.Text) + } + } + } + } else { + // 将系统级别QA简单的附加在用户对话前方的位置(ai会知道,但不会主动引导) + history = append(history, systemHistory...) + } + + // 留下最后一个systemHistory成员进行后续处理 + } + + // 添加用户历史到总历史中 + history = append(history, userHistory...) + } + + fmtf.Printf("Tyqw上下文history:%v\n", history) + + // 构建请求到Tyqw API + apiURL := config.GetTyqwApiPath() + + // 构造消息历史和当前消息 + messages := []map[string]interface{}{} + for _, hMsg := range history { + messages = append(messages, map[string]interface{}{ + "role": hMsg.Role, + "content": hMsg.Text, + }) + } + + // 添加当前用户消息 + messages = append(messages, map[string]interface{}{ + "role": "user", + "content": msg.Text, + }) + + var isIncrementalOutput bool + if config.GetTyqwSseType() == 1 { + isIncrementalOutput = true + } + // 获取配置信息 + useSSE := config.GetuseSse(promptstr) + apiKey := config.GetTyqwKey() + var requestBody map[string]interface{} + if useSSE { + // 构建请求体,根据提供的文档重新调整 + requestBody = map[string]interface{}{ + "parameters": map[string]interface{}{ + "max_tokens": config.GetTyqwMaxTokens(), // 最大生成的token数 + "temperature": config.GetTyqwTemperature(), // 控制随机性和多样性的温度 + "top_p": config.GetTyqwTopP(), // 核采样方法的概率阈值 + "top_k": config.GetTyqwTopK(), // 采样候选集的大小 + "repetition_penalty": config.GetTyqwRepetitionPenalty(), // 控制重复度的惩罚因子 + "stop": config.GetTyqwStopTokens(), // 停止标记 + "seed": config.GetTyqwSeed(), // 随机数种子 + "result_format": "message", // 返回结果的格式 + "enable_search": config.GetTyqwEnableSearch(), // 是否启用互联网搜索 + "incremental_output": isIncrementalOutput, // 是否使用增量SSE模式,使用增量模式会更快,rwkv和openai不支持增量模式 + }, + "model": config.GetTyqwModel(), // 指定对话模型 + "input": map[string]interface{}{ + "messages": messages, // 用户与模型的对话历史 + }, + "user_name": config.GetTyqwUserName(), // 用户名 + "assistant_name": config.GetTyqwAssistantName(), // 助手名 + "system_name": config.GetTyqwSystemName(), // 系统名 + "presystem": config.GetTyqwPreSystem(), // 预系统处理信息 + } + } else { + // 构建请求体,根据提供的文档重新调整 + requestBody = map[string]interface{}{ + "parameters": map[string]interface{}{ + "max_tokens": config.GetTyqwMaxTokens(), // 最大生成的token数 + "temperature": config.GetTyqwTemperature(), // 控制随机性和多样性的温度 + "top_p": config.GetTyqwTopP(), // 核采样方法的概率阈值 + "top_k": config.GetTyqwTopK(), // 采样候选集的大小 + "repetition_penalty": config.GetTyqwRepetitionPenalty(), // 控制重复度的惩罚因子 + "stop": config.GetTyqwStopTokens(), // 停止标记 + "seed": config.GetTyqwSeed(), // 随机数种子 + "result_format": "message", // 返回结果的格式 + "enable_search": config.GetTyqwEnableSearch(), // 是否启用互联网搜索 + }, + "model": config.GetTyqwModel(), // 指定对话模型 + "input": map[string]interface{}{ + "messages": messages, // 用户与模型的对话历史 + }, + "user_name": config.GetTyqwUserName(), // 用户名 + "assistant_name": config.GetTyqwAssistantName(), // 助手名 + "system_name": config.GetTyqwSystemName(), // 系统名 + "presystem": config.GetTyqwPreSystem(), // 预系统处理信息 + } + } + + fmtf.Printf("tyqw requestBody :%v", requestBody) + requestBodyJSON, _ := json.Marshal(requestBody) + + // 准备HTTP请求 + client := &http.Client{} + req, err := http.NewRequest("POST", apiURL, bytes.NewBuffer(requestBodyJSON)) + if err != nil { + http.Error(w, fmtf.Sprintf("Failed to create request: %v", err), http.StatusInternalServerError) + return + } + + req.Header.Set("Content-Type", "application/json") + + // 设置Content-Type和Authorization + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+apiKey) + + // 根据是否使用SSE来设置Accept和X-DashScope-SSE + if useSSE { + req.Header.Set("Accept", "text/event-stream") + req.Header.Set("X-DashScope-SSE", "enable") + } + + // 设置工作区 + workspace, err := config.GetTyqworkspace() // 假设这是获取workspace的函数 + if err != nil { + fmt.Println("Error getting workspace:", err) + } + if workspace != "" { + req.Header.Set("X-DashScope-WorkSpace", workspace) + } + + // 发送请求 + resp, err := client.Do(req) + if err != nil { + http.Error(w, fmtf.Sprintf("Error sending request to tyqw API: %v", err), http.StatusInternalServerError) + return + } + defer resp.Body.Close() + + if !config.GetuseSse(promptstr) { + // 处理响应 + responseBody, err := io.ReadAll(resp.Body) + if err != nil { + http.Error(w, fmt.Sprintf("Failed to read response body: %v", err), http.StatusInternalServerError) + return + } + fmt.Printf("TYQW 返回: %v", string(responseBody)) + + var tyqwApiResponse struct { + Output struct { + Choices []struct { + FinishReason string `json:"finish_reason"` + Message struct { + Role string `json:"role"` + Content string `json:"content"` + } `json:"message"` + } `json:"choices"` + } `json:"output"` + Usage struct { + TotalTokens int `json:"total_tokens"` + OutputTokens int `json:"output_tokens"` + InputTokens int `json:"input_tokens"` + } `json:"usage"` + RequestID string `json:"request_id"` + } + + if err := json.Unmarshal(responseBody, &tyqwApiResponse); err != nil { + http.Error(w, fmt.Sprintf("Error unmarshaling response: %v", err), http.StatusInternalServerError) + return + } + + // 从API响应中获取回复文本 + if len(tyqwApiResponse.Output.Choices) > 0 { + responseText := tyqwApiResponse.Output.Choices[0].Message.Content + + // 添加助理消息 + assistantMessageID, err := app.addMessage(structs.Message{ + ConversationID: msg.ConversationID, + ParentMessageID: userMessageID, + Text: responseText, + Role: "assistant", + }) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + // 构造响应数据,包括回复文本、对话ID、消息ID,以及使用情况 + responseMap := map[string]interface{}{ + "response": responseText, + "conversationId": msg.ConversationID, + "messageId": assistantMessageID, + "details": map[string]interface{}{ + "usage": structs.UsageInfo{ + PromptTokens: tyqwApiResponse.Usage.InputTokens, // 实际值 + CompletionTokens: tyqwApiResponse.Usage.OutputTokens, // 实际值 + }, + }, + } + + // 设置响应头部为JSON格式 + w.Header().Set("Content-Type", "application/json") + // 将响应数据编码为JSON并发送 + if err := json.NewEncoder(w).Encode(responseMap); err != nil { + http.Error(w, fmt.Sprintf("Error encoding response: %v", err), http.StatusInternalServerError) + return + } + } else { + http.Error(w, "No response data available from TYQW API", http.StatusInternalServerError) + } + } else { + // 设置SSE相关的响应头部 + w.Header().Set("Content-Type", "text/event-stream") + w.Header().Set("Cache-Control", "no-cache") + w.Header().Set("Connection", "keep-alive") + + flusher, ok := w.(http.Flusher) + if !ok { + http.Error(w, "Streaming unsupported!", http.StatusInternalServerError) + return + } + + // 生成一个随机的UUID + randomUUID, err := uuid.NewRandom() + if err != nil { + http.Error(w, "Failed to generate UUID", http.StatusInternalServerError) + return + } + + reader := bufio.NewReader(resp.Body) + var totalUsage structs.GPTUsageInfo + if config.GetTyqwSseType() == 1 { + for { + line, err := reader.ReadString('\n') + if err != nil { + if err == io.EOF { + break // 流结束 + } + // 处理错误 + fmt.Fprintf(w, "data: %s\n\n", fmt.Sprintf("读取流数据时发生错误: %v", err)) + flusher.Flush() + continue + } + if strings.HasPrefix(line, "data:") { + eventDataJSON := line[5:] // 去掉"data: "前缀 + // 解析JSON数据 + var eventData structs.TyqwSSEData + if err := json.Unmarshal([]byte(eventDataJSON), &eventData); err != nil { + fmt.Fprintf(w, "data: %s\n\n", fmt.Sprintf("解析事件数据出错: %v", err)) + flusher.Flush() + continue + } + + // 如果存在需要发送的临时响应数据(例如,在事件流中间点) + tempResponseMap := map[string]interface{}{ + "response": eventData.Output.Choices[0].Message.Content, + "conversationId": msg.ConversationID, // 确保msg.ConversationID已经定义并初始化 + // "details" 字段留待进一步处理,如有必要 + } + tempResponseJSON, _ := json.Marshal(tempResponseMap) + fmt.Fprintf(w, "data: %s\n\n", string(tempResponseJSON)) + flusher.Flush() + + // 维护累加信息,发送最后事件 + conversationId := msg.ConversationID + randomUUID.String() + // 读取完整信息 + completeResponse, _ := lastCompleteResponsesTyqw.LoadOrStore(conversationId, "") + // 更新存储的完整累积信息 + updatedCompleteResponse := completeResponse.(string) + eventData.Output.Choices[0].Message.Content + lastCompleteResponsesTyqw.Store(conversationId, updatedCompleteResponse) + } + } + } else { + for { + line, err := reader.ReadString('\n') + if err != nil { + if err == io.EOF { + break // 流结束 + } + fmt.Fprintf(w, "data: %s\n\n", fmt.Sprintf("读取流数据时发生错误: %v", err)) + flusher.Flush() + continue + } + + if strings.HasPrefix(line, "data:") { + eventDataJSON := line[5:] // 去掉"data: "前缀 + if eventDataJSON[0] != '{' { + fmt.Println("非JSON数据,跳过:", eventDataJSON) + continue + } + + // 解析JSON数据 + var eventData structs.TyqwSSEData + if err := json.Unmarshal([]byte(eventDataJSON), &eventData); err != nil { + fmt.Fprintf(w, "data: %s\n\n", fmt.Sprintf("解析事件数据出错: %v", err)) + flusher.Flush() + continue + } + + // 在修改共享资源之前锁定Mutex + mutexTyqw.Lock() + + conversationId := msg.ConversationID + randomUUID.String() + //读取完整信息 + completeResponse, _ := lastCompleteResponsesTyqw.LoadOrStore(conversationId, "") + + // 检索上一次的响应文本 + lastResponse, _ := lastResponsesTyqw.LoadOrStore(conversationId, "") + lastResponseText := lastResponse.(string) + + newContent := "" + for _, choice := range eventData.Output.Choices { + content := choice.Message.Content + // 如果新内容以旧内容开头 + if strings.HasPrefix(content, lastResponseText) { + // 特殊情况:当新内容和旧内容完全相同时,处理逻辑应当与新内容不以旧内容开头时相同 + if content == lastResponseText { + newContent += content + } else { + // 剔除旧内容部分,只保留新增的部分 + newContent += content[len(lastResponseText):] + } + } else { + // 如果新内容不以旧内容开头,可能是并发情况下的新消息,直接使用新内容 + newContent += content + } + } + + // 更新存储的完整累积信息 + updatedCompleteResponse := completeResponse.(string) + newContent + lastCompleteResponsesTyqw.Store(conversationId, updatedCompleteResponse) + + // 使用累加的新内容更新存储的最后响应状态 + if newContent != "" { + lastResponsesTyqw.Store(conversationId, newContent) + } + + // 完成修改后解锁Mutex + mutexTyqw.Unlock() + + // 发送新增的内容 + if newContent != "" { + tempResponseMap := map[string]interface{}{ + "response": newContent, + "conversationId": conversationId, + } + tempResponseJSON, _ := json.Marshal(tempResponseMap) + fmt.Fprintf(w, "data: %s\n\n", string(tempResponseJSON)) + flusher.Flush() + } + } + } + } + + //一点点奇怪的转换 + conversationId := msg.ConversationID + randomUUID.String() + completeResponse, _ := lastCompleteResponsesTyqw.LoadOrStore(conversationId, "") + // 在所有事件处理完毕后发送最终响应 + assistantMessageID, err := app.addMessage(structs.Message{ + ConversationID: msg.ConversationID, + ParentMessageID: userMessageID, + Text: completeResponse.(string), + Role: "assistant", + }) + + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + // 在所有事件处理完毕后发送最终响应 + // 首先从 conversationMap 获取真实的 conversationId + if finalContent, ok := lastCompleteResponsesTyqw.Load(conversationId); ok { + finalResponseMap := map[string]interface{}{ + "response": finalContent, + "conversationId": conversationId, + "messageId": assistantMessageID, + "details": map[string]interface{}{ + "usage": totalUsage, + }, + } + finalResponseJSON, _ := json.Marshal(finalResponseMap) + fmtf.Fprintf(w, "data: %s\n\n", string(finalResponseJSON)) + flusher.Flush() + } + } + +} + +func truncateHistoryTyqw(history []structs.Message, prompt string, promptstr string) []structs.Message { + MAX_TOKENS := config.GetTyqwMaxTokens(promptstr) + + tokenCount := len(prompt) + for _, msg := range history { + tokenCount += len(msg.Text) + } + + if tokenCount >= MAX_TOKENS { + // 第一步:从开始逐个移除消息,直到满足令牌数量限制 + for tokenCount > MAX_TOKENS && len(history) > 0 { + tokenCount -= len(history[0].Text) + history = history[1:] + + // 确保移除后,历史记录仍然以user消息结尾 + if len(history) > 0 && history[0].Role == "assistant" { + tokenCount -= len(history[0].Text) + history = history[1:] + } + } + } + + // 第二步:检查并移除包含空文本的QA对 + for i := 0; i < len(history)-1; i++ { // 使用len(history)-1是因为我们要检查成对的消息 + q := history[i] + a := history[i+1] + + // 检查Q和A是否成对,且A的角色应为assistant,Q的角色为user,避免删除非QA对的消息 + if q.Role == "user" && a.Role == "assistant" && (len(q.Text) == 0 || len(a.Text) == 0) { + fmtf.Println("closeai-找到了空的对话: ", q, a) + // 移除这对QA + history = append(history[:i], history[i+2:]...) + i-- // 因为删除了元素,调整索引以正确检查下一个元素 + } + } + + // 确保以user结尾,如果不是则尝试移除直到满足条件 + if len(history) > 0 && history[len(history)-1].Role != "user" { + for len(history) > 0 && history[len(history)-1].Role != "user" { + history = history[:len(history)-1] + } + } + + return history +} diff --git a/config/config.go b/config/config.go index 1eac6f3..771e37d 100644 --- a/config/config.go +++ b/config/config.go @@ -2031,3 +2031,200 @@ func GetNo4Promptkeyboard() bool { } return false } + +// 获取TYQW API路径 +func GetTyqwApiPath() string { + mu.Lock() + defer mu.Unlock() + if instance != nil { + return instance.Settings.TyqwApiPath + } + return "" // 默认值或错误处理 +} + +// 获取TYQW最大Token数量,可接受basename作为参数 +func GetTyqwMaxTokens(options ...string) int { + mu.Lock() + defer mu.Unlock() + return getTyqwMaxTokensInternal(options...) +} + +// 内部逻辑执行函数,不处理锁,可以安全地递归调用 +func getTyqwMaxTokensInternal(options ...string) int { + // 检查是否有参数传递进来,以及是否为空字符串 + if len(options) == 0 || options[0] == "" { + if instance != nil { + return instance.Settings.TyqwMaxTokens + } + return 0 // 默认值或错误处理 + } + + // 使用传入的 basename + basename := options[0] + maxTokensInterface, err := prompt.GetSettingFromFilename(basename, "TyqwMaxTokens") + if err != nil { + log.Println("Error retrieving TyqwMaxTokens:", err) + return getTyqwMaxTokensInternal() // 递归调用内部函数,不传递任何参数 + } + + maxTokens, ok := maxTokensInterface.(int) + if !ok { // 检查类型断言是否失败 + fmt.Println("Type assertion failed for TyqwMaxTokens, fetching default") + return getTyqwMaxTokensInternal() // 递归调用内部函数,不传递任何参数 + } + + return maxTokens +} + +// 获取TYQW温度设置 +func GetTyqwTemperature() float64 { + mu.Lock() + defer mu.Unlock() + if instance != nil { + return instance.Settings.TyqwTemperature + } + return 0.0 // 默认值或错误处理 +} + +// 获取TYQW Top P +func GetTyqwTopP() float64 { + mu.Lock() + defer mu.Unlock() + if instance != nil { + return instance.Settings.TyqwTopP + } + return 0.0 // 默认值或错误处理 +} + +// 获取TYQW Top K +func GetTyqwTopK() int { + mu.Lock() + defer mu.Unlock() + if instance != nil { + return instance.Settings.TyqwTopK + } + return 0 // 默认值或错误处理 +} + +// 获取TYQW SSE类型 +func GetTyqwSseType() int { + mu.Lock() + defer mu.Unlock() + if instance != nil { + return instance.Settings.TyqwSseType + } + return 0 // 默认值或错误处理 +} + +// 获取TYQW用户名 +func GetTyqwUserName() string { + mu.Lock() + defer mu.Unlock() + if instance != nil { + return instance.Settings.TyqwUserName + } + return "" // 默认值或错误处理 +} + +// 获取TYQW助手名称 +func GetTyqwAssistantName() string { + mu.Lock() + defer mu.Unlock() + if instance != nil { + return instance.Settings.TyqwAssistantName + } + return "" // 默认值或错误处理 +} + +// 获取TYQW系统名称 +func GetTyqwSystemName() string { + mu.Lock() + defer mu.Unlock() + if instance != nil { + return instance.Settings.TyqwSystemName + } + return "" // 默认值或错误处理 +} + +// 获取TYQW是否在系统层面进行预处理 +func GetTyqwPreSystem() bool { + mu.Lock() + defer mu.Unlock() + if instance != nil { + return instance.Settings.TyqwPreSystem + } + return false // 默认值或错误处理 +} + +// 获取TYQW重复度惩罚因子 +func GetTyqwRepetitionPenalty() float64 { + mu.Lock() + defer mu.Unlock() + if instance != nil { + return instance.Settings.TyqwRepetitionPenalty + } + return 1.0 // 默认值或错误处理 +} + +// 获取TYQW停止标记 +func GetTyqwStopTokens() []string { + mu.Lock() + defer mu.Unlock() + if instance != nil { + return instance.Settings.TyqwStop + } + return nil // 默认值或错误处理 +} + +// 获取TYQW随机数种子 +func GetTyqwSeed() int64 { + mu.Lock() + defer mu.Unlock() + if instance != nil { + return instance.Settings.TyqwSeed + } + return 0 // 默认值或错误处理 +} + +// 获取TYQW是否启用互联网搜索 +func GetTyqwEnableSearch() bool { + mu.Lock() + defer mu.Unlock() + if instance != nil { + return instance.Settings.TyqwEnableSearch + } + return false // 默认值或错误处理 +} + +// 获取TYQW模型名称 +func GetTyqwModel() string { + mu.Lock() + defer mu.Unlock() + if instance != nil { + return instance.Settings.TyqwModel + } + return "default-model" // 默认值或错误处理 +} + +// 获取TYQW API Key +func GetTyqwKey() string { + mu.Lock() + defer mu.Unlock() + if instance != nil { + return instance.Settings.TyqwApiKey + } + return "" // 默认值或错误处理,表示没有找到有效的API Key +} + +// 获取TYQW Workspace +func GetTyqworkspace() (string, error) { + mu.Lock() + defer mu.Unlock() + if instance != nil { + if instance.Settings.TyqwWorkspace == "" { + return "", fmt.Errorf("workspace is not configured") // 错误处理,当workspace未配置时 + } + return instance.Settings.TyqwWorkspace, nil + } + return "", fmt.Errorf("configuration instance is not initialized") // 错误处理,当配置实例未初始化时 +} diff --git a/main.go b/main.go index 85147b3..1a04e04 100644 --- a/main.go +++ b/main.go @@ -149,6 +149,9 @@ func main() { case 3: // 如果API类型是3,使用app.chatHandlerRwkv http.HandleFunc("/conversation", app.ChatHandlerRwkv) + case 4: + // 如果API类型是4,使用app.chatHandlerTyqw + http.HandleFunc("/conversation", app.ChatHandlerTyqw) default: // 如果是其他值,可以选择一个默认的处理器或者记录一个错误 log.Printf("Unknown API type: %d", apiType) @@ -159,6 +162,7 @@ func main() { http.HandleFunc("/conversation_hunyuan", app.ChatHandlerHunyuan) http.HandleFunc("/conversation_ernie", app.ChatHandlerErnie) http.HandleFunc("/conversation_rwkv", app.ChatHandlerRwkv) + http.HandleFunc("/conversation_tyqw", app.ChatHandlerTyqw) } if config.GetSelfPath() != "" { rateLimiter := server.NewRateLimiter() diff --git a/readme.md b/readme.md index cf33904..22cc6a5 100644 --- a/readme.md +++ b/readme.md @@ -22,7 +22,7 @@ _✨ 适用于Gensokyo以及Onebotv11的大模型一键端 ✨_ 基于sqlite数据库自动维系上下文,对话模式中,使用重置 命令即可重置 -可设置system,角色卡,上下文长度,内置多种模型,混元,文心,chatgpt +可设置system,角色卡,上下文长度, 同时对外提供带有自动上下文的openai原始风味api(经典3参数,id,parent id,messgae) @@ -33,6 +33,7 @@ _✨ 适用于Gensokyo以及Onebotv11的大模型一键端 ✨_ 并发环境下的sse内存安全,支持维持多用户同时双向sse传输 --- + ## 效果 ![效果图](/pic/3.png) @@ -41,6 +42,26 @@ _✨ 适用于Gensokyo以及Onebotv11的大模型一键端 ✨_ --- +## 支持llm平台 + +腾讯混元 + +百度文心 + +阿里通义 + +清华智谱 + +OpenAI + +Rwkv runner + +将这些平台的api转换为统一的api结构,并提供了上下文,支持sse方式返回 + +通过在yml设置相应平台的token,设置AllApi=true,可以同时进行切换调用. + +--- + ## 安全性 多重完备安全措施,尽可能保证开发者和llm应用安全. diff --git a/structs/struct.go b/structs/struct.go index e01cf23..41e3c59 100644 --- a/structs/struct.go +++ b/structs/struct.go @@ -153,6 +153,24 @@ type GPTEventData struct { } `json:"choices"` } +type TyqwSSEData struct { + Output struct { + Choices []struct { + Message struct { + Content string `json:"content"` + Role string `json:"role"` + } `json:"message"` + FinishReason string `json:"finish_reason"` + } `json:"choices"` + } `json:"output"` + Usage struct { + TotalTokens int `json:"total_tokens"` + InputTokens int `json:"input_tokens"` + OutputTokens int `json:"output_tokens"` + } `json:"usage"` + RequestID string `json:"request_id"` +} + // 定义用于累积使用情况的结构(如果API提供此信息) type GPTUsageInfo struct { PromptTokens int `json:"prompt_tokens"` @@ -316,6 +334,28 @@ type Settings struct { RwkvSseType int `yaml:"rwkvSseType"` HideExtraLogs bool `yaml:"hideExtraLogs"` + TyqwApiPath string `yaml:"tyqwApiPath"` + TyqwMaxTokens int `yaml:"tyqwMaxTokens"` + TyqwTemperature float64 `yaml:"tyqwTemperature"` + TyqwTopP float64 `yaml:"tyqwTopP"` + TyqwPresencePenalty float64 `yaml:"tyqwPresencePenalty"` + TyqwFrequencyPenalty float64 `yaml:"tyqwFrequencyPenalty"` + TyqwPenaltyDecay float64 `yaml:"tyqwPenaltyDecay"` + TyqwTopK int `yaml:"tyqwTopK"` + TyqwGlobalPenalty bool `yaml:"tyqwGlobalPenalty"` + TyqwStop []string `yaml:"tyqwStop"` + TyqwUserName string `yaml:"tyqwUserName"` + TyqwAssistantName string `yaml:"tyqwAssistantName"` + TyqwSystemName string `yaml:"tyqwSystemName"` + TyqwPreSystem bool `yaml:"tyqwPreSystem"` + TyqwSseType int `yaml:"tyqwSseType"` + TyqwRepetitionPenalty float64 `yaml:"tyqwRepetitionPenalty"` + TyqwSeed int64 `yaml:"tyqwSeed"` + TyqwEnableSearch bool `yaml:"tyqwEnableSearch"` + TyqwModel string `yaml:"tyqwModel"` + TyqwApiKey string `yaml:"tyqwApiKey"` + TyqwWorkspace string `yaml:"tyqwWorkspace"` + WSServerToken string `yaml:"wsServerToken"` WSPath string `yaml:"wsPath"` diff --git a/template/config_template.go b/template/config_template.go index 9b66d63..efe2667 100644 --- a/template/config_template.go +++ b/template/config_template.go @@ -12,7 +12,7 @@ settings: path : "http://123.123.123.123:11111" #调用gensokyo api的地址,填入 gensokyo 的 正向http地址 http_address: "0.0.0.0:46231" 对应填入 "http://127.0.0.1:46231" lotus : "" #当填写另一个gensokyo-llm的http地址时,将请求另一个的conversation端点,实现多个llm不需要多次配置,简化配置,单独使用请忽略留空.例:http://192.168.0.1:12345(包含http头和端口) pathToken : "" #gensokyo正向http-api的access_token(是onebotv11标准的) - apiType : 0 #0=混元 1=文心(文心平台包含了N种模型...) 2=gpt + apiType : 0 #0=混元 1=文心(文心平台包含了N种模型...) 2=gpt 3=rwkv 4=通义千问 5=智谱AI iPWhiteList : ["192.168.0.102"] #接口调用,安全ip白名单,gensokyo的ip地址,或调用api的程序的ip地址 systemPrompt : [""] #人格提示词,或多个随机 firstQ : [""] #强化思想钢印,在每次对话的system之前固定一个QA,需都填写内容,会增加token消耗,可一定程度提高人格提示词效果,或抵抗催眠 @@ -139,6 +139,31 @@ settings: rwkvAssistantName: "Assistant" # 助手名称 rwkvSystemName: "System" # 系统名称 rwkvPreSystem: false # 是否在系统层面进行预处理 + + # TYQW 模型配置文件,适用于对接您的平台。请遵守并符合相应的API资质要求。 + tyqwApiPath: "https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation" # 符合 TYQW 标准的 API 地址,是否以流形式取决于UseSSE配置 + tyqwMaxTokens: 1500 # 最大的输出 Token 数量 + tyqwModel : "" # 指定用于对话的通义千问模型名,目前可选择qwen-turbo、qwen-plus、qwen-max、qwen-max-0403、qwen-max-0107、qwen-max-1201和qwen-max-longcontext。 + tyqwApiKey : "" # api的key + tyqwWorkspace : "" # 指明本次调用需要使用的workspace;需要注意的是,对于子账号Apikey调用,此参数为必选项,子账号必须归属于某个workspace才能调用;对于主账号Apikey此项为可选项,添加则使用对应的workspace身份,不添加则使用主账号身份。 + tyqwTemperature: 0.85 # 生成的随机性控制 + tyqwTopP: 0.9 # 累积概率最高的令牌进行采样的界限 + tyqwPresencePenalty: 0.2 # 当前上下文中令牌出现的频率惩罚 + tyqwFrequencyPenalty: 0.2 # 全局令牌出现的频率惩罚 + tyqwPenaltyDecay: 0.99 # 惩罚值的衰减率 + tyqwRepetitionPenalty : 1.1 # 用于控制模型生成时的重复度。提高repetition_penalty时可以降低模型生成的重复度。1.0表示不做惩罚,默认为1.1。没有严格的取值范围。 + tyqwTopK: 40 # 从概率最高的K个令牌中采样 + tyqwSeed : 1234 # 生成时使用的随机数种子,用户控制模型生成内容的随机性。seed支持无符号64位整数,默认值为1234。在使用seed时,模型将尽可能生成相同或相似的结果,但目前不保证每次生成的结果完全相同。 + tyqwSseType: 1 # 同gptSseType, 例如0代表不使用, 1代表使用 + tyqwGlobalPenalty: false # 是否在全局上应用频率惩罚 + tyqwStop: # 停止生成的标记列表 + - "\n\nUser" + tyqwUserName: "User" # 用户名称 + tyqwAssistantName: "Assistant" # 助手名称 + tyqwSystemName: "System" # 系统名称 + tyqwPreSystem: false # 是否在系统层面进行预处理 + tyqwEnableSearch : false # 是否使用网络搜索 + ` const Logo = `