{"record":{"id":"249104e637225e52","repo":"HKUDS/Vibe-Trading","slug":"legs-must-be-a-non-empty-array","errorCode":null,"errorMessage":"legs must be a non-empty array","messagePattern":"legs must be a non-empty array","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"agent/src/tools/options_payoff_tool.py","lineNumber":231,"sourceCode":"            },\n            \"scenario_grid\": {\n                \"iv_values\": _rounded_array(iv_values),\n                \"spot\": _rounded_array(report.spot_grid),\n                \"pnl\": [_rounded_array(row) for row in np.asarray(scenarios, dtype=float)],\n            },\n            \"limitations\": [\n                \"European Black-Scholes marks with constant rate and volatility per scenario.\",\n                \"No dividends, early exercise, assignment, slippage, or margin model.\",\n                \"Scenario P&L is mark-to-market and does not deduct a hypothetical exit commission.\",\n            ],\n        }\n        return json.dumps(payload, ensure_ascii=False, allow_nan=False)\n\n\ndef _coerce_legs(raw: Any) -> list[OptionLeg]:\n    \"\"\"Parse and validate raw JSON-style leg objects.\"\"\"\n    if not isinstance(raw, list) or not raw:\n        raise ValueError(\"legs must be a non-empty array\")\n    if len(raw) > _MAX_LEGS:\n        raise ValueError(f\"legs may contain at most {_MAX_LEGS} entries\")\n\n    legs: list[OptionLeg] = []\n    for index, item in enumerate(raw):\n        if not isinstance(item, dict):\n            raise ValueError(f\"legs[{index}] must be an object\")\n        option_type = str(item.get(\"option_type\") or \"\").strip().lower()\n        try:\n            strike = float(item[\"strike\"])\n            raw_qty = item[\"qty\"]\n            qty_number = float(raw_qty)\n        except (KeyError, TypeError, ValueError, OverflowError) as exc:\n            raise ValueError(f\"legs[{index}] has invalid strike or qty: {exc}\") from exc\n        if isinstance(raw_qty, bool) or not qty_number.is_integer():\n            raise ValueError(f\"legs[{index}].qty must be a non-zero integer\")\n        qty = int(qty_number)\n        raw_premium = item.get(\"premium\")","sourceCodeStart":213,"sourceCodeEnd":249,"githubUrl":"https://github.com/HKUDS/Vibe-Trading/blob/80ffdda44c5c4db0dd84d70e051cca591cea67df/agent/src/tools/options_payoff_tool.py#L213-L249","documentation":"Thrown by _coerce_legs when the `legs` argument to the options payoff tool is not a JSON array or is an empty array. Legs define the option positions for payoff/greeks computation, so at least one leg is structurally required. This is the first schema gate before per-leg validation runs.","triggerScenarios":"Calling execute or _portfolio_greeks with legs=None, legs=\"...\", legs={} (a dict instead of list), or legs=[]. Often happens when the LLM/caller passes a JSON string instead of a parsed array, or omits legs entirely.","commonSituations":"Agent tool invocations where JSON arguments arrive as strings; clients building legs from user input that can be empty; passing an object keyed by leg index instead of an array.","solutions":["Pass legs as a parsed JSON array with at least one leg object, e.g. [{\"option_type\":\"call\",\"strike\":100,\"qty\":1}]","If legs arrives as a JSON string, json.loads it before calling the tool","Reject empty portfolios upstream in the calling agent's prompt/schema"],"exampleFix":"// before\nresult = execute({\"legs\": [], \"entry_spot\": 100})\n// after\nresult = execute({\"legs\": [{\"option_type\": \"call\", \"strike\": 100, \"qty\": 1, \"premium\": 2.5}], \"entry_spot\": 100})","handlingStrategy":"validation","validationCode":"import json\nif isinstance(legs, str):\n    legs = json.loads(legs)\nif not isinstance(legs, list) or not legs:\n    raise ValueError(\"legs must be a non-empty array of leg objects\")","typeGuard":"def is_legs_input(raw) -> bool:\n    return isinstance(raw, list) and len(raw) > 0 and all(isinstance(x, dict) for x in raw)","tryCatchPattern":"try:\n    result = tool.execute(kwargs)\nexcept ValueError as e:\n    return {\"error\": str(e)}  # surface message to caller/LLM for self-correction","preventionTips":["Define a strict JSON schema (type: array, minItems: 1) in the tool description","Parse JSON strings before forwarding","Reject empty portfolios at the UI/agent layer"],"tags":["validation","options","json","python"],"backgroundTag":"schema-validation-failed","analyzedSha":"80ffdda44c5c4db0dd84d70e051cca591cea67df","analyzedAt":"2026-08-28T12:46:38.989Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}