2026-05-18 19:19:17 +08:00
|
|
|
|
package prompt
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"context"
|
|
|
|
|
|
"fmt"
|
2026-06-03 13:30:39 +08:00
|
|
|
|
"prompts-core/service/gateway"
|
2026-05-18 19:19:17 +08:00
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
|
|
|
|
"prompts-core/common/util"
|
|
|
|
|
|
"prompts-core/dao"
|
2026-05-20 11:36:39 +08:00
|
|
|
|
"prompts-core/model/dto"
|
2026-05-18 19:19:17 +08:00
|
|
|
|
"prompts-core/model/entity"
|
|
|
|
|
|
|
2026-06-03 18:37:18 +08:00
|
|
|
|
"gitea.com/red-future/common/utils"
|
2026-05-23 18:08:09 +08:00
|
|
|
|
"github.com/gogf/gf/v2/encoding/gjson"
|
2026-05-18 19:19:17 +08:00
|
|
|
|
"github.com/gogf/gf/v2/util/gconv"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2026-05-20 11:36:39 +08:00
|
|
|
|
// buildPromptTypeRequest 构建提示词类型请求(BuildType=1)
|
2026-06-03 18:37:18 +08:00
|
|
|
|
func buildPromptTypeRequest(ctx context.Context, req *dto.ComposeMessagesReq, aiModel *gateway.AsynchModel, chatModel *gateway.AsynchModel, ir *PromptIR, totalBatches int) (map[string]any, error) {
|
2026-05-29 17:54:19 +08:00
|
|
|
|
//1) 构建系统提示词
|
2026-06-05 11:00:05 +08:00
|
|
|
|
systemPrompt := promptBuildWithRounds(ctx, chatModel, aiModel)
|
2026-05-20 11:36:39 +08:00
|
|
|
|
ir.AddSystem(systemPrompt)
|
2026-05-22 09:49:46 +08:00
|
|
|
|
userPrompt := buildUserPrompt(ctx, req, util.GetModelPrompt(ctx, aiModel.ModelType))
|
2026-05-20 11:36:39 +08:00
|
|
|
|
ir.AddUser(userPrompt)
|
2026-06-05 11:00:05 +08:00
|
|
|
|
//2) 检查整体内容是否超出窗口
|
2026-05-22 09:49:46 +08:00
|
|
|
|
if !checkOverallContent(ir, aiModel) {
|
|
|
|
|
|
availableWindow := util.GetAvailableWindow(aiModel.TokenConfig)
|
2026-05-20 11:36:39 +08:00
|
|
|
|
return nil, fmt.Errorf("整体内容超出模型窗口大小限制(可用窗口=%d tokens),请精简后重试", availableWindow)
|
|
|
|
|
|
}
|
2026-05-22 09:49:46 +08:00
|
|
|
|
return compileToProviderRequest(ctx, ir, chatModel)
|
2026-05-20 11:36:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// buildNodeTypeRequest 构建节点类型请求(BuildType=2)
|
2026-06-03 13:30:39 +08:00
|
|
|
|
func buildNodeTypeRequest(ctx context.Context, req *dto.ComposeMessagesReq, chatModel *gateway.AsynchModel, ir *PromptIR) (map[string]any, error) {
|
2026-05-20 11:36:39 +08:00
|
|
|
|
ir.AddUser(NodeBuild(ctx, req))
|
2026-05-22 09:49:46 +08:00
|
|
|
|
return compileToProviderRequest(ctx, ir, chatModel)
|
2026-05-20 11:36:39 +08:00
|
|
|
|
}
|
2026-05-18 19:19:17 +08:00
|
|
|
|
|
2026-06-03 18:37:18 +08:00
|
|
|
|
// buildStructTypeRequest 构建结构体类型请求(BuildType=3)
|
|
|
|
|
|
func buildStructTypeRequest(ctx context.Context, req *dto.ComposeMessagesReq, chatModel *gateway.AsynchModel, ir *PromptIR) (map[string]any, error) {
|
|
|
|
|
|
// 提取 userForm 中的 prompt 作为自定义提示词
|
|
|
|
|
|
var customPrompt string
|
|
|
|
|
|
for _, item := range req.UserForm {
|
|
|
|
|
|
if prompt, ok := item["prompt"]; ok && gconv.String(prompt) != "" {
|
|
|
|
|
|
customPrompt = gconv.String(prompt)
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// 用户消息
|
|
|
|
|
|
ir.AddSystem(customPrompt)
|
|
|
|
|
|
ir.AddUser(buildUserPrompt(ctx, req, ""))
|
|
|
|
|
|
return compileToProviderRequest(ctx, ir, chatModel, customPrompt)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-20 11:36:39 +08:00
|
|
|
|
// compileToProviderRequest 编译为 Provider 请求
|
2026-06-03 18:37:18 +08:00
|
|
|
|
func compileToProviderRequest(ctx context.Context, ir *PromptIR, chatModel *gateway.AsynchModel, customPrompt ...string) (map[string]any, error) {
|
2026-05-22 09:49:46 +08:00
|
|
|
|
protocol, err := GetProtocolByProvider(ctx, chatModel.OperatorName)
|
2026-06-03 18:37:18 +08:00
|
|
|
|
if err != nil || protocol == nil {
|
|
|
|
|
|
return nil, fmt.Errorf("协议配置不存在或获取失败: %w", err)
|
2026-05-18 19:19:17 +08:00
|
|
|
|
}
|
2026-06-03 18:37:18 +08:00
|
|
|
|
// 如果传了自定义提示词,替换掉协议模板
|
|
|
|
|
|
if len(customPrompt) > 0 && customPrompt[0] != "" {
|
|
|
|
|
|
protocol.SystemPromptTemplate = customPrompt[0]
|
2026-05-18 19:19:17 +08:00
|
|
|
|
}
|
2026-05-21 10:53:58 +08:00
|
|
|
|
providerReq, err := Compile(ir, protocol, chatModel)
|
2026-05-18 19:19:17 +08:00
|
|
|
|
if err != nil {
|
2026-05-20 11:36:39 +08:00
|
|
|
|
return nil, fmt.Errorf("编译请求失败: %w", err)
|
2026-05-18 19:19:17 +08:00
|
|
|
|
}
|
|
|
|
|
|
return map[string]any{
|
2026-05-22 09:49:46 +08:00
|
|
|
|
"modelName": chatModel.ModelName,
|
2026-05-29 17:54:19 +08:00
|
|
|
|
"bizName": util.GetServerName(ctx),
|
2026-06-03 18:37:18 +08:00
|
|
|
|
"callbackUrl": utils.GetCallbackURL(ctx, "/prompt/callback"),
|
2026-05-18 19:19:17 +08:00
|
|
|
|
"requestPayload": providerReq,
|
|
|
|
|
|
}, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-05 11:00:05 +08:00
|
|
|
|
func promptBuildWithRounds(ctx context.Context, chatModel *gateway.AsynchModel, aiModel *gateway.AsynchModel) string {
|
2026-05-18 19:19:17 +08:00
|
|
|
|
providerProtocol, err := dao.ProviderProtocol.Get(ctx, &entity.ProviderProtocol{
|
2026-05-29 17:54:19 +08:00
|
|
|
|
ProviderName: chatModel.OperatorName,
|
2026-05-18 19:19:17 +08:00
|
|
|
|
Status: 1,
|
|
|
|
|
|
})
|
|
|
|
|
|
if err != nil || providerProtocol == nil {
|
|
|
|
|
|
return ""
|
|
|
|
|
|
}
|
2026-05-29 17:54:19 +08:00
|
|
|
|
outputJSON := util.JSONPretty(util.ReverseMap(aiModel.RequestMapping, map[string]any{}))
|
2026-05-20 11:36:39 +08:00
|
|
|
|
return fmt.Sprintf(providerProtocol.SystemPromptTemplate,
|
2026-06-05 11:00:05 +08:00
|
|
|
|
outputJSON, //【输出结构】 %s
|
2026-05-20 11:36:39 +08:00
|
|
|
|
)
|
|
|
|
|
|
}
|
2026-05-18 19:19:17 +08:00
|
|
|
|
|
2026-05-20 11:36:39 +08:00
|
|
|
|
// buildUserFormContent 构建用户表单内容字符串
|
|
|
|
|
|
func buildUserFormContent(userForm []map[string]any) string {
|
|
|
|
|
|
var builder strings.Builder
|
|
|
|
|
|
for _, item := range userForm {
|
|
|
|
|
|
builder.WriteString(fmt.Sprintf("%v\n", item))
|
|
|
|
|
|
}
|
|
|
|
|
|
return builder.String()
|
2026-05-18 19:19:17 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-20 11:36:39 +08:00
|
|
|
|
// checkOverallContent 检查整体内容是否超出窗口
|
2026-06-03 13:30:39 +08:00
|
|
|
|
func checkOverallContent(ir *PromptIR, model *gateway.AsynchModel) bool {
|
2026-05-20 11:36:39 +08:00
|
|
|
|
fullContent := ir.String()
|
|
|
|
|
|
return util.CountToken(fullContent, model.TokenConfig)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// buildUserPrompt 构建用户提示词
|
|
|
|
|
|
func buildUserPrompt(ctx context.Context, req *dto.ComposeMessagesReq, prompt string) string {
|
2026-06-05 11:00:05 +08:00
|
|
|
|
var b strings.Builder
|
|
|
|
|
|
b.WriteString(fmt.Sprintf("目标模型:%s\n", req.ModelName))
|
|
|
|
|
|
if prompt != "" {
|
|
|
|
|
|
b.WriteString(fmt.Sprintf("系统提示词:%s\n", prompt))
|
2026-05-20 11:36:39 +08:00
|
|
|
|
}
|
2026-06-05 11:00:05 +08:00
|
|
|
|
if skills := SkillMdContent(ctx, req.SkillName); skills != "" {
|
|
|
|
|
|
b.WriteString(fmt.Sprintf("技能内容:\n%s\n", skills))
|
2026-05-20 11:36:39 +08:00
|
|
|
|
}
|
2026-06-05 11:00:05 +08:00
|
|
|
|
if formText := buildUserFormText(req.Form); formText != "" {
|
|
|
|
|
|
b.WriteString(fmt.Sprintf("系统参数:\n%s\n", formText))
|
|
|
|
|
|
}
|
|
|
|
|
|
if userFormText := buildUserFormText(req.UserForm); userFormText != "" {
|
|
|
|
|
|
b.WriteString(fmt.Sprintf("用户需求:\n%s\n", userFormText))
|
|
|
|
|
|
}
|
|
|
|
|
|
if len(req.Consult) > 0 {
|
|
|
|
|
|
b.WriteString(fmt.Sprintf("参考附件:%s\n", gjson.New(req.Consult).String()))
|
|
|
|
|
|
}
|
|
|
|
|
|
if fileTexts := ExtractFileTexts(ctx, req.Consult); fileTexts != "" {
|
|
|
|
|
|
b.WriteString(fmt.Sprintf("附件内容:\n%s\n", fileTexts))
|
|
|
|
|
|
}
|
|
|
|
|
|
return b.String()
|
2026-05-20 11:36:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-05 11:00:05 +08:00
|
|
|
|
// buildUserFormText 构建用户表单内容字符串
|
|
|
|
|
|
func buildUserFormText(form []map[string]any) string {
|
|
|
|
|
|
if len(form) == 0 {
|
|
|
|
|
|
return ""
|
|
|
|
|
|
}
|
2026-05-20 11:36:39 +08:00
|
|
|
|
var builder strings.Builder
|
2026-06-05 11:00:05 +08:00
|
|
|
|
for _, item := range form {
|
|
|
|
|
|
for k, v := range item {
|
|
|
|
|
|
switch val := v.(type) {
|
|
|
|
|
|
case []any:
|
|
|
|
|
|
// 数组类型:逐条列出
|
|
|
|
|
|
builder.WriteString(fmt.Sprintf("%s:\n", k))
|
|
|
|
|
|
for i, elem := range val {
|
|
|
|
|
|
if m, ok := elem.(map[string]any); ok {
|
|
|
|
|
|
builder.WriteString(fmt.Sprintf(" %d. ", i+1))
|
|
|
|
|
|
for mk, mv := range m {
|
|
|
|
|
|
builder.WriteString(fmt.Sprintf("%s:%v ", mk, mv))
|
|
|
|
|
|
}
|
|
|
|
|
|
builder.WriteString("\n")
|
|
|
|
|
|
} else {
|
|
|
|
|
|
builder.WriteString(fmt.Sprintf(" %d. %v\n", i+1, elem))
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
case []map[string]any:
|
|
|
|
|
|
builder.WriteString(fmt.Sprintf("%s:\n", k))
|
|
|
|
|
|
for i, m := range val {
|
|
|
|
|
|
builder.WriteString(fmt.Sprintf(" %d. ", i+1))
|
|
|
|
|
|
for mk, mv := range m {
|
|
|
|
|
|
builder.WriteString(fmt.Sprintf("%s:%v ", mk, mv))
|
|
|
|
|
|
}
|
|
|
|
|
|
builder.WriteString("\n")
|
|
|
|
|
|
}
|
|
|
|
|
|
default:
|
|
|
|
|
|
builder.WriteString(fmt.Sprintf("%s:%v\n", k, v))
|
|
|
|
|
|
}
|
2026-05-20 11:36:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-06-05 11:00:05 +08:00
|
|
|
|
return strings.TrimSpace(builder.String())
|
2026-05-20 11:36:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-18 19:19:17 +08:00
|
|
|
|
// NodeBuild 节点构建
|
2026-05-20 11:36:39 +08:00
|
|
|
|
func NodeBuild(ctx context.Context, req *dto.ComposeMessagesReq) string {
|
|
|
|
|
|
promptTpl := util.GetBuildPrompt(ctx)
|
2026-05-18 19:19:17 +08:00
|
|
|
|
if promptTpl == "" {
|
|
|
|
|
|
return ""
|
|
|
|
|
|
}
|
2026-05-20 11:36:39 +08:00
|
|
|
|
|
2026-05-18 19:19:17 +08:00
|
|
|
|
formStr := util.FormToJSON(req.Form)
|
2026-05-20 11:36:39 +08:00
|
|
|
|
userFormStr := util.UserFormToJSON(req.UserForm)
|
|
|
|
|
|
|
2026-05-18 19:19:17 +08:00
|
|
|
|
return fmt.Sprintf(promptTpl, formStr, userFormStr)
|
|
|
|
|
|
}
|