155 lines
4.9 KiB
Go
155 lines
4.9 KiB
Go
package reasoningmode
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
const defaultIdleTimeout = 10 * time.Minute
|
|
|
|
type Scope struct {
|
|
TenantKey string
|
|
Channel string
|
|
ExternalActorID string
|
|
}
|
|
|
|
type TurnCompletion struct {
|
|
Scope Scope
|
|
TurnID string
|
|
Execution ExecutionMode
|
|
Answer string
|
|
Locale string
|
|
Expired bool
|
|
At time.Time
|
|
Idle time.Duration
|
|
}
|
|
|
|
type Repository interface {
|
|
ResolveState(context.Context, Scope, time.Time) (State, bool, error)
|
|
Enable(context.Context, Scope, time.Time, time.Duration) (State, error)
|
|
Disable(context.Context, Scope, time.Time) (State, bool, error)
|
|
SetMaxRounds(context.Context, Scope, int, time.Time) (State, error)
|
|
SetHintEnabled(context.Context, Scope, bool, time.Time) (State, error)
|
|
CompleteTurn(context.Context, TurnCompletion) (Completion, error)
|
|
}
|
|
|
|
type Decision struct {
|
|
Handled bool
|
|
Reply string
|
|
Query string
|
|
AgentID string
|
|
Execution ExecutionMode
|
|
Expired bool
|
|
}
|
|
|
|
type Service struct {
|
|
repo Repository
|
|
quickAgentID string
|
|
reasoningAgentID string
|
|
now func() time.Time
|
|
idleTimeout time.Duration
|
|
}
|
|
|
|
func NewService(repo Repository, quickAgentID, reasoningAgentID string) (*Service, error) {
|
|
quickAgentID = strings.TrimSpace(quickAgentID)
|
|
reasoningAgentID = strings.TrimSpace(reasoningAgentID)
|
|
if repo == nil {
|
|
return nil, errors.New("reasoning mode repository is required")
|
|
}
|
|
if quickAgentID == "" || reasoningAgentID == "" {
|
|
return nil, errors.New("quick and reasoning agent IDs are required")
|
|
}
|
|
if quickAgentID == reasoningAgentID {
|
|
return nil, errors.New("quick and reasoning agent IDs must differ")
|
|
}
|
|
return &Service{
|
|
repo: repo,
|
|
quickAgentID: quickAgentID,
|
|
reasoningAgentID: reasoningAgentID,
|
|
now: time.Now,
|
|
idleTimeout: defaultIdleTimeout,
|
|
}, nil
|
|
}
|
|
|
|
func (s *Service) Prepare(ctx context.Context, scope Scope, text string) (Decision, error) {
|
|
if err := validateScope(scope); err != nil {
|
|
return Decision{}, err
|
|
}
|
|
query := strings.TrimSpace(text)
|
|
if query == "" {
|
|
return Decision{}, errors.New("reasoning mode query is required")
|
|
}
|
|
|
|
if command, ok := ParseCommand(query); ok {
|
|
switch command.Kind {
|
|
case CommandEnable:
|
|
state, err := s.repo.Enable(ctx, scope, s.now(), s.idleTimeout)
|
|
if err != nil {
|
|
return Decision{}, err
|
|
}
|
|
return Decision{Handled: true, Reply: fmt.Sprintf(
|
|
"✅ 已开启【智能推理模式】(计费标准更高)\n持续模式将在%d轮对话后自动关闭,也可随时输入 /think off 手动关闭",
|
|
state.MaxRounds,
|
|
)}, nil
|
|
case CommandDisable:
|
|
_, wasReasoning, err := s.repo.Disable(ctx, scope, s.now())
|
|
if err != nil {
|
|
return Decision{}, err
|
|
}
|
|
if !wasReasoning {
|
|
return Decision{Handled: true, Reply: "当前未开启推理模式,默认是快速问答模式。"}, nil
|
|
}
|
|
return Decision{Handled: true, Reply: "✅ 已关闭智能推理模式,切回【快速问答模式】"}, nil
|
|
case CommandSingle:
|
|
return Decision{Query: command.Query, AgentID: s.reasoningAgentID, Execution: ExecutionReasoningSingle}, nil
|
|
case CommandHelp:
|
|
return Decision{Handled: true, Reply: helpReply()}, nil
|
|
default:
|
|
return Decision{Handled: true, Reply: "用法:/think <问题>、/think on、/think off。"}, nil
|
|
}
|
|
}
|
|
|
|
state, expired, err := s.repo.ResolveState(ctx, scope, s.now())
|
|
if err != nil {
|
|
return Decision{}, err
|
|
}
|
|
if state.Mode == ModeReasoning {
|
|
return Decision{Query: query, AgentID: s.reasoningAgentID, Execution: ExecutionReasoning}, nil
|
|
}
|
|
return Decision{Query: query, AgentID: s.quickAgentID, Execution: ExecutionQuick, Expired: expired}, nil
|
|
}
|
|
|
|
func helpReply() string {
|
|
return "一、模式说明\n" +
|
|
"默认模式:快速问答模式,基于知识库快速准确作答,响应快、成本低,适合信息查询和事实确认。\n" +
|
|
"智能推理模式:支持多步思考与工具调用,计费标准更高,适合复杂分析和推导。\n\n" +
|
|
"二、模式切换指令\n" +
|
|
"/think + 你的问题:仅当前问题使用智能推理\n" +
|
|
"/think on:开启持续智能推理模式\n" +
|
|
"/think off 或 /rag:关闭并切回快速问答模式\n\n" +
|
|
"三、其他常用指令\n" +
|
|
"/help:查看本帮助说明\n" +
|
|
"/setting 或 /设置:配置语言、自动关闭轮数和提示开关\n\n" +
|
|
"只有行首以 / 开头的完整指令会被识别。持续推理模式会在剩余2轮时提醒,到期自动切回快速问答模式。"
|
|
}
|
|
|
|
func (s *Service) Complete(ctx context.Context, input TurnCompletion) (Completion, error) {
|
|
if input.At.IsZero() {
|
|
input.At = s.now()
|
|
}
|
|
if input.Idle <= 0 {
|
|
input.Idle = s.idleTimeout
|
|
}
|
|
return s.repo.CompleteTurn(ctx, input)
|
|
}
|
|
|
|
func validateScope(scope Scope) error {
|
|
if strings.TrimSpace(scope.TenantKey) == "" || strings.TrimSpace(scope.Channel) == "" || strings.TrimSpace(scope.ExternalActorID) == "" {
|
|
return errors.New("tenant, channel and external actor are required")
|
|
}
|
|
return nil
|
|
}
|