转人工流程问题修复
This commit is contained in:
@@ -69,7 +69,8 @@ func (e *AgentLoopEngine) Run(ctx context.Context, req RunInput) (*RunResult, er
|
|||||||
turn := e.prepareTurn(ctx, req, snapshot)
|
turn := e.prepareTurn(ctx, req, snapshot)
|
||||||
var toolCalls []svc.AgentLoopToolCallInput
|
var toolCalls []svc.AgentLoopToolCallInput
|
||||||
state := agentLoopExecutionState{}
|
state := agentLoopExecutionState{}
|
||||||
loopResult, loopErr := e.loop(ctx, req.AIConfig, turn.SystemPrompt, turn.UserPrompt, agentLoopToolDefinitions(turn), req.AIAgent.MaxSteps,
|
definitions := append(agentLoopToolDefinitions(turn), agentLoopDecisionTool)
|
||||||
|
loopResult, loopErr := e.loop(ctx, req.AIConfig, turn.SystemPrompt, turn.UserPrompt, definitions, req.AIAgent.MaxSteps,
|
||||||
e.toolSearchExecutor(req, turn, &state, &toolCalls))
|
e.toolSearchExecutor(req, turn, &state, &toolCalls))
|
||||||
if state.Interrupted != nil {
|
if state.Interrupted != nil {
|
||||||
result := state.Interrupted
|
result := state.Interrupted
|
||||||
@@ -90,12 +91,18 @@ func (e *AgentLoopEngine) Run(ctx context.Context, req RunInput) (*RunResult, er
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
result := &loopResult.ChatCompletionResult
|
result := &loopResult.ChatCompletionResult
|
||||||
if strings.TrimSpace(result.Content) == "" {
|
replyText, handoffRequested, handoffReason, err := resolveAgentLoopReply(result.Content, state.Decision)
|
||||||
|
if err != nil {
|
||||||
|
_, _ = writeAgentLoopRun(req, startedAt, nil, turn.UserPrompt, turn.HistoryCount, turn.RetrieverCount, turn.RetrieveErr, state.SkillContext, turn.ResponsePolicy, toolCalls, err, false, state.WorkflowSteps)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result.Content = replyText
|
||||||
|
if result.Content == "" && !handoffRequested {
|
||||||
err = errorsx.InvalidParam("Agent Loop returned an empty reply")
|
err = errorsx.InvalidParam("Agent Loop returned an empty reply")
|
||||||
_, _ = writeAgentLoopRun(req, startedAt, nil, turn.UserPrompt, turn.HistoryCount, turn.RetrieverCount, turn.RetrieveErr, state.SkillContext, turn.ResponsePolicy, toolCalls, err, false, state.WorkflowSteps)
|
_, _ = writeAgentLoopRun(req, startedAt, nil, turn.UserPrompt, turn.HistoryCount, turn.RetrieverCount, turn.RetrieveErr, state.SkillContext, turn.ResponsePolicy, toolCalls, err, false, state.WorkflowSteps)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
result.Content, err = aitooling.NormalizeCustomerReply(result.Content)
|
result.Content, err = normalizeAgentLoopReply(result.Content, handoffRequested)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_, _ = writeAgentLoopRun(req, startedAt, nil, turn.UserPrompt, turn.HistoryCount, turn.RetrieverCount, turn.RetrieveErr, state.SkillContext, turn.ResponsePolicy, toolCalls, err, false, state.WorkflowSteps)
|
_, _ = writeAgentLoopRun(req, startedAt, nil, turn.UserPrompt, turn.HistoryCount, turn.RetrieverCount, turn.RetrieveErr, state.SkillContext, turn.ResponsePolicy, toolCalls, err, false, state.WorkflowSteps)
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -128,11 +135,34 @@ func (e *AgentLoopEngine) Run(ctx context.Context, req RunInput) (*RunResult, er
|
|||||||
InvokedToolCodes: agentLoopInvokedToolCodes(toolCalls),
|
InvokedToolCodes: agentLoopInvokedToolCodes(toolCalls),
|
||||||
WorkflowRunID: state.WorkflowRunID,
|
WorkflowRunID: state.WorkflowRunID,
|
||||||
AgentRunID: runID,
|
AgentRunID: runID,
|
||||||
HandoffRequested: turn.ResponsePolicy.RequestHandoff && !req.Debug,
|
HandoffRequested: handoffRequested && !req.Debug,
|
||||||
|
HandoffReason: handoffReason,
|
||||||
|
ConversationDecision: state.Decision,
|
||||||
TraceData: string(trace),
|
TraceData: string(trace),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func resolveAgentLoopReply(modelReply string, decision *ConversationDecision) (reply string, handoffRequested bool, handoffReason string, err error) {
|
||||||
|
if decision == nil {
|
||||||
|
return strings.TrimSpace(modelReply), false, "", nil
|
||||||
|
}
|
||||||
|
switch decision.Action {
|
||||||
|
case ConversationActionHandoff:
|
||||||
|
return "", true, decision.Reason, nil
|
||||||
|
case ConversationActionReply, ConversationActionAskHandoffConfirmation:
|
||||||
|
return strings.TrimSpace(decision.Reply), false, "", nil
|
||||||
|
default:
|
||||||
|
return "", false, "", fmt.Errorf("invalid conversation decision action: %s", decision.Action)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeAgentLoopReply(reply string, handoffRequested bool) (string, error) {
|
||||||
|
if handoffRequested {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
return aitooling.NormalizeCustomerReply(reply)
|
||||||
|
}
|
||||||
|
|
||||||
func (e *AgentLoopEngine) buildUserPrompt(req RunInput) (string, int) {
|
func (e *AgentLoopEngine) buildUserPrompt(req RunInput) (string, int) {
|
||||||
limit := req.AIAgent.ContextWindow
|
limit := req.AIAgent.ContextWindow
|
||||||
if limit <= 0 {
|
if limit <= 0 {
|
||||||
@@ -470,8 +500,8 @@ func parseAgentLoopToolPolicy(raw string) agentLoopToolPolicy {
|
|||||||
return policy
|
return policy
|
||||||
}
|
}
|
||||||
|
|
||||||
func agentLoopToolSearchDefinition() ai.ToolDefinition {
|
var (
|
||||||
return ai.ToolDefinition{
|
agentLoopToolSearchTool = ai.ToolDefinition{
|
||||||
Name: "tool_search",
|
Name: "tool_search",
|
||||||
Description: "Activate a configured Skill or execute a configured Workflow, builtin capability, or MCP tool. Pass the exact capability code and arguments.",
|
Description: "Activate a configured Skill or execute a configured Workflow, builtin capability, or MCP tool. Pass the exact capability code and arguments.",
|
||||||
Parameters: map[string]any{
|
Parameters: map[string]any{
|
||||||
@@ -483,7 +513,22 @@ func agentLoopToolSearchDefinition() ai.ToolDefinition {
|
|||||||
"required": []string{"toolCode", "arguments"},
|
"required": []string{"toolCode", "arguments"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
agentLoopDecisionTool = ai.ToolDefinition{
|
||||||
|
Name: "conversation_decision",
|
||||||
|
Description: "Return the final structured conversation decision after completing any needed analysis or tool calls.",
|
||||||
|
Parameters: map[string]any{
|
||||||
|
"type": "object",
|
||||||
|
"properties": map[string]any{
|
||||||
|
"action": map[string]any{"type": "string", "enum": []string{"reply", "handoff", "ask_handoff_confirmation"}},
|
||||||
|
"reason": map[string]any{"type": "string"},
|
||||||
|
"reply": map[string]any{"type": "string"},
|
||||||
|
"handoffInitiator": map[string]any{"type": "string", "enum": []string{"none", "customer", "agent"}},
|
||||||
|
"handoffConfirmed": map[string]any{"type": "boolean"},
|
||||||
|
},
|
||||||
|
"required": []string{"action", "reason", "reply", "handoffInitiator", "handoffConfirmed"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
// agentLoopToolDefinitions registers the capability codes as compatibility
|
// agentLoopToolDefinitions registers the capability codes as compatibility
|
||||||
// aliases in addition to tool_search. Some OpenAI-compatible providers invoke
|
// aliases in addition to tool_search. Some OpenAI-compatible providers invoke
|
||||||
@@ -492,7 +537,7 @@ func agentLoopToolSearchDefinition() ai.ToolDefinition {
|
|||||||
// those calls must be registered here and then routed through the same policy
|
// those calls must be registered here and then routed through the same policy
|
||||||
// boundary below.
|
// boundary below.
|
||||||
func agentLoopToolDefinitions(turn agentLoopTurn) []ai.ToolDefinition {
|
func agentLoopToolDefinitions(turn agentLoopTurn) []ai.ToolDefinition {
|
||||||
definitions := []ai.ToolDefinition{agentLoopToolSearchDefinition()}
|
definitions := []ai.ToolDefinition{agentLoopToolSearchTool}
|
||||||
seen := map[string]struct{}{"tool_search": {}}
|
seen := map[string]struct{}{"tool_search": {}}
|
||||||
for _, code := range turn.AllowedTools {
|
for _, code := range turn.AllowedTools {
|
||||||
code = strings.TrimSpace(code)
|
code = strings.TrimSpace(code)
|
||||||
@@ -552,6 +597,7 @@ type agentLoopExecutionState struct {
|
|||||||
WorkflowRunID int64
|
WorkflowRunID int64
|
||||||
WorkflowSteps []svc.AgentLoopStepInput
|
WorkflowSteps []svc.AgentLoopStepInput
|
||||||
Interrupted *RunResult
|
Interrupted *RunResult
|
||||||
|
Decision *ConversationDecision
|
||||||
}
|
}
|
||||||
|
|
||||||
type agentLoopInterruptError struct {
|
type agentLoopInterruptError struct {
|
||||||
@@ -565,6 +611,23 @@ func (e *agentLoopInterruptError) Error() string {
|
|||||||
func (e *AgentLoopEngine) toolSearchExecutor(runInput RunInput, turn agentLoopTurn, state *agentLoopExecutionState, records *[]svc.AgentLoopToolCallInput) ai.ToolCallExecutor {
|
func (e *AgentLoopEngine) toolSearchExecutor(runInput RunInput, turn agentLoopTurn, state *agentLoopExecutionState, records *[]svc.AgentLoopToolCallInput) ai.ToolCallExecutor {
|
||||||
return func(ctx context.Context, call ai.ToolCall) (string, error) {
|
return func(ctx context.Context, call ai.ToolCall) (string, error) {
|
||||||
startedAt := time.Now()
|
startedAt := time.Now()
|
||||||
|
if call.Name == "conversation_decision" {
|
||||||
|
decision, err := parseConversationDecision(call.Arguments)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
state.Decision = decision
|
||||||
|
result, _ := json.Marshal(decision)
|
||||||
|
*records = append(*records, svc.AgentLoopToolCallInput{
|
||||||
|
ToolCode: "conversation_decision",
|
||||||
|
RiskLevel: aitooling.RiskLevelRead,
|
||||||
|
Status: "completed",
|
||||||
|
ArgumentsPreview: aitooling.SanitizePreview(call.Arguments),
|
||||||
|
ResultPreview: aitooling.SanitizePreview(string(result)),
|
||||||
|
DurationMS: int(time.Since(startedAt).Milliseconds()),
|
||||||
|
})
|
||||||
|
return string(result), nil
|
||||||
|
}
|
||||||
toolCode, arguments, err := resolveAgentLoopToolCall(call)
|
toolCode, arguments, err := resolveAgentLoopToolCall(call)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@@ -629,6 +692,35 @@ func (e *AgentLoopEngine) toolSearchExecutor(runInput RunInput, turn agentLoopTu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseConversationDecision(raw string) (*ConversationDecision, error) {
|
||||||
|
decision := &ConversationDecision{}
|
||||||
|
if err := json.Unmarshal([]byte(raw), decision); err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid conversation decision: %w", err)
|
||||||
|
}
|
||||||
|
decision.Reason = strings.TrimSpace(decision.Reason)
|
||||||
|
decision.Reply = strings.TrimSpace(decision.Reply)
|
||||||
|
switch decision.Action {
|
||||||
|
case ConversationActionReply:
|
||||||
|
if decision.HandoffInitiator != HandoffInitiatorNone || decision.HandoffConfirmed {
|
||||||
|
return nil, fmt.Errorf("reply decision must not contain handoff state")
|
||||||
|
}
|
||||||
|
case ConversationActionHandoff:
|
||||||
|
if decision.HandoffInitiator == HandoffInitiatorNone || !decision.HandoffConfirmed {
|
||||||
|
return nil, fmt.Errorf("handoff decision requires a confirmed handoff initiator")
|
||||||
|
}
|
||||||
|
case ConversationActionAskHandoffConfirmation:
|
||||||
|
if decision.HandoffInitiator != HandoffInitiatorAgent || decision.HandoffConfirmed {
|
||||||
|
return nil, fmt.Errorf("handoff confirmation may only be requested for an unconfirmed agent recommendation")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("invalid conversation decision action: %s", decision.Action)
|
||||||
|
}
|
||||||
|
if decision.Action != ConversationActionHandoff && decision.Reply == "" {
|
||||||
|
return nil, fmt.Errorf("conversation decision reply is required")
|
||||||
|
}
|
||||||
|
return decision, nil
|
||||||
|
}
|
||||||
|
|
||||||
func resolveAgentLoopToolCall(call ai.ToolCall) (string, map[string]any, error) {
|
func resolveAgentLoopToolCall(call ai.ToolCall) (string, map[string]any, error) {
|
||||||
if call.Name == "tool_search" {
|
if call.Name == "tool_search" {
|
||||||
var request agentLoopToolSearchRequest
|
var request agentLoopToolSearchRequest
|
||||||
|
|||||||
@@ -69,6 +69,68 @@ func TestAgentLoopRegistersDirectCapabilityAliases(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestConversationDecisionIsStructuredAndValidated(t *testing.T) {
|
||||||
|
decision, err := parseConversationDecision(`{"action":"handoff","reason":"customer requested a human","reply":"","handoffInitiator":"customer","handoffConfirmed":true}`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parse handoff decision: %v", err)
|
||||||
|
}
|
||||||
|
if decision.Action != ConversationActionHandoff || decision.Reason != "customer requested a human" {
|
||||||
|
t.Fatalf("unexpected handoff decision: %#v", decision)
|
||||||
|
}
|
||||||
|
for _, raw := range []string{
|
||||||
|
`{"action":"unknown","reason":"x","reply":"x","handoffInitiator":"none","handoffConfirmed":false}`,
|
||||||
|
`{"action":"reply","reason":"x","reply":"","handoffInitiator":"none","handoffConfirmed":false}`,
|
||||||
|
`{"action":"ask_handoff_confirmation","reason":"x","reply":"confirm?","handoffInitiator":"customer","handoffConfirmed":false}`,
|
||||||
|
} {
|
||||||
|
if _, err := parseConversationDecision(raw); err == nil {
|
||||||
|
t.Fatalf("expected invalid decision to fail: %s", raw)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAgentLoopRecordsConversationDecision(t *testing.T) {
|
||||||
|
state := agentLoopExecutionState{}
|
||||||
|
var calls []svc.AgentLoopToolCallInput
|
||||||
|
execute := NewAgentLoopEngine().toolSearchExecutor(RunInput{}, agentLoopTurn{}, &state, &calls)
|
||||||
|
if _, err := execute(context.Background(), ai.ToolCall{
|
||||||
|
Name: "conversation_decision", Arguments: `{"action":"handoff","reason":"customer requested a human","reply":"","handoffInitiator":"customer","handoffConfirmed":true}`,
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatalf("record decision: %v", err)
|
||||||
|
}
|
||||||
|
if state.Decision == nil || state.Decision.Action != ConversationActionHandoff {
|
||||||
|
t.Fatalf("decision was not stored: %#v", state.Decision)
|
||||||
|
}
|
||||||
|
if len(calls) != 1 || calls[0].ToolCode != "conversation_decision" || calls[0].Status != "completed" {
|
||||||
|
t.Fatalf("decision audit missing: %#v", calls)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResolveAgentLoopReplyKeepsNormalModelReplyWithoutDecision(t *testing.T) {
|
||||||
|
reply, handoff, reason, err := resolveAgentLoopReply("你好,有什么可以帮你?", nil)
|
||||||
|
if err != nil || handoff || reason != "" || reply != "你好,有什么可以帮你?" {
|
||||||
|
t.Fatalf("unexpected normal reply resolution: reply=%q handoff=%t reason=%q err=%v", reply, handoff, reason, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResolveAgentLoopReplyUsesStructuredHandoffDecision(t *testing.T) {
|
||||||
|
reply, handoff, reason, err := resolveAgentLoopReply("模型自由文本不应生效", &ConversationDecision{
|
||||||
|
Action: ConversationActionHandoff, Reason: "customer requested human support", HandoffInitiator: HandoffInitiatorCustomer, HandoffConfirmed: true,
|
||||||
|
})
|
||||||
|
if err != nil || !handoff || reply != "" || reason != "customer requested human support" {
|
||||||
|
t.Fatalf("unexpected handoff resolution: reply=%q handoff=%t reason=%q err=%v", reply, handoff, reason, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNormalizeAgentLoopReplyAllowsEmptyInternalHandoff(t *testing.T) {
|
||||||
|
reply, err := normalizeAgentLoopReply("", true)
|
||||||
|
if err != nil || reply != "" {
|
||||||
|
t.Fatalf("handoff must bypass customer reply normalization: reply=%q err=%v", reply, err)
|
||||||
|
}
|
||||||
|
if _, err := normalizeAgentLoopReply("", false); err == nil {
|
||||||
|
t.Fatal("ordinary empty model replies must still be rejected")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestAgentLoopDirectCapabilityAliasUsesSamePolicyBoundary(t *testing.T) {
|
func TestAgentLoopDirectCapabilityAliasUsesSamePolicyBoundary(t *testing.T) {
|
||||||
skill := models.SkillDefinition{
|
skill := models.SkillDefinition{
|
||||||
ID: 7, Name: "售后升级处理", Instruction: "先确认升级诉求。", Status: enums.StatusOk,
|
ID: 7, Name: "售后升级处理", Instruction: "先确认升级诉求。", Status: enums.StatusOk,
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ func (e *AgentLoopEngine) prepareTurn(ctx context.Context, req RunInput, snapsho
|
|||||||
catalog = append(catalog, fmt.Sprintf("- %s | MCP | %s | %s", tool.ToolCode, tool.Title, tool.Description))
|
catalog = append(catalog, fmt.Sprintf("- %s | MCP | %s | %s", tool.ToolCode, tool.Title, tool.Description))
|
||||||
}
|
}
|
||||||
systemPrompt += "\n\nAvailable capabilities:\n" + strings.Join(catalog, "\n")
|
systemPrompt += "\n\nAvailable capabilities:\n" + strings.Join(catalog, "\n")
|
||||||
systemPrompt += "\n\nUse tool_search with an exact capability code only when needed. You decide whether to answer directly, activate a Skill, execute a Workflow, retrieve knowledge, or call MCP. A Skill activation returns instructions for this same run. Never invent a capability code."
|
systemPrompt += "\n\nUse tool_search with an exact capability code only when needed. You decide whether to answer directly, activate a Skill, execute a Workflow, retrieve knowledge, or call MCP. A Skill activation returns instructions for this same run. Never invent a capability code. For any requested internal action such as human handoff, call conversation_decision; its action is a structured proposal only, and the runtime performs the action. When the customer explicitly asks for human support, set action=handoff, handoffInitiator=customer, and handoffConfirmed=true; do not ask again. Use ask_handoff_confirmation only when you, not the customer, recommend an unconfirmed handoff, with handoffInitiator=agent and handoffConfirmed=false. Never claim a handoff, assignment, or queue entry succeeded in reply text."
|
||||||
return agentLoopTurn{
|
return agentLoopTurn{
|
||||||
RetrieverCount: retrieverCount,
|
RetrieverCount: retrieverCount,
|
||||||
RetrieveErr: retrieveErr,
|
RetrieveErr: retrieveErr,
|
||||||
|
|||||||
@@ -58,11 +58,39 @@ type RunResult struct {
|
|||||||
CheckPointData string
|
CheckPointData string
|
||||||
Interrupted bool
|
Interrupted bool
|
||||||
HandoffRequested bool
|
HandoffRequested bool
|
||||||
|
HandoffReason string
|
||||||
|
ConversationDecision *ConversationDecision
|
||||||
Interrupts []InterruptContextSummary
|
Interrupts []InterruptContextSummary
|
||||||
TraceData string
|
TraceData string
|
||||||
ErrorMessage string
|
ErrorMessage string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ConversationAction string
|
||||||
|
|
||||||
|
const (
|
||||||
|
ConversationActionReply ConversationAction = "reply"
|
||||||
|
ConversationActionHandoff ConversationAction = "handoff"
|
||||||
|
ConversationActionAskHandoffConfirmation ConversationAction = "ask_handoff_confirmation"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HandoffInitiator string
|
||||||
|
|
||||||
|
const (
|
||||||
|
HandoffInitiatorNone HandoffInitiator = "none"
|
||||||
|
HandoffInitiatorCustomer HandoffInitiator = "customer"
|
||||||
|
HandoffInitiatorAgent HandoffInitiator = "agent"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ConversationDecision is the model's structured, non-side-effect decision.
|
||||||
|
// The runtime remains the only component that can execute a handoff.
|
||||||
|
type ConversationDecision struct {
|
||||||
|
Action ConversationAction `json:"action"`
|
||||||
|
Reason string `json:"reason"`
|
||||||
|
Reply string `json:"reply"`
|
||||||
|
HandoffInitiator HandoffInitiator `json:"handoffInitiator"`
|
||||||
|
HandoffConfirmed bool `json:"handoffConfirmed"`
|
||||||
|
}
|
||||||
|
|
||||||
type StreamEventType string
|
type StreamEventType string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -89,11 +89,12 @@ func (s *aiReplyService) executeReply(ctx context.Context, replyCtx aiReplyConte
|
|||||||
if _, err := svc.ConversationHumanDispatchService.HandoffByAIWithRequestID(
|
if _, err := svc.ConversationHumanDispatchService.HandoffByAIWithRequestID(
|
||||||
replyCtx.Conversation.ID,
|
replyCtx.Conversation.ID,
|
||||||
replyCtx.AIAgent,
|
replyCtx.AIAgent,
|
||||||
"knowledge evidence unavailable",
|
summary.HandoffReason,
|
||||||
replyCtx.Message.RequestID,
|
replyCtx.Message.RequestID,
|
||||||
); err == nil {
|
); err != nil {
|
||||||
return nil
|
return err
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
if summary != nil && strings.TrimSpace(summary.ReplyText) != "" {
|
if summary != nil && strings.TrimSpace(summary.ReplyText) != "" {
|
||||||
_, err := s.commit.CommitAIReply(replyCommitInput{
|
_, err := s.commit.CommitAIReply(replyCommitInput{
|
||||||
|
|||||||
Reference in New Issue
Block a user