176 lines
5.5 KiB
Go
176 lines
5.5 KiB
Go
package agentllm
|
||
|
||
import (
|
||
"context"
|
||
"encoding/json"
|
||
"fmt"
|
||
"strings"
|
||
|
||
agentprompt "github.com/LoveLosita/smartflow/backend/agent2/prompt"
|
||
"github.com/LoveLosita/smartflow/backend/model"
|
||
"github.com/cloudwego/eino-ext/components/model/ark"
|
||
einoModel "github.com/cloudwego/eino/components/model"
|
||
"github.com/cloudwego/eino/schema"
|
||
arkModel "github.com/volcengine/volcengine-go-sdk/service/arkruntime/model"
|
||
)
|
||
|
||
// ScheduleIntentOutput 是 plan 节点要求模型返回的结构化结果。
|
||
//
|
||
// 兼容说明:
|
||
// 1. 新主语义是 task_class_ids(数组);
|
||
// 2. 为兼容旧 prompt/旧缓存输出,保留 task_class_id(单值)兜底解析;
|
||
// 3. TaskTags 的 key 兼容两种写法:
|
||
// 3.1 推荐:task_item_id(例如 "12");
|
||
// 3.2 兼容:任务名称(例如 "高数复习")。
|
||
type ScheduleIntentOutput struct {
|
||
Intent string `json:"intent"`
|
||
Constraints []string `json:"constraints"`
|
||
TaskClassIDs []int `json:"task_class_ids"`
|
||
TaskClassID int `json:"task_class_id"`
|
||
Strategy string `json:"strategy"`
|
||
TaskTags map[string]string `json:"task_tags"`
|
||
Restart bool `json:"restart"`
|
||
AdjustmentScope string `json:"adjustment_scope"`
|
||
Reason string `json:"reason"`
|
||
Confidence float64 `json:"confidence"`
|
||
}
|
||
|
||
// ReactToolCall 是 LLM 输出的单个工具调用。
|
||
type ReactToolCall struct {
|
||
Tool string `json:"tool"`
|
||
Params map[string]any `json:"params"`
|
||
}
|
||
|
||
// ReactLLMOutput 是 ReAct 节点要求模型返回的统一 JSON。
|
||
type ReactLLMOutput struct {
|
||
Done bool `json:"done"`
|
||
Summary string `json:"summary"`
|
||
ToolCalls []ReactToolCall `json:"tool_calls"`
|
||
}
|
||
|
||
// IdentifySchedulePlanIntent 调用模型识别“排程意图 + 约束 + 任务类集合”。
|
||
func IdentifySchedulePlanIntent(
|
||
ctx context.Context,
|
||
chatModel *ark.ChatModel,
|
||
nowText string,
|
||
userMessage string,
|
||
adjustmentHint string,
|
||
) (*ScheduleIntentOutput, error) {
|
||
prompt := fmt.Sprintf(
|
||
"当前时间(北京时间):%s\n用户输入:%s%s\n\n请提取排程意图与约束。",
|
||
strings.TrimSpace(nowText),
|
||
strings.TrimSpace(userMessage),
|
||
strings.TrimSpace(adjustmentHint),
|
||
)
|
||
|
||
parsed, _, err := CallArkJSON[ScheduleIntentOutput](ctx, chatModel, agentprompt.SchedulePlanIntentPrompt, prompt, ArkCallOptions{
|
||
Temperature: 0,
|
||
MaxTokens: 256,
|
||
Thinking: ThinkingModeDisabled,
|
||
})
|
||
return parsed, err
|
||
}
|
||
|
||
// ParseScheduleReactOutput 解析 ReAct 节点的 JSON 输出。
|
||
func ParseScheduleReactOutput(raw string) (*ReactLLMOutput, error) {
|
||
return ParseJSONObject[ReactLLMOutput](raw)
|
||
}
|
||
|
||
// GenerateScheduleDailyReactRound 调用模型生成“单天日内优化”的一轮决策。
|
||
//
|
||
// 职责边界:
|
||
// 1. 只负责统一关闭 thinking、设置温度,并返回纯文本;
|
||
// 2. 不负责工具执行,不负责结果回灌。
|
||
func GenerateScheduleDailyReactRound(
|
||
ctx context.Context,
|
||
chatModel *ark.ChatModel,
|
||
messages []*schema.Message,
|
||
) (string, error) {
|
||
resp, err := chatModel.Generate(
|
||
ctx,
|
||
messages,
|
||
ark.WithThinking(&arkModel.Thinking{Type: arkModel.ThinkingTypeDisabled}),
|
||
einoModel.WithTemperature(0),
|
||
)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
if resp == nil {
|
||
return "", fmt.Errorf("日内优化调用返回为空")
|
||
}
|
||
content := strings.TrimSpace(resp.Content)
|
||
if content == "" {
|
||
return "", fmt.Errorf("日内优化调用返回内容为空")
|
||
}
|
||
return content, nil
|
||
}
|
||
|
||
// GenerateScheduleWeeklyReactRound 调用模型生成“单周单步优化”的一轮决策。
|
||
//
|
||
// 职责边界:
|
||
// 1. 周级仍保留 thinking,提高复杂排程准确率;
|
||
// 2. 仅返回最终 content,是否透出思考流由上层决定。
|
||
func GenerateScheduleWeeklyReactRound(
|
||
ctx context.Context,
|
||
chatModel *ark.ChatModel,
|
||
messages []*schema.Message,
|
||
) (string, error) {
|
||
resp, err := chatModel.Generate(
|
||
ctx,
|
||
messages,
|
||
ark.WithThinking(&arkModel.Thinking{Type: arkModel.ThinkingTypeEnabled}),
|
||
einoModel.WithTemperature(0.2),
|
||
)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
if resp == nil {
|
||
return "", fmt.Errorf("周级单步调用返回为空")
|
||
}
|
||
content := strings.TrimSpace(resp.Content)
|
||
if content == "" {
|
||
return "", fmt.Errorf("周级单步调用返回内容为空")
|
||
}
|
||
return content, nil
|
||
}
|
||
|
||
// GenerateScheduleHumanSummary 调用模型生成“用户可读”的最终总结。
|
||
func GenerateScheduleHumanSummary(
|
||
ctx context.Context,
|
||
chatModel *ark.ChatModel,
|
||
entries []model.HybridScheduleEntry,
|
||
constraints []string,
|
||
actionLogs []string,
|
||
) (string, error) {
|
||
if chatModel == nil {
|
||
return "", fmt.Errorf("final summary model is nil")
|
||
}
|
||
|
||
entriesJSON, _ := json.Marshal(entries)
|
||
constraintText := "无"
|
||
if len(constraints) > 0 {
|
||
constraintText = strings.Join(constraints, "、")
|
||
}
|
||
actionLogText := "无"
|
||
if len(actionLogs) > 0 {
|
||
start := 0
|
||
if len(actionLogs) > 30 {
|
||
start = len(actionLogs) - 30
|
||
}
|
||
actionLogText = strings.Join(actionLogs[start:], "\n")
|
||
}
|
||
|
||
userPrompt := fmt.Sprintf(
|
||
"以下是最终排程方案(JSON):\n%s\n\n用户约束:%s\n\n以下是本次周级优化动作日志(按时间顺序):\n%s\n\n请基于“结果+过程”输出2-3句自然中文总结,重点说明本方案的优点和改进点。",
|
||
string(entriesJSON),
|
||
constraintText,
|
||
actionLogText,
|
||
)
|
||
|
||
return CallArkText(ctx, chatModel, agentprompt.SchedulePlanFinalCheckPrompt, userPrompt, ArkCallOptions{
|
||
Temperature: 0.4,
|
||
MaxTokens: 256,
|
||
Thinking: ThinkingModeDisabled,
|
||
})
|
||
}
|