mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-06-06 06:13:58 +02:00
Add files via upload
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
package multiagent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/cloudwego/eino/compose"
|
||||
)
|
||||
|
||||
// softRecoveryToolCallMiddleware returns an InvokableToolMiddleware that catches
|
||||
// specific recoverable errors from tool execution (JSON parse errors, tool-not-found,
|
||||
// etc.) and converts them into soft errors: nil error + descriptive error content
|
||||
// returned to the LLM. This allows the model to self-correct within the same
|
||||
// iteration rather than crashing the entire graph and requiring a full replay.
|
||||
//
|
||||
// Without this middleware, a JSON parse failure in any tool's InvokableRun propagates
|
||||
// as a hard error through the Eino ToolsNode → [NodeRunError] → ev.Err, which
|
||||
// either triggers the full-replay retry loop (expensive) or terminates the run
|
||||
// entirely once retries are exhausted. With it, the LLM simply sees an error message
|
||||
// in the tool result and can adjust its next tool call accordingly.
|
||||
func softRecoveryToolCallMiddleware() compose.InvokableToolMiddleware {
|
||||
return func(next compose.InvokableToolEndpoint) compose.InvokableToolEndpoint {
|
||||
return func(ctx context.Context, input *compose.ToolInput) (*compose.ToolOutput, error) {
|
||||
output, err := next(ctx, input)
|
||||
if err == nil {
|
||||
return output, nil
|
||||
}
|
||||
if !isSoftRecoverableToolError(err) {
|
||||
return output, err
|
||||
}
|
||||
// Convert the hard error into a soft error: the LLM will see this
|
||||
// message as the tool's output and can self-correct.
|
||||
msg := buildSoftRecoveryMessage(input.Name, input.Arguments, err)
|
||||
return &compose.ToolOutput{Result: msg}, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// isSoftRecoverableToolError determines whether a tool execution error should be
|
||||
// silently converted to a tool-result message rather than crashing the graph.
|
||||
func isSoftRecoverableToolError(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
s := strings.ToLower(err.Error())
|
||||
|
||||
// JSON unmarshal/parse failures — the model generated truncated or malformed arguments.
|
||||
if isJSONRelatedError(s) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Sub-agent type not found (from deep/task_tool.go)
|
||||
if strings.Contains(s, "subagent type") && strings.Contains(s, "not found") {
|
||||
return true
|
||||
}
|
||||
|
||||
// Tool not found in ToolsNode indexes
|
||||
if strings.Contains(s, "tool") && strings.Contains(s, "not found") {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// isJSONRelatedError checks whether an error string indicates a JSON parsing problem.
|
||||
func isJSONRelatedError(lower string) bool {
|
||||
if !strings.Contains(lower, "json") {
|
||||
return false
|
||||
}
|
||||
jsonIndicators := []string{
|
||||
"unexpected end of json",
|
||||
"unmarshal",
|
||||
"invalid character",
|
||||
"cannot unmarshal",
|
||||
"invalid tool arguments",
|
||||
"failed to unmarshal",
|
||||
"must be in json format",
|
||||
"unexpected eof",
|
||||
}
|
||||
for _, ind := range jsonIndicators {
|
||||
if strings.Contains(lower, ind) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// buildSoftRecoveryMessage creates a bilingual error message that the LLM can act on.
|
||||
func buildSoftRecoveryMessage(toolName, arguments string, err error) string {
|
||||
// Truncate arguments preview to avoid flooding the context.
|
||||
argPreview := arguments
|
||||
if len(argPreview) > 300 {
|
||||
argPreview = argPreview[:300] + "... (truncated)"
|
||||
}
|
||||
|
||||
// Try to determine if it's specifically a JSON parse error for a friendlier message.
|
||||
errStr := err.Error()
|
||||
var jsonErr *json.SyntaxError
|
||||
isJSONErr := strings.Contains(strings.ToLower(errStr), "json") ||
|
||||
strings.Contains(strings.ToLower(errStr), "unmarshal")
|
||||
_ = jsonErr // suppress unused
|
||||
|
||||
if isJSONErr {
|
||||
return fmt.Sprintf(
|
||||
"[Tool Error] The arguments for tool '%s' are not valid JSON and could not be parsed.\n"+
|
||||
"Error: %s\n"+
|
||||
"Arguments received: %s\n\n"+
|
||||
"Please fix the JSON (ensure double-quoted keys, matched braces/brackets, no trailing commas, "+
|
||||
"no truncation) and call the tool again.\n\n"+
|
||||
"[工具错误] 工具 '%s' 的参数不是合法 JSON,无法解析。\n"+
|
||||
"错误:%s\n"+
|
||||
"收到的参数:%s\n\n"+
|
||||
"请修正 JSON(确保双引号键名、括号配对、无尾部逗号、无截断),然后重新调用工具。",
|
||||
toolName, errStr, argPreview,
|
||||
toolName, errStr, argPreview,
|
||||
)
|
||||
}
|
||||
|
||||
return fmt.Sprintf(
|
||||
"[Tool Error] Tool '%s' execution failed: %s\n"+
|
||||
"Arguments: %s\n\n"+
|
||||
"Please review the available tools and their expected arguments, then retry.\n\n"+
|
||||
"[工具错误] 工具 '%s' 执行失败:%s\n"+
|
||||
"参数:%s\n\n"+
|
||||
"请检查可用工具及其参数要求,然后重试。",
|
||||
toolName, errStr, argPreview,
|
||||
toolName, errStr, argPreview,
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user