71
ai-web-agent-console/ai_web_agent_console/scenarios.py
Normal file
71
ai-web-agent-console/ai_web_agent_console/scenarios.py
Normal file
@@ -0,0 +1,71 @@
|
||||
"""File-backed demo scenarios used by the PoC4 Streamlit screen.
|
||||
|
||||
Scenario content is deliberately configuration, not executable routing policy.
|
||||
Changing this JSON changes only the menu shown to a demo user.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any, Mapping
|
||||
|
||||
|
||||
class ScenarioConfigError(RuntimeError):
|
||||
"""A safe, user-facing error for invalid scenario configuration."""
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class DemoScenario:
|
||||
scenario_id: str
|
||||
category: str
|
||||
title: str
|
||||
question: str
|
||||
|
||||
# Compatibility aliases keep the Streamlit rendering independent from the
|
||||
# storage field names and make a future scenario source interchangeable.
|
||||
@property
|
||||
def question_id(self) -> str:
|
||||
return self.scenario_id
|
||||
|
||||
@property
|
||||
def text(self) -> str:
|
||||
return self.question
|
||||
|
||||
|
||||
def load_demo_scenarios(path: Path) -> tuple[DemoScenario, ...]:
|
||||
"""Read enabled scenarios and reject malformed or duplicated entries."""
|
||||
|
||||
try:
|
||||
payload: Any = json.loads(path.read_text(encoding="utf-8"))
|
||||
except (OSError, UnicodeError, ValueError):
|
||||
raise ScenarioConfigError(f"질문 시나리오 설정을 읽지 못했습니다: {path}") from None
|
||||
|
||||
raw_scenarios = payload.get("scenarios") if isinstance(payload, Mapping) else None
|
||||
if not isinstance(raw_scenarios, list):
|
||||
raise ScenarioConfigError("질문 시나리오 설정에 scenarios 배열이 필요합니다.")
|
||||
|
||||
scenarios: list[DemoScenario] = []
|
||||
seen_ids: set[str] = set()
|
||||
for raw in raw_scenarios:
|
||||
if not isinstance(raw, Mapping) or raw.get("enabled", True) is not True:
|
||||
continue
|
||||
scenario_id = str(raw.get("id") or "").strip().upper()
|
||||
category = str(raw.get("category") or "일반").strip()
|
||||
title = str(raw.get("title") or "").strip()
|
||||
question = str(raw.get("question") or "").strip()
|
||||
if not scenario_id or not title or not question:
|
||||
raise ScenarioConfigError("각 질문 시나리오에는 id, title, question이 필요합니다.")
|
||||
if scenario_id in seen_ids:
|
||||
raise ScenarioConfigError(f"중복된 질문 시나리오 ID입니다: {scenario_id}")
|
||||
scenarios.append(
|
||||
DemoScenario(
|
||||
scenario_id=scenario_id,
|
||||
category=category,
|
||||
title=title,
|
||||
question=question,
|
||||
)
|
||||
)
|
||||
seen_ids.add(scenario_id)
|
||||
return tuple(scenarios)
|
||||
Reference in New Issue
Block a user