34051a4631
- Updated labels in the AI Agents dashboard for clarity, changing "流程状态" to "Playbook 状态" and "未发布流程" to "未发布 Playbook". - Introduced AI Agent rollout percentage management in channel editing, allowing users to set and rollback rollout percentages. - Added new API endpoints for rolling back AI Agent rollout and fetching agent run metrics. - Implemented new UI components for displaying agent run details, including status, duration, and input/output tokens. - Enhanced type definitions for AdminChannel and AIAgent to include rollout percentages and runtime modes. - Updated navigation to include a section for agent runs. - Added new translations for agent run features in both English and Chinese.
103 lines
3.9 KiB
Go
103 lines
3.9 KiB
Go
package einoexperiment
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
|
|
applicationruntime "agent-desk/internal/ai/application/runtime"
|
|
"agent-desk/internal/ai/runtime/graphs"
|
|
)
|
|
|
|
const confirmationInterruptType = "human_confirm"
|
|
|
|
// ConfirmationRequest represents a high-risk Eino tool action that must pause
|
|
// at AgentDesk's existing conversation-interrupt boundary.
|
|
type ConfirmationRequest struct {
|
|
InterruptID string
|
|
ToolCode string
|
|
Prompt string
|
|
Arguments map[string]any
|
|
}
|
|
|
|
type confirmationCheckpoint struct {
|
|
Version int `json:"version"`
|
|
Engine string `json:"engine"`
|
|
InterruptID string `json:"interruptId"`
|
|
ToolCode string `json:"toolCode"`
|
|
Arguments map[string]any `json:"arguments"`
|
|
}
|
|
|
|
// BuildConfirmationResult returns the generic interrupted result consumed by
|
|
// replyInterruptService. That service persists ConversationInterrupt from the
|
|
// result, so this package remains independent of database writes.
|
|
func BuildConfirmationResult(input applicationruntime.RunInput, request ConfirmationRequest) (*applicationruntime.RunResult, error) {
|
|
interruptID := strings.TrimSpace(request.InterruptID)
|
|
if interruptID == "" {
|
|
interruptID = "eino_confirm"
|
|
}
|
|
toolCode := strings.TrimSpace(request.ToolCode)
|
|
if toolCode == "" {
|
|
return nil, fmt.Errorf("confirmation tool code is required")
|
|
}
|
|
prompt := strings.TrimSpace(request.Prompt)
|
|
if prompt == "" {
|
|
return nil, fmt.Errorf("confirmation prompt is required")
|
|
}
|
|
checkpointData, err := json.Marshal(confirmationCheckpoint{
|
|
Version: 1, Engine: "eino", InterruptID: interruptID, ToolCode: toolCode, Arguments: cloneConfirmationArguments(request.Arguments),
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("encode Eino confirmation checkpoint: %w", err)
|
|
}
|
|
return &applicationruntime.RunResult{
|
|
Status: "interrupted",
|
|
Interrupted: true,
|
|
CheckPointID: confirmationCheckpointID(input, interruptID, toolCode, checkpointData),
|
|
CheckPointData: string(checkpointData),
|
|
Interrupts: []applicationruntime.InterruptContextSummary{{
|
|
Type: confirmationInterruptType, ID: interruptID, InfoPreview: string(mustMarshalConfirmationPrompt(prompt)),
|
|
}},
|
|
}, nil
|
|
}
|
|
|
|
// ResumeConfirmation reads the generic ResumeInput populated by the existing
|
|
// AgentApplicationService and validates that it belongs to this checkpoint.
|
|
func ResumeConfirmation(checkPointData string, input applicationruntime.ResumeInput) (string, confirmationCheckpoint, error) {
|
|
checkpoint := confirmationCheckpoint{}
|
|
if err := json.Unmarshal([]byte(strings.TrimSpace(checkPointData)), &checkpoint); err != nil {
|
|
return "", checkpoint, fmt.Errorf("decode Eino confirmation checkpoint: %w", err)
|
|
}
|
|
if checkpoint.Version != 1 || checkpoint.Engine != "eino" || strings.TrimSpace(checkpoint.InterruptID) == "" || strings.TrimSpace(checkpoint.ToolCode) == "" {
|
|
return "", checkpoint, fmt.Errorf("invalid Eino confirmation checkpoint")
|
|
}
|
|
decision := graphs.ParseConfirmationDecision(strings.TrimSpace(input.ResumeData[checkpoint.InterruptID]))
|
|
if decision == "" {
|
|
return "", checkpoint, fmt.Errorf("Eino confirmation decision is required")
|
|
}
|
|
return string(decision), checkpoint, nil
|
|
}
|
|
|
|
func confirmationCheckpointID(input applicationruntime.RunInput, interruptID, toolCode string, data []byte) string {
|
|
digest := sha256.Sum256(append([]byte(strings.TrimSpace(toolCode)+":"+strings.TrimSpace(interruptID)+":"), data...))
|
|
return fmt.Sprintf("eino:%d:%d:%s", input.Conversation.ID, input.UserMessage.ID, hex.EncodeToString(digest[:8]))
|
|
}
|
|
|
|
func cloneConfirmationArguments(input map[string]any) map[string]any {
|
|
if len(input) == 0 {
|
|
return map[string]any{}
|
|
}
|
|
ret := make(map[string]any, len(input))
|
|
for key, value := range input {
|
|
ret[key] = value
|
|
}
|
|
return ret
|
|
}
|
|
|
|
func mustMarshalConfirmationPrompt(prompt string) []byte {
|
|
data, _ := json.Marshal(map[string]string{"message": prompt})
|
|
return data
|
|
}
|