{"record":{"id":"5c49814da7ec29ff","repo":"vxcontrol/pentagi","slug":"failed-to-unmarshal-search-arguments-w","errorCode":null,"errorMessage":"failed to unmarshal search arguments: %w","messagePattern":"failed to unmarshal search arguments: %w","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/pkg/tools/graphiti_search.go","lineNumber":160,"sourceCode":"func (t *graphitiSearchTool) IsAvailable() bool {\n\treturn t.graphitiClient != nil && t.graphitiClient.IsEnabled()\n}\n\n// Handle executes the search based on search_type\nfunc (t *graphitiSearchTool) Handle(ctx context.Context, name string, args json.RawMessage) (string, error) {\n\tif !t.IsAvailable() {\n\t\treturn \"Graphiti knowledge graph is not enabled. No historical context or memory data is available for this search.\", nil\n\t}\n\n\tlogger := logrus.WithContext(ctx).WithFields(enrichLogrusFields(t.flowID, t.taskID, t.subtaskID, logrus.Fields{\n\t\t\"tool\": name,\n\t\t\"args\": string(args),\n\t}))\n\n\tvar searchArgs GraphitiSearchAction\n\tif err := json.Unmarshal(args, &searchArgs); err != nil {\n\t\tlogger.WithError(err).Error(\"failed to unmarshal search arguments\")\n\t\treturn \"\", fmt.Errorf(\"failed to unmarshal search arguments: %w\", err)\n\t}\n\n\tsearchArgs.Query = strings.TrimSpace(searchArgs.Query)\n\n\tif searchArgs.Query == \"\" {\n\t\tlogger.Error(\"query parameter is required\")\n\t\treturn \"\", fmt.Errorf(\"query parameter is required\")\n\t}\n\tif searchArgs.SearchType == \"\" {\n\t\tlogger.Error(\"search_type parameter is required\")\n\t\treturn \"\", fmt.Errorf(\"search_type parameter is required\")\n\t}\n\n\tctx, observation := obs.Observer.NewObservation(ctx)\n\n\tretrieverTitle, ok := graphitiRetrieverTitles[searchArgs.SearchType.String()]\n\tif !ok {\n\t\tretrieverTitle = \"retrieve context from graphiti knowledge graph\"","sourceCodeStart":142,"sourceCodeEnd":178,"githubUrl":"https://github.com/vxcontrol/pentagi/blob/ea665308baaff015b226f308438a68d929d0f29b/backend/pkg/tools/graphiti_search.go#L142-L178","documentation":"The graphiti_search tool receives its arguments as a raw JSON byte slice from the LLM and unmarshals them into GraphitiSearchAction. If the payload is not valid JSON for that struct (malformed JSON, wrong types such as a string where an integer array is expected, or a truncated payload), the handler logs the error and returns \"failed to unmarshal search arguments: %w\". The tool never executes against partially parsed arguments.","triggerScenarios":"The model emits graphiti_search arguments that are not valid JSON: unquoted keys, trailing commas, single quotes, a JSON-encoded string instead of an object (double-encoded), max_results/max_depth/min_mentions given as strings (\"5\" instead of 5), or node_labels/edge_types given as a string instead of an array.","commonSituations":"Weak LLM providers that emit sloppy JSON; prompt frameworks that double-serialize arguments; a provider streaming a truncated tool-call payload; custom OpenAI-compatible endpoints that mangle tool_call.function.arguments.","solutions":["Inspect the wrapped json.Unmarshal cause and the logged raw \"args\" payload to find the exact syntax/type mistake.","Re-issue the tool call as a proper JSON object matching GraphitiSearchAction (search_type, query, message as strings; max_results, max_depth, min_mentions as numbers; node_labels, edge_types as arrays).","If the provider double-encodes arguments (a quoted JSON string), fix the client/adapter so arguments are passed as an object, not a string.","Ensure search_type uses one of the enum values (temporal_window, entity_relationships, diverse_results, episode_context, successful_tools, recent_context, entity_by_label)."],"exampleFix":"// before (invalid: numbers as strings, array as string)\n{\"search_type\": \"entity_by_label\", \"query\": \"find hosts\", \"max_results\": \"5\", \"node_labels\": \"Host\"}\n// after\n{\"search_type\": \"entity_by_label\", \"query\": \"find hosts\", \"max_results\": 5, \"node_labels\": [\"Host\"]}","handlingStrategy":"validation","validationCode":"func validGraphitiArgs(raw string) error {\n    var probe map[string]any\n    if err := json.Unmarshal([]byte(raw), &probe); err != nil {\n        return err // not valid JSON at all\n    }\n    for _, intField := range []string{\"max_results\", \"max_depth\", \"min_mentions\"} {\n        if v, ok := probe[intField]; ok {\n            if _, isNum := v.(float64); !isNum {\n                return fmt.Errorf(\"%s must be a number\", intField)\n            }\n        }\n    }\n    for _, arrField := range []string{\"node_labels\", \"edge_types\"} {\n        if v, ok := probe[arrField]; ok {\n            if _, isArr := v.([]any); !isArr {\n                return fmt.Errorf(\"%s must be an array\", arrField)\n            }\n        }\n    }\n    return nil\n}","typeGuard":"func isGraphitiSearchAction(v any) (*GraphitiSearchAction, bool) {\n    a, ok := v.(GraphitiSearchAction)\n    return &a, ok\n}","tryCatchPattern":"if _, err := graphitiSearchHandle(ctx, args); err != nil {\n    if strings.HasPrefix(err.Error(), \"failed to unmarshal search arguments:\") {\n        // log raw args, fix JSON syntax/types, re-issue the tool call\n    }\n}","preventionTips":["Emit tool arguments as a single JSON object — never a JSON-encoded string inside a string.","Use numeric literals for max_results/max_depth/min_mentions and arrays for node_labels/edge_types.","Test providers with a strict structured-output/JSON mode before production use.","Log the raw args (the handler already does) and diff against the schema when debugging."],"tags":["json","unmarshal","go","llm","tool-arguments"],"backgroundTag":"json-unmarshal-failed","analyzedSha":"ea665308baaff015b226f308438a68d929d0f29b","analyzedAt":"2026-09-01T14:16:31.421Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}