Refactor: replace surveyor knobs with attributes list
Browse files- backend/api/conversation_routes.py +4 -4
- backend/api/conversation_service.py +13 -13
- backend/api/conversation_ws.py +4 -4
- backend/core/conversation_manager.py +6 -6
- backend/core/surveyor_knobs.py +16 -37
- docs/README.md +1 -1
- docs/development.md +1 -1
- docs/persona-knobs.md +40 -82
- frontend/pages/config_view.py +163 -201
- frontend/pages/main_page.py +11 -2
- frontend/react_gradio_hybrid.py +2 -2
backend/api/conversation_routes.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
| 2 |
|
| 3 |
from __future__ import annotations
|
| 4 |
|
| 5 |
-
from typing import Dict, Optional, Any
|
| 6 |
|
| 7 |
from fastapi import APIRouter, HTTPException
|
| 8 |
from pydantic import BaseModel, Field
|
|
@@ -26,9 +26,9 @@ class StartConversationRequest(BaseModel):
|
|
| 26 |
default=None,
|
| 27 |
description="Extra instructions appended to the patient system prompt for this run",
|
| 28 |
)
|
| 29 |
-
|
| 30 |
default=None,
|
| 31 |
-
description="
|
| 32 |
)
|
| 33 |
surveyor_question_bank: Optional[str] = Field(
|
| 34 |
default=None,
|
|
@@ -57,7 +57,7 @@ async def start_conversation(request: StartConversationRequest) -> Dict[str, str
|
|
| 57 |
model=request.model,
|
| 58 |
surveyor_prompt_addition=request.surveyor_prompt_addition,
|
| 59 |
patient_prompt_addition=request.patient_prompt_addition,
|
| 60 |
-
|
| 61 |
surveyor_question_bank=request.surveyor_question_bank,
|
| 62 |
)
|
| 63 |
if success:
|
|
|
|
| 2 |
|
| 3 |
from __future__ import annotations
|
| 4 |
|
| 5 |
+
from typing import Dict, Optional, Any, List
|
| 6 |
|
| 7 |
from fastapi import APIRouter, HTTPException
|
| 8 |
from pydantic import BaseModel, Field
|
|
|
|
| 26 |
default=None,
|
| 27 |
description="Extra instructions appended to the patient system prompt for this run",
|
| 28 |
)
|
| 29 |
+
surveyor_attributes: Optional[List[str]] = Field(
|
| 30 |
default=None,
|
| 31 |
+
description="Plain-language surveyor attributes (bullet lines) compiled into the surveyor system prompt",
|
| 32 |
)
|
| 33 |
surveyor_question_bank: Optional[str] = Field(
|
| 34 |
default=None,
|
|
|
|
| 57 |
model=request.model,
|
| 58 |
surveyor_prompt_addition=request.surveyor_prompt_addition,
|
| 59 |
patient_prompt_addition=request.patient_prompt_addition,
|
| 60 |
+
surveyor_attributes=request.surveyor_attributes,
|
| 61 |
surveyor_question_bank=request.surveyor_question_bank,
|
| 62 |
)
|
| 63 |
if success:
|
backend/api/conversation_service.py
CHANGED
|
@@ -42,7 +42,7 @@ from backend.core.persona_system import PersonaSystem # noqa: E402
|
|
| 42 |
from .conversation_ws import ConnectionManager # noqa: E402
|
| 43 |
from .storage_service import get_run_store # noqa: E402
|
| 44 |
from backend.storage import RunRecord # noqa: E402
|
| 45 |
-
from backend.core.surveyor_knobs import
|
| 46 |
|
| 47 |
# Setup logging
|
| 48 |
logger = logging.getLogger(__name__)
|
|
@@ -272,7 +272,7 @@ class ConversationInfo:
|
|
| 272 |
stop_requested: bool = False
|
| 273 |
surveyor_prompt_addition: Optional[str] = None
|
| 274 |
patient_prompt_addition: Optional[str] = None
|
| 275 |
-
|
| 276 |
surveyor_question_bank: Optional[str] = None
|
| 277 |
|
| 278 |
|
|
@@ -291,7 +291,7 @@ class HumanChatInfo:
|
|
| 291 |
stop_requested: bool = False
|
| 292 |
surveyor_prompt_addition: Optional[str] = None
|
| 293 |
patient_prompt_addition: Optional[str] = None
|
| 294 |
-
|
| 295 |
surveyor_question_bank: Optional[str] = None
|
| 296 |
asked_question_ids: List[str] = field(default_factory=list)
|
| 297 |
lock: asyncio.Lock = field(default_factory=asyncio.Lock)
|
|
@@ -334,7 +334,7 @@ class ConversationService:
|
|
| 334 |
model: Optional[str] = None,
|
| 335 |
surveyor_prompt_addition: Optional[str] = None,
|
| 336 |
patient_prompt_addition: Optional[str] = None,
|
| 337 |
-
|
| 338 |
surveyor_question_bank: Optional[str] = None,
|
| 339 |
) -> bool:
|
| 340 |
"""Start a new human-to-surveyor chat session."""
|
|
@@ -361,7 +361,7 @@ class ConversationService:
|
|
| 361 |
llm_backend=resolved_backend,
|
| 362 |
surveyor_prompt_addition=surveyor_prompt_addition,
|
| 363 |
patient_prompt_addition=patient_prompt_addition,
|
| 364 |
-
|
| 365 |
surveyor_question_bank=surveyor_question_bank if isinstance(surveyor_question_bank, str) and surveyor_question_bank.strip() else None,
|
| 366 |
status=ConversationStatus.STARTING,
|
| 367 |
created_at=datetime.now(),
|
|
@@ -511,14 +511,14 @@ class ConversationService:
|
|
| 511 |
user_prompt=user_prompt,
|
| 512 |
)
|
| 513 |
|
| 514 |
-
overlay = compile_surveyor_overlay(chat_info.surveyor_knobs)
|
| 515 |
-
if overlay:
|
| 516 |
-
system_prompt = (system_prompt + "\n\n" + overlay).strip()
|
| 517 |
-
|
| 518 |
qb = compile_question_bank_overlay(chat_info.surveyor_question_bank)
|
| 519 |
if qb:
|
| 520 |
system_prompt = (system_prompt + "\n\n" + qb).strip()
|
| 521 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 522 |
patient_persona = self.persona_system.get_persona(chat_info.patient_persona_id) or {}
|
| 523 |
try:
|
| 524 |
patient_context = self.persona_system.prompt_builder.build_system_prompt(patient_persona)
|
|
@@ -578,7 +578,7 @@ class ConversationService:
|
|
| 578 |
model: Optional[str] = None,
|
| 579 |
surveyor_prompt_addition: Optional[str] = None,
|
| 580 |
patient_prompt_addition: Optional[str] = None,
|
| 581 |
-
|
| 582 |
surveyor_question_bank: Optional[str] = None) -> bool:
|
| 583 |
"""Start a new AI-to-AI conversation.
|
| 584 |
|
|
@@ -623,7 +623,7 @@ class ConversationService:
|
|
| 623 |
llm_backend=resolved_backend,
|
| 624 |
surveyor_prompt_addition=surveyor_prompt_addition,
|
| 625 |
patient_prompt_addition=patient_prompt_addition,
|
| 626 |
-
|
| 627 |
surveyor_question_bank=surveyor_question_bank if isinstance(surveyor_question_bank, str) and surveyor_question_bank.strip() else None,
|
| 628 |
status=ConversationStatus.STARTING,
|
| 629 |
created_at=datetime.now()
|
|
@@ -647,7 +647,7 @@ class ConversationService:
|
|
| 647 |
llm_parameters=llm_parameters,
|
| 648 |
surveyor_prompt_addition=surveyor_prompt_addition,
|
| 649 |
patient_prompt_addition=patient_prompt_addition,
|
| 650 |
-
|
| 651 |
surveyor_question_bank=surveyor_question_bank if isinstance(surveyor_question_bank, str) else None,
|
| 652 |
)
|
| 653 |
|
|
@@ -914,7 +914,7 @@ class ConversationService:
|
|
| 914 |
"patient_persona_id": conv_info.patient_persona_id,
|
| 915 |
"surveyor_prompt_addition": getattr(conv_info, "surveyor_prompt_addition", None),
|
| 916 |
"patient_prompt_addition": getattr(conv_info, "patient_prompt_addition", None),
|
| 917 |
-
"
|
| 918 |
"surveyor_question_bank": getattr(conv_info, "surveyor_question_bank", None),
|
| 919 |
"asked_question_ids": getattr(manager, "asked_question_ids", None) if hasattr(manager, "asked_question_ids") else None,
|
| 920 |
},
|
|
|
|
| 42 |
from .conversation_ws import ConnectionManager # noqa: E402
|
| 43 |
from .storage_service import get_run_store # noqa: E402
|
| 44 |
from backend.storage import RunRecord # noqa: E402
|
| 45 |
+
from backend.core.surveyor_knobs import compile_surveyor_attributes_overlay, compile_question_bank_overlay # noqa: E402
|
| 46 |
|
| 47 |
# Setup logging
|
| 48 |
logger = logging.getLogger(__name__)
|
|
|
|
| 272 |
stop_requested: bool = False
|
| 273 |
surveyor_prompt_addition: Optional[str] = None
|
| 274 |
patient_prompt_addition: Optional[str] = None
|
| 275 |
+
surveyor_attributes: List[str] = field(default_factory=list)
|
| 276 |
surveyor_question_bank: Optional[str] = None
|
| 277 |
|
| 278 |
|
|
|
|
| 291 |
stop_requested: bool = False
|
| 292 |
surveyor_prompt_addition: Optional[str] = None
|
| 293 |
patient_prompt_addition: Optional[str] = None
|
| 294 |
+
surveyor_attributes: List[str] = field(default_factory=list)
|
| 295 |
surveyor_question_bank: Optional[str] = None
|
| 296 |
asked_question_ids: List[str] = field(default_factory=list)
|
| 297 |
lock: asyncio.Lock = field(default_factory=asyncio.Lock)
|
|
|
|
| 334 |
model: Optional[str] = None,
|
| 335 |
surveyor_prompt_addition: Optional[str] = None,
|
| 336 |
patient_prompt_addition: Optional[str] = None,
|
| 337 |
+
surveyor_attributes: Optional[List[str]] = None,
|
| 338 |
surveyor_question_bank: Optional[str] = None,
|
| 339 |
) -> bool:
|
| 340 |
"""Start a new human-to-surveyor chat session."""
|
|
|
|
| 361 |
llm_backend=resolved_backend,
|
| 362 |
surveyor_prompt_addition=surveyor_prompt_addition,
|
| 363 |
patient_prompt_addition=patient_prompt_addition,
|
| 364 |
+
surveyor_attributes=[s.strip() for s in (surveyor_attributes or []) if isinstance(s, str) and s.strip()],
|
| 365 |
surveyor_question_bank=surveyor_question_bank if isinstance(surveyor_question_bank, str) and surveyor_question_bank.strip() else None,
|
| 366 |
status=ConversationStatus.STARTING,
|
| 367 |
created_at=datetime.now(),
|
|
|
|
| 511 |
user_prompt=user_prompt,
|
| 512 |
)
|
| 513 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 514 |
qb = compile_question_bank_overlay(chat_info.surveyor_question_bank)
|
| 515 |
if qb:
|
| 516 |
system_prompt = (system_prompt + "\n\n" + qb).strip()
|
| 517 |
|
| 518 |
+
attrs = compile_surveyor_attributes_overlay(chat_info.surveyor_attributes)
|
| 519 |
+
if attrs:
|
| 520 |
+
system_prompt = (system_prompt + "\n\n" + attrs).strip()
|
| 521 |
+
|
| 522 |
patient_persona = self.persona_system.get_persona(chat_info.patient_persona_id) or {}
|
| 523 |
try:
|
| 524 |
patient_context = self.persona_system.prompt_builder.build_system_prompt(patient_persona)
|
|
|
|
| 578 |
model: Optional[str] = None,
|
| 579 |
surveyor_prompt_addition: Optional[str] = None,
|
| 580 |
patient_prompt_addition: Optional[str] = None,
|
| 581 |
+
surveyor_attributes: Optional[List[str]] = None,
|
| 582 |
surveyor_question_bank: Optional[str] = None) -> bool:
|
| 583 |
"""Start a new AI-to-AI conversation.
|
| 584 |
|
|
|
|
| 623 |
llm_backend=resolved_backend,
|
| 624 |
surveyor_prompt_addition=surveyor_prompt_addition,
|
| 625 |
patient_prompt_addition=patient_prompt_addition,
|
| 626 |
+
surveyor_attributes=[s.strip() for s in (surveyor_attributes or []) if isinstance(s, str) and s.strip()],
|
| 627 |
surveyor_question_bank=surveyor_question_bank if isinstance(surveyor_question_bank, str) and surveyor_question_bank.strip() else None,
|
| 628 |
status=ConversationStatus.STARTING,
|
| 629 |
created_at=datetime.now()
|
|
|
|
| 647 |
llm_parameters=llm_parameters,
|
| 648 |
surveyor_prompt_addition=surveyor_prompt_addition,
|
| 649 |
patient_prompt_addition=patient_prompt_addition,
|
| 650 |
+
surveyor_attributes=[s.strip() for s in (surveyor_attributes or []) if isinstance(s, str) and s.strip()],
|
| 651 |
surveyor_question_bank=surveyor_question_bank if isinstance(surveyor_question_bank, str) else None,
|
| 652 |
)
|
| 653 |
|
|
|
|
| 914 |
"patient_persona_id": conv_info.patient_persona_id,
|
| 915 |
"surveyor_prompt_addition": getattr(conv_info, "surveyor_prompt_addition", None),
|
| 916 |
"patient_prompt_addition": getattr(conv_info, "patient_prompt_addition", None),
|
| 917 |
+
"surveyor_attributes": getattr(conv_info, "surveyor_attributes", None),
|
| 918 |
"surveyor_question_bank": getattr(conv_info, "surveyor_question_bank", None),
|
| 919 |
"asked_question_ids": getattr(manager, "asked_question_ids", None) if hasattr(manager, "asked_question_ids") else None,
|
| 920 |
},
|
backend/api/conversation_ws.py
CHANGED
|
@@ -335,7 +335,7 @@ async def handle_start_conversation(data: dict, conversation_id: str):
|
|
| 335 |
model = data.get("model")
|
| 336 |
surveyor_prompt_addition = data.get("surveyor_prompt_addition")
|
| 337 |
patient_prompt_addition = data.get("patient_prompt_addition")
|
| 338 |
-
|
| 339 |
surveyor_question_bank = data.get("surveyor_question_bank")
|
| 340 |
|
| 341 |
if not surveyor_persona_id or not patient_persona_id:
|
|
@@ -355,7 +355,7 @@ async def handle_start_conversation(data: dict, conversation_id: str):
|
|
| 355 |
model=model,
|
| 356 |
surveyor_prompt_addition=surveyor_prompt_addition,
|
| 357 |
patient_prompt_addition=patient_prompt_addition,
|
| 358 |
-
|
| 359 |
surveyor_question_bank=surveyor_question_bank,
|
| 360 |
)
|
| 361 |
|
|
@@ -390,7 +390,7 @@ async def handle_start_human_chat(data: dict, conversation_id: str):
|
|
| 390 |
model = data.get("model")
|
| 391 |
surveyor_prompt_addition = data.get("surveyor_prompt_addition")
|
| 392 |
patient_prompt_addition = data.get("patient_prompt_addition")
|
| 393 |
-
|
| 394 |
surveyor_question_bank = data.get("surveyor_question_bank")
|
| 395 |
|
| 396 |
if not surveyor_persona_id or not patient_persona_id:
|
|
@@ -409,7 +409,7 @@ async def handle_start_human_chat(data: dict, conversation_id: str):
|
|
| 409 |
model=model,
|
| 410 |
surveyor_prompt_addition=surveyor_prompt_addition,
|
| 411 |
patient_prompt_addition=patient_prompt_addition,
|
| 412 |
-
|
| 413 |
surveyor_question_bank=surveyor_question_bank,
|
| 414 |
)
|
| 415 |
|
|
|
|
| 335 |
model = data.get("model")
|
| 336 |
surveyor_prompt_addition = data.get("surveyor_prompt_addition")
|
| 337 |
patient_prompt_addition = data.get("patient_prompt_addition")
|
| 338 |
+
surveyor_attributes = data.get("surveyor_attributes")
|
| 339 |
surveyor_question_bank = data.get("surveyor_question_bank")
|
| 340 |
|
| 341 |
if not surveyor_persona_id or not patient_persona_id:
|
|
|
|
| 355 |
model=model,
|
| 356 |
surveyor_prompt_addition=surveyor_prompt_addition,
|
| 357 |
patient_prompt_addition=patient_prompt_addition,
|
| 358 |
+
surveyor_attributes=surveyor_attributes,
|
| 359 |
surveyor_question_bank=surveyor_question_bank,
|
| 360 |
)
|
| 361 |
|
|
|
|
| 390 |
model = data.get("model")
|
| 391 |
surveyor_prompt_addition = data.get("surveyor_prompt_addition")
|
| 392 |
patient_prompt_addition = data.get("patient_prompt_addition")
|
| 393 |
+
surveyor_attributes = data.get("surveyor_attributes")
|
| 394 |
surveyor_question_bank = data.get("surveyor_question_bank")
|
| 395 |
|
| 396 |
if not surveyor_persona_id or not patient_persona_id:
|
|
|
|
| 409 |
model=model,
|
| 410 |
surveyor_prompt_addition=surveyor_prompt_addition,
|
| 411 |
patient_prompt_addition=patient_prompt_addition,
|
| 412 |
+
surveyor_attributes=surveyor_attributes,
|
| 413 |
surveyor_question_bank=surveyor_question_bank,
|
| 414 |
)
|
| 415 |
|
backend/core/conversation_manager.py
CHANGED
|
@@ -31,7 +31,7 @@ sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
| 31 |
|
| 32 |
from backend.core.llm_client import create_llm_client
|
| 33 |
from backend.core.persona_system import PersonaSystem
|
| 34 |
-
from backend.core.surveyor_knobs import
|
| 35 |
|
| 36 |
SURVEYOR_MAX_TOKENS = 140
|
| 37 |
PATIENT_MAX_TOKENS = 240
|
|
@@ -82,7 +82,7 @@ class ConversationManager:
|
|
| 82 |
patient_persona: dict = None,
|
| 83 |
surveyor_prompt_addition: Optional[str] = None,
|
| 84 |
patient_prompt_addition: Optional[str] = None,
|
| 85 |
-
|
| 86 |
surveyor_question_bank: Optional[str] = None,
|
| 87 |
host: str = "http://localhost:11434",
|
| 88 |
model: str = "llama3.2:latest",
|
|
@@ -132,7 +132,7 @@ class ConversationManager:
|
|
| 132 |
self.turn_count = 0
|
| 133 |
self.surveyor_prompt_addition = (surveyor_prompt_addition or "").strip()
|
| 134 |
self.patient_prompt_addition = (patient_prompt_addition or "").strip()
|
| 135 |
-
self.
|
| 136 |
self.surveyor_question_bank = (surveyor_question_bank or "").strip() or None
|
| 137 |
self.asked_question_ids: List[str] = []
|
| 138 |
|
|
@@ -235,12 +235,12 @@ class ConversationManager:
|
|
| 235 |
conversation_history=conversation_history,
|
| 236 |
user_prompt=user_prompt
|
| 237 |
)
|
| 238 |
-
overlay = compile_surveyor_overlay(self.surveyor_knobs)
|
| 239 |
-
if overlay:
|
| 240 |
-
system_prompt = f"{system_prompt}\n\n{overlay}"
|
| 241 |
qb = compile_question_bank_overlay(self.surveyor_question_bank)
|
| 242 |
if qb:
|
| 243 |
system_prompt = f"{system_prompt}\n\n{qb}"
|
|
|
|
|
|
|
|
|
|
| 244 |
if self.surveyor_prompt_addition:
|
| 245 |
system_prompt = f"{system_prompt}\n\nAdditional instructions:\n{self.surveyor_prompt_addition}"
|
| 246 |
|
|
|
|
| 31 |
|
| 32 |
from backend.core.llm_client import create_llm_client
|
| 33 |
from backend.core.persona_system import PersonaSystem
|
| 34 |
+
from backend.core.surveyor_knobs import compile_surveyor_attributes_overlay, compile_question_bank_overlay
|
| 35 |
|
| 36 |
SURVEYOR_MAX_TOKENS = 140
|
| 37 |
PATIENT_MAX_TOKENS = 240
|
|
|
|
| 82 |
patient_persona: dict = None,
|
| 83 |
surveyor_prompt_addition: Optional[str] = None,
|
| 84 |
patient_prompt_addition: Optional[str] = None,
|
| 85 |
+
surveyor_attributes: Optional[List[str]] = None,
|
| 86 |
surveyor_question_bank: Optional[str] = None,
|
| 87 |
host: str = "http://localhost:11434",
|
| 88 |
model: str = "llama3.2:latest",
|
|
|
|
| 132 |
self.turn_count = 0
|
| 133 |
self.surveyor_prompt_addition = (surveyor_prompt_addition or "").strip()
|
| 134 |
self.patient_prompt_addition = (patient_prompt_addition or "").strip()
|
| 135 |
+
self.surveyor_attributes = [s.strip() for s in (surveyor_attributes or []) if isinstance(s, str) and s.strip()]
|
| 136 |
self.surveyor_question_bank = (surveyor_question_bank or "").strip() or None
|
| 137 |
self.asked_question_ids: List[str] = []
|
| 138 |
|
|
|
|
| 235 |
conversation_history=conversation_history,
|
| 236 |
user_prompt=user_prompt
|
| 237 |
)
|
|
|
|
|
|
|
|
|
|
| 238 |
qb = compile_question_bank_overlay(self.surveyor_question_bank)
|
| 239 |
if qb:
|
| 240 |
system_prompt = f"{system_prompt}\n\n{qb}"
|
| 241 |
+
attrs = compile_surveyor_attributes_overlay(self.surveyor_attributes)
|
| 242 |
+
if attrs:
|
| 243 |
+
system_prompt = f"{system_prompt}\n\n{attrs}"
|
| 244 |
if self.surveyor_prompt_addition:
|
| 245 |
system_prompt = f"{system_prompt}\n\nAdditional instructions:\n{self.surveyor_prompt_addition}"
|
| 246 |
|
backend/core/surveyor_knobs.py
CHANGED
|
@@ -1,55 +1,34 @@
|
|
| 1 |
from __future__ import annotations
|
| 2 |
|
| 3 |
-
from typing import Any,
|
| 4 |
|
| 5 |
|
| 6 |
-
def
|
| 7 |
if not value:
|
| 8 |
return []
|
|
|
|
|
|
|
|
|
|
| 9 |
if isinstance(value, list):
|
| 10 |
out: List[str] = []
|
| 11 |
for item in value:
|
| 12 |
-
if isinstance(item, str)
|
| 13 |
-
|
|
|
|
|
|
|
| 14 |
return out
|
| 15 |
return []
|
| 16 |
|
| 17 |
|
| 18 |
-
def
|
| 19 |
-
"""Compile
|
| 20 |
-
|
|
|
|
| 21 |
return ""
|
| 22 |
-
|
| 23 |
-
stance = knobs.get("stance")
|
| 24 |
-
question_strategy = knobs.get("question_strategy")
|
| 25 |
-
empathy_style = knobs.get("empathy_style")
|
| 26 |
-
off_track_handling = knobs.get("off_track_handling")
|
| 27 |
-
|
| 28 |
-
probing_policy = _as_str_list(knobs.get("probing_policy"))
|
| 29 |
-
sensitivity_handling = _as_str_list(knobs.get("sensitivity_handling"))
|
| 30 |
-
|
| 31 |
lines: List[str] = []
|
| 32 |
-
lines.append("Surveyor
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
if isinstance(value, str) and value.strip():
|
| 36 |
-
lines.append(f"- {label}: {value.strip()}")
|
| 37 |
-
|
| 38 |
-
add("stance", stance)
|
| 39 |
-
add("question_strategy", question_strategy)
|
| 40 |
-
add("empathy_style", empathy_style)
|
| 41 |
-
add("off_track_handling", off_track_handling)
|
| 42 |
-
|
| 43 |
-
if probing_policy:
|
| 44 |
-
lines.append("- probing_policy:")
|
| 45 |
-
for item in probing_policy:
|
| 46 |
-
lines.append(f" - {item}")
|
| 47 |
-
|
| 48 |
-
if sensitivity_handling:
|
| 49 |
-
lines.append("- sensitivity_handling:")
|
| 50 |
-
for item in sensitivity_handling:
|
| 51 |
-
lines.append(f" - {item}")
|
| 52 |
-
|
| 53 |
return "\n".join(lines).strip()
|
| 54 |
|
| 55 |
|
|
|
|
| 1 |
from __future__ import annotations
|
| 2 |
|
| 3 |
+
from typing import Any, List, Optional
|
| 4 |
|
| 5 |
|
| 6 |
+
def _clean_bullets(value: Any) -> List[str]:
|
| 7 |
if not value:
|
| 8 |
return []
|
| 9 |
+
if isinstance(value, str):
|
| 10 |
+
parts = [line.strip() for line in value.splitlines()]
|
| 11 |
+
return [p for p in parts if p and not p.startswith("#")]
|
| 12 |
if isinstance(value, list):
|
| 13 |
out: List[str] = []
|
| 14 |
for item in value:
|
| 15 |
+
if isinstance(item, str):
|
| 16 |
+
s = item.strip()
|
| 17 |
+
if s and not s.startswith("#"):
|
| 18 |
+
out.append(s)
|
| 19 |
return out
|
| 20 |
return []
|
| 21 |
|
| 22 |
|
| 23 |
+
def compile_surveyor_attributes_overlay(attributes: Any) -> str:
|
| 24 |
+
"""Compile surveyor attributes (plain bullet lines) into a prompt overlay."""
|
| 25 |
+
items = _clean_bullets(attributes)
|
| 26 |
+
if not items:
|
| 27 |
return ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
lines: List[str] = []
|
| 29 |
+
lines.append("Surveyor attributes (follow these):")
|
| 30 |
+
for item in items:
|
| 31 |
+
lines.append(f"- {item}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
return "\n".join(lines).strip()
|
| 33 |
|
| 34 |
|
docs/README.md
CHANGED
|
@@ -5,7 +5,7 @@ These short guides are all you need to extend the AI Survey Simulator:
|
|
| 5 |
- `overview.md` — architecture summary, major components, and repository map.
|
| 6 |
- `development.md` — setup, runtime instructions, and implementation guidelines.
|
| 7 |
- `persistence.md` — persistent run history + persona CRUD design (HF `/data` now, Railway later).
|
| 8 |
-
- `persona-knobs.md` — persona configuration
|
| 9 |
- `roadmap.md` — current status and prioritized future work.
|
| 10 |
|
| 11 |
Keep documentation lean: update the relevant file when behavior changes or priorities shift.
|
|
|
|
| 5 |
- `overview.md` — architecture summary, major components, and repository map.
|
| 6 |
- `development.md` — setup, runtime instructions, and implementation guidelines.
|
| 7 |
- `persistence.md` — persistent run history + persona CRUD design (HF `/data` now, Railway later).
|
| 8 |
+
- `persona-knobs.md` — persona configuration controls (v2) for Surveyor + Patient.
|
| 9 |
- `roadmap.md` — current status and prioritized future work.
|
| 10 |
|
| 11 |
Keep documentation lean: update the relevant file when behavior changes or priorities shift.
|
docs/development.md
CHANGED
|
@@ -74,7 +74,7 @@ The UI also includes a **Configuration** view that lets you select personas and
|
|
| 74 |
- No automated test suite yet. Add lightweight `pytest` modules under `tests/` as you extend functionality.
|
| 75 |
- Manually verify through the primary web UI (`frontend/react_gradio_hybrid.py`).
|
| 76 |
- For persistence + history: complete a run, confirm it appears in **History**, restart the container, and confirm it still appears and exports download.
|
| 77 |
-
- For
|
| 78 |
- For question bank (surveyor): set a question bank in **Configuration → Surveyors**, complete a run, and confirm the surveyor sticks to one bank question per turn and the run’s `config.personas.asked_question_ids` is populated in `/api/runs/{run_id}`.
|
| 79 |
- If you need to debug the conversation loop, instrument `backend/core/conversation_manager.py` or launch a shell and run it directly.
|
| 80 |
|
|
|
|
| 74 |
- No automated test suite yet. Add lightweight `pytest` modules under `tests/` as you extend functionality.
|
| 75 |
- Manually verify through the primary web UI (`frontend/react_gradio_hybrid.py`).
|
| 76 |
- For persistence + history: complete a run, confirm it appears in **History**, restart the container, and confirm it still appears and exports download.
|
| 77 |
+
- For surveyor configuration: change **Surveyors → Attributes** and/or **Question bank** in **Configuration**, run an AI↔AI session, and confirm the completed run’s `config.personas.surveyor_attributes` and `config.personas.surveyor_question_bank` are present when fetched from `/api/runs/{run_id}`.
|
| 78 |
- For question bank (surveyor): set a question bank in **Configuration → Surveyors**, complete a run, and confirm the surveyor sticks to one bank question per turn and the run’s `config.personas.asked_question_ids` is populated in `/api/runs/{run_id}`.
|
| 79 |
- If you need to debug the conversation loop, instrument `backend/core/conversation_manager.py` or launch a shell and run it directly.
|
| 80 |
|
docs/persona-knobs.md
CHANGED
|
@@ -1,136 +1,94 @@
|
|
| 1 |
-
# Persona
|
| 2 |
|
| 3 |
-
This document defines the **core, behavior-changing configuration
|
| 4 |
|
| 5 |
Goal alignment:
|
| 6 |
|
| 7 |
- **A) Surveyor quality**: reliably covers target questions with empathy, good flow, and appropriate probing.
|
| 8 |
- **B) Validation harness**: synthetic patients exist to *stress test* the surveyor under realistic situations.
|
| 9 |
-
- **C) Analysis**: handled separately (resource agent
|
| 10 |
|
| 11 |
-
The guiding principle is: **every UI
|
| 12 |
|
| 13 |
---
|
| 14 |
|
| 15 |
-
## Key design decision: compile
|
| 16 |
|
| 17 |
-
Today,
|
| 18 |
|
| 19 |
-
For
|
| 20 |
|
| 21 |
-
1.
|
| 22 |
-
2.
|
| 23 |
-
3.
|
| 24 |
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
Non-goal: making every current YAML field first-class.
|
| 28 |
|
| 29 |
---
|
| 30 |
|
| 31 |
## What is “baked in” (global, not persona-specific)
|
| 32 |
|
| 33 |
-
These are global constraints
|
| 34 |
|
| 35 |
-
- Turn-format constraints (e.g.,
|
| 36 |
- Conversation scaffolding templates (greeting/follow-up framing; use of the last message).
|
| 37 |
-
- Conversation-history inclusion and formatting
|
| 38 |
-
- Safety/compliance rules we want consistently enforced
|
| 39 |
|
| 40 |
-
Personas can express style *within* these constraints, but do not change the constraints themselves in
|
| 41 |
|
| 42 |
---
|
| 43 |
|
| 44 |
-
## Surveyor
|
| 45 |
-
|
| 46 |
-
These should be exposed as first-class fields and compiled into the surveyor system prompt.
|
| 47 |
|
| 48 |
### Identity
|
| 49 |
- `name` (required): display name.
|
| 50 |
- `description` (optional): short one-liner shown in selectors.
|
| 51 |
|
| 52 |
-
###
|
| 53 |
-
|
| 54 |
-
-
|
| 55 |
-
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
- `offer_skip`
|
| 66 |
-
- `normalize`
|
| 67 |
-
- `avoid_advice`
|
| 68 |
-
|
| 69 |
-
### Freeform prompt
|
| 70 |
-
- `system_prompt` (required): human-editable base prompt (still important).
|
| 71 |
-
- `system_prompt_addition` (optional, per-run): already supported in the configuration UI; stays as run-level override.
|
| 72 |
|
| 73 |
---
|
| 74 |
|
| 75 |
-
## Patient
|
| 76 |
|
| 77 |
-
|
| 78 |
|
| 79 |
### Identity
|
| 80 |
- `name` (required): display name.
|
| 81 |
- `description` (optional): short one-liner shown in selectors.
|
| 82 |
|
| 83 |
-
###
|
| 84 |
-
|
| 85 |
-
- `medications` (list): e.g., `metformin`, `albuterol`.
|
| 86 |
-
- `care_context` (short text): e.g., “primary care follow-up”, “recent ER visit”.
|
| 87 |
-
- `recent_events` (short bullets): key timeline anchors used to answer questions realistically.
|
| 88 |
-
|
| 89 |
-
### Barriers / constraints
|
| 90 |
-
- `barriers` (multi-select):
|
| 91 |
-
- `cost`
|
| 92 |
-
- `transport`
|
| 93 |
-
- `time`
|
| 94 |
-
- `insurance`
|
| 95 |
-
- `digital_access`
|
| 96 |
-
- `language`
|
| 97 |
-
- `stigma`
|
| 98 |
-
|
| 99 |
-
### Conversational behavior
|
| 100 |
-
- `cooperativeness` (ordinal): `forthcoming | mixed | guarded`.
|
| 101 |
-
- `emotional_stance` (dropdown): `anxious | frustrated | neutral | optimistic`.
|
| 102 |
-
- `verbosity` (dropdown): `brief | normal | detailed`.
|
| 103 |
-
- `health_literacy` (dropdown): `low | medium | high`.
|
| 104 |
-
- `recall_certainty` (dropdown): `confident | mixed | uncertain`.
|
| 105 |
-
- `digression_tendency` (dropdown): `on_track | occasional_anecdotes | tangential`.
|
| 106 |
-
|
| 107 |
-
### Freeform prompt
|
| 108 |
-
- `system_prompt` (required): human-editable base prompt.
|
| 109 |
-
- `system_prompt_addition` (optional, per-run): already supported in the configuration UI; stays as run-level override.
|
| 110 |
|
| 111 |
---
|
| 112 |
|
| 113 |
-
## Secondary
|
| 114 |
|
| 115 |
These may be useful later but are intentionally deferred to avoid UI complexity without clear validation value.
|
| 116 |
|
| 117 |
Surveyor:
|
| 118 |
-
-
|
| 119 |
-
- pacing (brief/normal/thorough) beyond global constraints
|
| 120 |
-
- explicit consent/confidentiality micro-scripts as toggles
|
| 121 |
|
| 122 |
Patient:
|
| 123 |
-
-
|
| 124 |
-
- quirks (interrupts, under-reports symptoms, minimizes pain)
|
| 125 |
-
- richer biography fields (occupation, family structure) unless used for study design
|
| 126 |
|
| 127 |
---
|
| 128 |
|
| 129 |
-
## Implementation notes (how these
|
| 130 |
|
| 131 |
To prevent dead fields, the implementation should ensure:
|
| 132 |
|
| 133 |
-
-
|
| 134 |
-
-
|
| 135 |
-
- Persisted runs capture the persona snapshot and the prompt overlay used (already supported by run snapshots).
|
| 136 |
-
|
|
|
|
| 1 |
+
# Persona Controls (v2) — Surveyor + Patient (local-first)
|
| 2 |
|
| 3 |
+
This document defines the **core, behavior-changing configuration controls** we support for personas.
|
| 4 |
|
| 5 |
Goal alignment:
|
| 6 |
|
| 7 |
- **A) Surveyor quality**: reliably covers target questions with empathy, good flow, and appropriate probing.
|
| 8 |
- **B) Validation harness**: synthetic patients exist to *stress test* the surveyor under realistic situations.
|
| 9 |
+
- **C) Analysis**: handled separately (resource agent controls are out of scope here).
|
| 10 |
|
| 11 |
+
The guiding principle is: **every UI control must have a guaranteed effect** on generation (no decorative controls).
|
| 12 |
|
| 13 |
---
|
| 14 |
|
| 15 |
+
## Key design decision: compile attributes + question bank into prompts
|
| 16 |
|
| 17 |
+
Today, behavior is primarily driven by the persona’s `system_prompt`. Many YAML fields do not materially affect generation unless they are duplicated into that prompt.
|
| 18 |
|
| 19 |
+
For v2, we:
|
| 20 |
|
| 21 |
+
1. Let the user define a small set of plain-language **Attributes** (bullet lines) for the surveyor.
|
| 22 |
+
2. Let the user define a **Question bank** the surveyor must work through.
|
| 23 |
+
3. **Compile** both into a deterministic prompt overlay included in the surveyor system prompt, so the controls always matter.
|
| 24 |
|
| 25 |
+
Non-goal: making every YAML field first-class in the UI.
|
|
|
|
|
|
|
| 26 |
|
| 27 |
---
|
| 28 |
|
| 29 |
## What is “baked in” (global, not persona-specific)
|
| 30 |
|
| 31 |
+
These are global constraints/scaffolding that apply to all personas and are not edited in the config UI:
|
| 32 |
|
| 33 |
+
- Turn-format constraints (e.g., 1–2 sentences, at most one question per turn).
|
| 34 |
- Conversation scaffolding templates (greeting/follow-up framing; use of the last message).
|
| 35 |
+
- Conversation-history inclusion and formatting.
|
| 36 |
+
- Safety/compliance rules we want consistently enforced.
|
| 37 |
|
| 38 |
+
Personas can express style *within* these constraints, but do not change the constraints themselves in v2.
|
| 39 |
|
| 40 |
---
|
| 41 |
|
| 42 |
+
## Surveyor controls (Core v2)
|
|
|
|
|
|
|
| 43 |
|
| 44 |
### Identity
|
| 45 |
- `name` (required): display name.
|
| 46 |
- `description` (optional): short one-liner shown in selectors.
|
| 47 |
|
| 48 |
+
### Attributes (plain bullet lines)
|
| 49 |
+
A list of short, testable rules, for example:
|
| 50 |
+
- “Ask exactly one question per turn; no multi-part questions.”
|
| 51 |
+
- “Reflect emotion briefly before redirecting.”
|
| 52 |
+
- “Avoid medical advice; suggest clinician follow-up when needed.”
|
| 53 |
+
|
| 54 |
+
### Question bank (targets to cover)
|
| 55 |
+
A list of survey questions the surveyor must work through.
|
| 56 |
+
|
| 57 |
+
When a question bank is present, the surveyor outputs strict JSON including the chosen question id (e.g., `q01`) so asked questions can be tracked deterministically.
|
| 58 |
+
|
| 59 |
+
### Surveyor prompt addition (optional, per-run)
|
| 60 |
+
Additional free-form instructions appended last (lowest priority).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
|
| 62 |
---
|
| 63 |
|
| 64 |
+
## Patient controls (Core v1, minimal)
|
| 65 |
|
| 66 |
+
Patient structured controls are intentionally minimal for now; the synthetic patient exists as a validation harness.
|
| 67 |
|
| 68 |
### Identity
|
| 69 |
- `name` (required): display name.
|
| 70 |
- `description` (optional): short one-liner shown in selectors.
|
| 71 |
|
| 72 |
+
### Patient prompt addition (optional, per-run)
|
| 73 |
+
Patient prompt addition is supported as a run-level override.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 74 |
|
| 75 |
---
|
| 76 |
|
| 77 |
+
## Secondary controls (documented, not implemented yet)
|
| 78 |
|
| 79 |
These may be useful later but are intentionally deferred to avoid UI complexity without clear validation value.
|
| 80 |
|
| 81 |
Surveyor:
|
| 82 |
+
- richer structured knobs (stance/probing/off-track handling) if we find a strong need
|
|
|
|
|
|
|
| 83 |
|
| 84 |
Patient:
|
| 85 |
+
- richer structured scenario/behavior fields once we define how to validate them
|
|
|
|
|
|
|
| 86 |
|
| 87 |
---
|
| 88 |
|
| 89 |
+
## Implementation notes (how these controls become real)
|
| 90 |
|
| 91 |
To prevent dead fields, the implementation should ensure:
|
| 92 |
|
| 93 |
+
- Core controls are included in the final system prompt text (compiled overlay).
|
| 94 |
+
- Persisted runs capture the config snapshot used (including attributes + question bank).
|
|
|
|
|
|
frontend/pages/config_view.py
CHANGED
|
@@ -1,13 +1,13 @@
|
|
| 1 |
def get_config_view_js() -> str:
|
| 2 |
return r"""
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
|
| 8 |
const [selectedSurveyorId, setSelectedSurveyorId] = React.useState(existing?.surveyor_persona_id || 'friendly_researcher_001');
|
| 9 |
const [selectedPatientId, setSelectedPatientId] = React.useState(existing?.patient_persona_id || 'cooperative_senior_001');
|
| 10 |
-
|
| 11 |
const items = Array.isArray(existing?.surveyor_question_bank_items) ? existing.surveyor_question_bank_items : null;
|
| 12 |
if (items && items.length) {
|
| 13 |
return items
|
|
@@ -18,18 +18,20 @@ def get_config_view_js() -> str:
|
|
| 18 |
const raw = typeof existing?.surveyor_question_bank === 'string' ? existing.surveyor_question_bank : '';
|
| 19 |
const lines = raw.split('\n').map((l) => (l || '').trim()).filter((l) => l.length > 0);
|
| 20 |
return lines.map((text, idx) => ({ id: `q${String(idx + 1).padStart(2, '0')}`, text }));
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
|
|
|
|
|
|
| 33 |
|
| 34 |
const surveyorQuestionBankText = React.useMemo(() => {
|
| 35 |
const lines = (surveyorQuestionItems || [])
|
|
@@ -38,7 +40,7 @@ def get_config_view_js() -> str:
|
|
| 38 |
return lines.join('\n');
|
| 39 |
}, [surveyorQuestionItems]);
|
| 40 |
|
| 41 |
-
|
| 42 |
const nextId = `q${String((surveyorQuestionItems || []).length + 1).padStart(2, '0')}`;
|
| 43 |
setSurveyorQuestionItems((prev) => ([...(prev || []), { id: nextId, text: '' }]));
|
| 44 |
};
|
|
@@ -47,9 +49,21 @@ def get_config_view_js() -> str:
|
|
| 47 |
setSurveyorQuestionItems((prev) => (prev || []).map((q) => (q.id === id ? Object.assign({}, q, { text }) : q)));
|
| 48 |
};
|
| 49 |
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
|
| 54 |
React.useEffect(() => {
|
| 55 |
authedFetch('/api/personas')
|
|
@@ -63,22 +77,24 @@ def get_config_view_js() -> str:
|
|
| 63 |
.catch(() => {});
|
| 64 |
}, []);
|
| 65 |
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
|
|
|
|
|
|
| 82 |
|
| 83 |
const TabNav = () => {
|
| 84 |
const base = "px-4 py-2 rounded-lg text-sm font-semibold border transition-colors";
|
|
@@ -110,174 +126,120 @@ def get_config_view_js() -> str:
|
|
| 110 |
|
| 111 |
<TabNav />
|
| 112 |
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
>
|
| 122 |
-
{(personas.surveyors.length ? personas.surveyors : [{id: 'friendly_researcher_001', name: 'Alex Thompson'}]).map(p => (
|
| 123 |
-
<option key={p.id} value={p.id}>{p.name} ({p.id})</option>
|
| 124 |
-
))}
|
| 125 |
-
</select>
|
| 126 |
-
|
| 127 |
-
<div className="mt-4">
|
| 128 |
-
<label className="block text-sm font-semibold text-slate-700 mb-2">Surveyor stance</label>
|
| 129 |
-
<select
|
| 130 |
-
className="w-full border border-slate-300 rounded-lg px-3 py-2 text-sm bg-white"
|
| 131 |
-
value={surveyorKnobs.stance || 'empathetic_researcher'}
|
| 132 |
-
onChange={(e) => setSurveyorKnobs(prev => Object.assign({}, prev, { stance: e.target.value }))}
|
| 133 |
-
>
|
| 134 |
-
<option value="empathetic_researcher">Empathetic researcher</option>
|
| 135 |
-
<option value="neutral_clinical">Neutral clinical</option>
|
| 136 |
-
<option value="time_efficient">Time efficient</option>
|
| 137 |
-
</select>
|
| 138 |
-
</div>
|
| 139 |
-
|
| 140 |
-
<div className="mt-4">
|
| 141 |
-
<label className="block text-sm font-semibold text-slate-700 mb-2">Question strategy</label>
|
| 142 |
-
<select
|
| 143 |
-
className="w-full border border-slate-300 rounded-lg px-3 py-2 text-sm bg-white"
|
| 144 |
-
value={surveyorKnobs.question_strategy || 'conversational_cover_all'}
|
| 145 |
-
onChange={(e) => setSurveyorKnobs(prev => Object.assign({}, prev, { question_strategy: e.target.value }))}
|
| 146 |
-
>
|
| 147 |
-
<option value="sequential">Sequential</option>
|
| 148 |
-
<option value="adaptive_followups">Adaptive with follow-ups</option>
|
| 149 |
-
<option value="conversational_cover_all">Conversational but cover all</option>
|
| 150 |
-
</select>
|
| 151 |
-
</div>
|
| 152 |
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
</select>
|
| 164 |
-
</div>
|
| 165 |
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 178 |
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 202 |
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
const next = new Set(surveyorKnobs.sensitivity_handling || []);
|
| 217 |
-
if (e.target.checked) next.add(item.key); else next.delete(item.key);
|
| 218 |
-
setSurveyorKnobs(prev => Object.assign({}, prev, { sensitivity_handling: Array.from(next) }));
|
| 219 |
-
}}
|
| 220 |
-
/>
|
| 221 |
-
{item.label}
|
| 222 |
-
</label>
|
| 223 |
-
))}
|
| 224 |
-
</div>
|
| 225 |
-
</div>
|
| 226 |
-
|
| 227 |
-
<div>
|
| 228 |
-
<div className="flex flex-col gap-4">
|
| 229 |
-
<div>
|
| 230 |
-
<label className="block text-sm font-semibold text-slate-700 mb-2">Question bank</label>
|
| 231 |
-
<div className="text-xs text-slate-500 mb-2">
|
| 232 |
-
Questions the surveyor should aim to cover during the session.
|
| 233 |
-
</div>
|
| 234 |
-
<div className="space-y-2">
|
| 235 |
-
{(surveyorQuestionItems || []).length === 0 ? (
|
| 236 |
-
<div className="text-sm text-slate-500">No questions yet.</div>
|
| 237 |
-
) : (
|
| 238 |
-
(surveyorQuestionItems || []).map((q) => (
|
| 239 |
-
<div key={q.id} className="flex items-start gap-2">
|
| 240 |
-
<div className="mt-2 text-xs font-mono text-slate-500 w-10 shrink-0">{q.id}</div>
|
| 241 |
-
<input
|
| 242 |
-
className="flex-1 border border-slate-300 rounded-lg px-3 py-2 text-sm bg-white"
|
| 243 |
-
value={q.text || ''}
|
| 244 |
-
onChange={(e) => updateQuestionText(q.id, e.target.value)}
|
| 245 |
-
placeholder="Type a question..."
|
| 246 |
-
/>
|
| 247 |
-
<button
|
| 248 |
-
type="button"
|
| 249 |
-
onClick={() => removeQuestion(q.id)}
|
| 250 |
-
className="text-slate-600 hover:text-red-600 px-2 py-2 text-sm"
|
| 251 |
-
title="Remove question"
|
| 252 |
-
>
|
| 253 |
-
✕
|
| 254 |
-
</button>
|
| 255 |
-
</div>
|
| 256 |
-
))
|
| 257 |
-
)}
|
| 258 |
-
<button
|
| 259 |
-
type="button"
|
| 260 |
-
onClick={addQuestion}
|
| 261 |
-
className="bg-slate-100 hover:bg-slate-200 text-slate-800 px-3 py-2 rounded-lg text-sm font-semibold transition-all border border-slate-300"
|
| 262 |
-
>
|
| 263 |
-
+ Add question
|
| 264 |
-
</button>
|
| 265 |
-
</div>
|
| 266 |
-
</div>
|
| 267 |
-
|
| 268 |
-
<div>
|
| 269 |
-
<label className="block text-sm font-semibold text-slate-700 mb-2">Surveyor prompt addition (optional)</label>
|
| 270 |
-
<textarea
|
| 271 |
-
className="w-full border border-slate-300 rounded-lg px-3 py-2 text-sm bg-white h-40"
|
| 272 |
-
placeholder="E.g., Ask only one short question per turn; avoid advice; acknowledge and probe..."
|
| 273 |
-
value={surveyorPromptAddition}
|
| 274 |
-
onChange={(e) => setSurveyorPromptAddition(e.target.value)}
|
| 275 |
-
/>
|
| 276 |
-
</div>
|
| 277 |
-
</div>
|
| 278 |
-
</div>
|
| 279 |
-
</div>
|
| 280 |
-
) : activePane === 'patients' ? (
|
| 281 |
<div className="mt-6 grid grid-cols-2 gap-6">
|
| 282 |
<div>
|
| 283 |
<label className="block text-sm font-semibold text-slate-700 mb-2">Patient persona</label>
|
|
|
|
| 1 |
def get_config_view_js() -> str:
|
| 2 |
return r"""
|
| 3 |
+
function ConfigView() {
|
| 4 |
+
const existing = React.useMemo(() => loadConfig(), []);
|
| 5 |
+
const [personas, setPersonas] = React.useState({ surveyors: [], patients: [] });
|
| 6 |
+
const [activePane, setActivePane] = React.useState('surveyors'); // surveyors|patients|analysis|system
|
| 7 |
|
| 8 |
const [selectedSurveyorId, setSelectedSurveyorId] = React.useState(existing?.surveyor_persona_id || 'friendly_researcher_001');
|
| 9 |
const [selectedPatientId, setSelectedPatientId] = React.useState(existing?.patient_persona_id || 'cooperative_senior_001');
|
| 10 |
+
const [surveyorQuestionItems, setSurveyorQuestionItems] = React.useState(() => {
|
| 11 |
const items = Array.isArray(existing?.surveyor_question_bank_items) ? existing.surveyor_question_bank_items : null;
|
| 12 |
if (items && items.length) {
|
| 13 |
return items
|
|
|
|
| 18 |
const raw = typeof existing?.surveyor_question_bank === 'string' ? existing.surveyor_question_bank : '';
|
| 19 |
const lines = raw.split('\n').map((l) => (l || '').trim()).filter((l) => l.length > 0);
|
| 20 |
return lines.map((text, idx) => ({ id: `q${String(idx + 1).padStart(2, '0')}`, text }));
|
| 21 |
+
});
|
| 22 |
+
const legacySurveyorKnobsPresent = !!(existing && existing.surveyor_knobs);
|
| 23 |
+
const [surveyorAttributeItems, setSurveyorAttributeItems] = React.useState(() => {
|
| 24 |
+
const attrs = Array.isArray(existing?.surveyor_attributes) ? existing.surveyor_attributes : null;
|
| 25 |
+
if (attrs && attrs.length) {
|
| 26 |
+
return attrs
|
| 27 |
+
.map((x) => (typeof x === 'string' ? x.trim() : ''))
|
| 28 |
+
.filter((x) => x.length > 0);
|
| 29 |
+
}
|
| 30 |
+
return [];
|
| 31 |
+
});
|
| 32 |
+
const [surveyorPromptAddition, setSurveyorPromptAddition] = React.useState(existing?.surveyor_prompt_addition || '');
|
| 33 |
+
const [patientPromptAddition, setPatientPromptAddition] = React.useState(existing?.patient_prompt_addition || '');
|
| 34 |
+
const [savedAt, setSavedAt] = React.useState(existing?.saved_at || null);
|
| 35 |
|
| 36 |
const surveyorQuestionBankText = React.useMemo(() => {
|
| 37 |
const lines = (surveyorQuestionItems || [])
|
|
|
|
| 40 |
return lines.join('\n');
|
| 41 |
}, [surveyorQuestionItems]);
|
| 42 |
|
| 43 |
+
const addQuestion = () => {
|
| 44 |
const nextId = `q${String((surveyorQuestionItems || []).length + 1).padStart(2, '0')}`;
|
| 45 |
setSurveyorQuestionItems((prev) => ([...(prev || []), { id: nextId, text: '' }]));
|
| 46 |
};
|
|
|
|
| 49 |
setSurveyorQuestionItems((prev) => (prev || []).map((q) => (q.id === id ? Object.assign({}, q, { text }) : q)));
|
| 50 |
};
|
| 51 |
|
| 52 |
+
const removeQuestion = (id) => {
|
| 53 |
+
setSurveyorQuestionItems((prev) => (prev || []).filter((q) => q.id !== id));
|
| 54 |
+
};
|
| 55 |
+
|
| 56 |
+
const addAttribute = () => {
|
| 57 |
+
setSurveyorAttributeItems((prev) => ([...(prev || []), '' ]));
|
| 58 |
+
};
|
| 59 |
+
|
| 60 |
+
const updateAttribute = (idx, text) => {
|
| 61 |
+
setSurveyorAttributeItems((prev) => (prev || []).map((v, i) => (i === idx ? text : v)));
|
| 62 |
+
};
|
| 63 |
+
|
| 64 |
+
const removeAttribute = (idx) => {
|
| 65 |
+
setSurveyorAttributeItems((prev) => (prev || []).filter((_, i) => i !== idx));
|
| 66 |
+
};
|
| 67 |
|
| 68 |
React.useEffect(() => {
|
| 69 |
authedFetch('/api/personas')
|
|
|
|
| 77 |
.catch(() => {});
|
| 78 |
}, []);
|
| 79 |
|
| 80 |
+
const onSave = () => {
|
| 81 |
+
const cfg = {
|
| 82 |
+
surveyor_persona_id: selectedSurveyorId,
|
| 83 |
+
patient_persona_id: selectedPatientId,
|
| 84 |
+
surveyor_question_bank: surveyorQuestionBankText,
|
| 85 |
+
surveyor_question_bank_items: (surveyorQuestionItems || [])
|
| 86 |
+
.map((q) => ({ id: q.id, text: (q.text || '').trim() }))
|
| 87 |
+
.filter((q) => q.id && q.text.length > 0),
|
| 88 |
+
surveyor_attributes: (surveyorAttributeItems || [])
|
| 89 |
+
.map((x) => (typeof x === 'string' ? x.trim() : ''))
|
| 90 |
+
.filter((x) => x.length > 0),
|
| 91 |
+
surveyor_prompt_addition: surveyorPromptAddition,
|
| 92 |
+
patient_prompt_addition: patientPromptAddition,
|
| 93 |
+
saved_at: new Date().toISOString()
|
| 94 |
+
};
|
| 95 |
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(cfg));
|
| 96 |
+
setSavedAt(cfg.saved_at);
|
| 97 |
+
};
|
| 98 |
|
| 99 |
const TabNav = () => {
|
| 100 |
const base = "px-4 py-2 rounded-lg text-sm font-semibold border transition-colors";
|
|
|
|
| 126 |
|
| 127 |
<TabNav />
|
| 128 |
|
| 129 |
+
{activePane === 'surveyors' ? (
|
| 130 |
+
<div className="mt-6 grid grid-cols-2 gap-6">
|
| 131 |
+
<div>
|
| 132 |
+
{legacySurveyorKnobsPresent ? (
|
| 133 |
+
<div className="mb-4 rounded-lg border border-amber-300 bg-amber-50 px-4 py-3 text-sm text-amber-900">
|
| 134 |
+
Legacy surveyor knobs were removed. Please use “Attributes” instead and click “Save Configuration”.
|
| 135 |
+
</div>
|
| 136 |
+
) : null}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 137 |
|
| 138 |
+
<label className="block text-sm font-semibold text-slate-700 mb-2">Surveyor persona</label>
|
| 139 |
+
<select
|
| 140 |
+
className="w-full border border-slate-300 rounded-lg px-3 py-2 text-sm bg-white"
|
| 141 |
+
value={selectedSurveyorId}
|
| 142 |
+
onChange={(e) => setSelectedSurveyorId(e.target.value)}
|
| 143 |
+
>
|
| 144 |
+
{(personas.surveyors.length ? personas.surveyors : [{id: 'friendly_researcher_001', name: 'Alex Thompson'}]).map(p => (
|
| 145 |
+
<option key={p.id} value={p.id}>{p.name} ({p.id})</option>
|
| 146 |
+
))}
|
| 147 |
+
</select>
|
|
|
|
|
|
|
| 148 |
|
| 149 |
+
<div className="mt-4">
|
| 150 |
+
<label className="block text-sm font-semibold text-slate-700 mb-2">Question bank</label>
|
| 151 |
+
<div className="text-xs text-slate-500 mb-2">
|
| 152 |
+
Questions the surveyor should aim to cover during the session.
|
| 153 |
+
</div>
|
| 154 |
+
<div className="space-y-2">
|
| 155 |
+
{(surveyorQuestionItems || []).length === 0 ? (
|
| 156 |
+
<div className="text-sm text-slate-500">No questions yet.</div>
|
| 157 |
+
) : (
|
| 158 |
+
(surveyorQuestionItems || []).map((q) => (
|
| 159 |
+
<div key={q.id} className="flex items-start gap-2">
|
| 160 |
+
<div className="mt-2 text-xs font-mono text-slate-500 w-10 shrink-0">{q.id}</div>
|
| 161 |
+
<input
|
| 162 |
+
className="flex-1 border border-slate-300 rounded-lg px-3 py-2 text-sm bg-white"
|
| 163 |
+
value={q.text || ''}
|
| 164 |
+
onChange={(e) => updateQuestionText(q.id, e.target.value)}
|
| 165 |
+
placeholder="Type a question..."
|
| 166 |
+
/>
|
| 167 |
+
<button
|
| 168 |
+
type="button"
|
| 169 |
+
onClick={() => removeQuestion(q.id)}
|
| 170 |
+
className="text-slate-600 hover:text-red-600 px-2 py-2 text-sm"
|
| 171 |
+
title="Remove question"
|
| 172 |
+
>
|
| 173 |
+
✕
|
| 174 |
+
</button>
|
| 175 |
+
</div>
|
| 176 |
+
))
|
| 177 |
+
)}
|
| 178 |
+
<button
|
| 179 |
+
type="button"
|
| 180 |
+
onClick={addQuestion}
|
| 181 |
+
className="bg-slate-100 hover:bg-slate-200 text-slate-800 px-3 py-2 rounded-lg text-sm font-semibold transition-all border border-slate-300"
|
| 182 |
+
>
|
| 183 |
+
+ Add question
|
| 184 |
+
</button>
|
| 185 |
+
</div>
|
| 186 |
+
</div>
|
| 187 |
+
</div>
|
| 188 |
|
| 189 |
+
<div>
|
| 190 |
+
<div className="flex flex-col gap-4">
|
| 191 |
+
<div>
|
| 192 |
+
<label className="block text-sm font-semibold text-slate-700 mb-2">Attributes</label>
|
| 193 |
+
<div className="text-xs text-slate-500 mb-2">
|
| 194 |
+
Plain-language rules the surveyor should follow during the session.
|
| 195 |
+
</div>
|
| 196 |
+
<div className="space-y-2">
|
| 197 |
+
{(surveyorAttributeItems || []).length === 0 ? (
|
| 198 |
+
<div className="text-sm text-slate-500">No attributes yet.</div>
|
| 199 |
+
) : (
|
| 200 |
+
(surveyorAttributeItems || []).map((attr, idx) => (
|
| 201 |
+
<div key={idx} className="flex items-start gap-2">
|
| 202 |
+
<div className="mt-2 text-xs font-mono text-slate-500 w-10 shrink-0">{String(idx + 1).padStart(2, '0')}</div>
|
| 203 |
+
<input
|
| 204 |
+
className="flex-1 border border-slate-300 rounded-lg px-3 py-2 text-sm bg-white"
|
| 205 |
+
value={attr || ''}
|
| 206 |
+
onChange={(e) => updateAttribute(idx, e.target.value)}
|
| 207 |
+
placeholder="Type an attribute..."
|
| 208 |
+
/>
|
| 209 |
+
<button
|
| 210 |
+
type="button"
|
| 211 |
+
onClick={() => removeAttribute(idx)}
|
| 212 |
+
className="text-slate-600 hover:text-red-600 px-2 py-2 text-sm"
|
| 213 |
+
title="Remove attribute"
|
| 214 |
+
>
|
| 215 |
+
✕
|
| 216 |
+
</button>
|
| 217 |
+
</div>
|
| 218 |
+
))
|
| 219 |
+
)}
|
| 220 |
+
<button
|
| 221 |
+
type="button"
|
| 222 |
+
onClick={addAttribute}
|
| 223 |
+
className="bg-slate-100 hover:bg-slate-200 text-slate-800 px-3 py-2 rounded-lg text-sm font-semibold transition-all border border-slate-300"
|
| 224 |
+
>
|
| 225 |
+
+ Add attribute
|
| 226 |
+
</button>
|
| 227 |
+
</div>
|
| 228 |
+
</div>
|
| 229 |
|
| 230 |
+
<div>
|
| 231 |
+
<label className="block text-sm font-semibold text-slate-700 mb-2">Surveyor prompt addition (optional)</label>
|
| 232 |
+
<textarea
|
| 233 |
+
className="w-full border border-slate-300 rounded-lg px-3 py-2 text-sm bg-white h-40"
|
| 234 |
+
placeholder="Extra free-form instructions appended last (lowest priority)."
|
| 235 |
+
value={surveyorPromptAddition}
|
| 236 |
+
onChange={(e) => setSurveyorPromptAddition(e.target.value)}
|
| 237 |
+
/>
|
| 238 |
+
</div>
|
| 239 |
+
</div>
|
| 240 |
+
</div>
|
| 241 |
+
</div>
|
| 242 |
+
) : activePane === 'patients' ? (
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 243 |
<div className="mt-6 grid grid-cols-2 gap-6">
|
| 244 |
<div>
|
| 245 |
<label className="block text-sm font-semibold text-slate-700 mb-2">Patient persona</label>
|
frontend/pages/main_page.py
CHANGED
|
@@ -44,6 +44,13 @@ def get_main_page_html(auth_enabled: bool = False) -> str:
|
|
| 44 |
return lines.join('\n').trim();
|
| 45 |
}
|
| 46 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
function loadSessionToken() {
|
| 48 |
try {
|
| 49 |
const raw = localStorage.getItem(SESSION_KEY);
|
|
@@ -538,6 +545,7 @@ def get_main_page_html(auth_enabled: bool = False) -> str:
|
|
| 538 |
|
| 539 |
const cfg = loadConfig() || {};
|
| 540 |
const surveyorQuestionBank = getSurveyorQuestionBank(cfg);
|
|
|
|
| 541 |
const surveyorId = cfg.surveyor_persona_id || 'friendly_researcher_001';
|
| 542 |
const patientId = cfg.patient_persona_id || 'cooperative_senior_001';
|
| 543 |
|
|
@@ -548,9 +556,9 @@ def get_main_page_html(auth_enabled: bool = False) -> str:
|
|
| 548 |
surveyor_persona_id: surveyorId,
|
| 549 |
patient_persona_id: patientId,
|
| 550 |
surveyor_question_bank: surveyorQuestionBank || undefined,
|
|
|
|
| 551 |
surveyor_prompt_addition: cfg.surveyor_prompt_addition || undefined,
|
| 552 |
patient_prompt_addition: cfg.patient_prompt_addition || undefined,
|
| 553 |
-
surveyor_knobs: cfg.surveyor_knobs || undefined
|
| 554 |
}));
|
| 555 |
}
|
| 556 |
}, 500);
|
|
@@ -570,6 +578,7 @@ def get_main_page_html(auth_enabled: bool = False) -> str:
|
|
| 570 |
|
| 571 |
const cfg = loadConfig() || {};
|
| 572 |
const surveyorQuestionBank = getSurveyorQuestionBank(cfg);
|
|
|
|
| 573 |
const surveyorId = cfg.surveyor_persona_id || 'friendly_researcher_001';
|
| 574 |
const patientId = cfg.patient_persona_id || 'cooperative_senior_001';
|
| 575 |
|
|
@@ -580,9 +589,9 @@ def get_main_page_html(auth_enabled: bool = False) -> str:
|
|
| 580 |
surveyor_persona_id: surveyorId,
|
| 581 |
patient_persona_id: patientId,
|
| 582 |
surveyor_question_bank: surveyorQuestionBank || undefined,
|
|
|
|
| 583 |
surveyor_prompt_addition: cfg.surveyor_prompt_addition || undefined,
|
| 584 |
patient_prompt_addition: cfg.patient_prompt_addition || undefined,
|
| 585 |
-
surveyor_knobs: cfg.surveyor_knobs || undefined
|
| 586 |
}));
|
| 587 |
}
|
| 588 |
}, 500);
|
|
|
|
| 44 |
return lines.join('\n').trim();
|
| 45 |
}
|
| 46 |
|
| 47 |
+
function getSurveyorAttributes(cfg) {
|
| 48 |
+
const attrs = (cfg && Array.isArray(cfg.surveyor_attributes)) ? cfg.surveyor_attributes : [];
|
| 49 |
+
return attrs
|
| 50 |
+
.map((x) => (typeof x === 'string') ? x.trim() : '')
|
| 51 |
+
.filter((x) => x.length > 0);
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
function loadSessionToken() {
|
| 55 |
try {
|
| 56 |
const raw = localStorage.getItem(SESSION_KEY);
|
|
|
|
| 545 |
|
| 546 |
const cfg = loadConfig() || {};
|
| 547 |
const surveyorQuestionBank = getSurveyorQuestionBank(cfg);
|
| 548 |
+
const surveyorAttributes = getSurveyorAttributes(cfg);
|
| 549 |
const surveyorId = cfg.surveyor_persona_id || 'friendly_researcher_001';
|
| 550 |
const patientId = cfg.patient_persona_id || 'cooperative_senior_001';
|
| 551 |
|
|
|
|
| 556 |
surveyor_persona_id: surveyorId,
|
| 557 |
patient_persona_id: patientId,
|
| 558 |
surveyor_question_bank: surveyorQuestionBank || undefined,
|
| 559 |
+
surveyor_attributes: surveyorAttributes.length ? surveyorAttributes : undefined,
|
| 560 |
surveyor_prompt_addition: cfg.surveyor_prompt_addition || undefined,
|
| 561 |
patient_prompt_addition: cfg.patient_prompt_addition || undefined,
|
|
|
|
| 562 |
}));
|
| 563 |
}
|
| 564 |
}, 500);
|
|
|
|
| 578 |
|
| 579 |
const cfg = loadConfig() || {};
|
| 580 |
const surveyorQuestionBank = getSurveyorQuestionBank(cfg);
|
| 581 |
+
const surveyorAttributes = getSurveyorAttributes(cfg);
|
| 582 |
const surveyorId = cfg.surveyor_persona_id || 'friendly_researcher_001';
|
| 583 |
const patientId = cfg.patient_persona_id || 'cooperative_senior_001';
|
| 584 |
|
|
|
|
| 589 |
surveyor_persona_id: surveyorId,
|
| 590 |
patient_persona_id: patientId,
|
| 591 |
surveyor_question_bank: surveyorQuestionBank || undefined,
|
| 592 |
+
surveyor_attributes: surveyorAttributes.length ? surveyorAttributes : undefined,
|
| 593 |
surveyor_prompt_addition: cfg.surveyor_prompt_addition || undefined,
|
| 594 |
patient_prompt_addition: cfg.patient_prompt_addition || undefined,
|
|
|
|
| 595 |
}));
|
| 596 |
}
|
| 597 |
}, 500);
|
frontend/react_gradio_hybrid.py
CHANGED
|
@@ -165,9 +165,9 @@ async def frontend_websocket(websocket: WebSocket, conversation_id: str):
|
|
| 165 |
"surveyor_persona_id": data.get("surveyor_persona_id", "friendly_researcher_001"),
|
| 166 |
"patient_persona_id": data.get("patient_persona_id", "cooperative_senior_001"),
|
| 167 |
"surveyor_question_bank": data.get("surveyor_question_bank"),
|
|
|
|
| 168 |
"surveyor_prompt_addition": data.get("surveyor_prompt_addition"),
|
| 169 |
"patient_prompt_addition": data.get("patient_prompt_addition"),
|
| 170 |
-
"surveyor_knobs": data.get("surveyor_knobs"),
|
| 171 |
"host": settings.llm.host,
|
| 172 |
"model": settings.llm.model,
|
| 173 |
})
|
|
@@ -189,9 +189,9 @@ async def frontend_websocket(websocket: WebSocket, conversation_id: str):
|
|
| 189 |
"surveyor_persona_id": data.get("surveyor_persona_id", "friendly_researcher_001"),
|
| 190 |
"patient_persona_id": data.get("patient_persona_id", "cooperative_senior_001"),
|
| 191 |
"surveyor_question_bank": data.get("surveyor_question_bank"),
|
|
|
|
| 192 |
"surveyor_prompt_addition": data.get("surveyor_prompt_addition"),
|
| 193 |
"patient_prompt_addition": data.get("patient_prompt_addition"),
|
| 194 |
-
"surveyor_knobs": data.get("surveyor_knobs"),
|
| 195 |
"host": settings.llm.host,
|
| 196 |
"model": settings.llm.model,
|
| 197 |
})
|
|
|
|
| 165 |
"surveyor_persona_id": data.get("surveyor_persona_id", "friendly_researcher_001"),
|
| 166 |
"patient_persona_id": data.get("patient_persona_id", "cooperative_senior_001"),
|
| 167 |
"surveyor_question_bank": data.get("surveyor_question_bank"),
|
| 168 |
+
"surveyor_attributes": data.get("surveyor_attributes"),
|
| 169 |
"surveyor_prompt_addition": data.get("surveyor_prompt_addition"),
|
| 170 |
"patient_prompt_addition": data.get("patient_prompt_addition"),
|
|
|
|
| 171 |
"host": settings.llm.host,
|
| 172 |
"model": settings.llm.model,
|
| 173 |
})
|
|
|
|
| 189 |
"surveyor_persona_id": data.get("surveyor_persona_id", "friendly_researcher_001"),
|
| 190 |
"patient_persona_id": data.get("patient_persona_id", "cooperative_senior_001"),
|
| 191 |
"surveyor_question_bank": data.get("surveyor_question_bank"),
|
| 192 |
+
"surveyor_attributes": data.get("surveyor_attributes"),
|
| 193 |
"surveyor_prompt_addition": data.get("surveyor_prompt_addition"),
|
| 194 |
"patient_prompt_addition": data.get("patient_prompt_addition"),
|
|
|
|
| 195 |
"host": settings.llm.host,
|
| 196 |
"model": settings.llm.model,
|
| 197 |
})
|