fix(platform): validate options/keyword params and pass sessionId to ClarificationQuestionsCard

- Validate options is actually a list (LLM may send string); coerce gracefully
- Validate keyword is a string
- Pass sessionId prop to ClarificationQuestionsCard for localStorage persistence
- Add test for invalid options coercion
This commit is contained in:
Zamil Majdy
2026-04-02 14:34:58 +02:00
parent 078e89f8a6
commit 988edd6fe9
3 changed files with 24 additions and 2 deletions

View File

@@ -72,8 +72,12 @@ class AskQuestionTool(BaseTool):
if not isinstance(question_raw, str) or not question_raw.strip():
raise ValueError("ask_question requires a non-empty 'question' string")
question = question_raw.strip()
options: list[str] = kwargs.get("options", [])
keyword: str = kwargs.get("keyword", "")
raw_options = kwargs.get("options", [])
if not isinstance(raw_options, list):
raw_options = []
options: list[str] = [str(o) for o in raw_options if o]
raw_keyword = kwargs.get("keyword", "")
keyword: str = str(raw_keyword) if raw_keyword else ""
session_id = session.session_id if session else None
example = ", ".join(options) if options else None

View File

@@ -80,3 +80,20 @@ async def test_execute_rejects_empty_question(
with pytest.raises(ValueError, match="non-empty"):
await tool._execute(user_id=None, session=session, question=" ")
@pytest.mark.asyncio
async def test_execute_coerces_invalid_options(
tool: AskQuestionTool, session: ChatSession
):
"""LLM may send options as a string instead of a list; should not crash."""
result = await tool._execute(
user_id=None,
session=session,
question="Pick one",
options="not-a-list", # type: ignore[arg-type]
)
assert isinstance(result, ClarificationNeededResponse)
q = result.questions[0]
assert q.example is None

View File

@@ -44,6 +44,7 @@ export function AskQuestionTool({ part }: Props) {
<ClarificationQuestionsCard
questions={normalizeClarifyingQuestions(output.questions ?? [])}
message={output.message}
sessionId={output.session_id}
onSubmitAnswers={handleAnswers}
/>
);