Imagine: a medical student on a reception — the patient complains of back pain but omits a recent fall. A scripted trainer won't catch the trick, but an AI character 'remembers' the missed question and flags the error at the end of the session. We build exactly such systems — with realistic character psychology and an adaptive scenario for each student. This is not just a dialogue simulation, but a full-fledged situational task where every answer affects the scenario's development. According to IBM Training Report research, companies using AI simulations reduce training time by 40%.
How AI simulations solve the problem of scalable training?
Classic role-playing games require actors and scriptwriters — expensive, and replaying gives the same answer. AI simulations generate a unique dialogue every time: the character reacts to specific words, changes emotions and tactics. At the core is an LLM, framed by a system prompt with a role, goals, and secret information. The code below shows a minimal implementation of such a character.
from openai import AsyncOpenAI
from dataclasses import dataclass, field
from typing import Optional
client = AsyncOpenAI()
@dataclass
class SimulationCharacter:
name: str
role: str
personality: str
objectives: list[str] # Что персонаж хочет добиться
knowledge: str # Что персонаж знает
emotional_state: str = "neutral"
secret_info: str = "" # Информация, которую персонаж скрывает
@dataclass
class SimulationScenario:
title: str
learning_objectives: list[str]
characters: list[SimulationCharacter]
context: str
success_criteria: list[str]
difficulty: str = "medium"
@dataclass
class SimulationSession:
scenario: SimulationScenario
conversation_history: list[dict] = field(default_factory=list)
score: float = 0.0
attempts: int = 0
feedback_notes: list[str] = field(default_factory=list)
class InteractiveSimulator:
def __init__(self):
self.client = AsyncOpenAI()
async def create_character_response(
self,
session: SimulationSession,
learner_input: str,
character: SimulationCharacter
) -> dict:
"""Генерируем реалистичный ответ персонажа + оценку действий ученика"""
system_prompt = f"""Ты — {character.name}, {character.role}.
Личность: {character.personality}
Твои цели в этой ситуации: {', '.join(character.objectives)}
Контекст сценария: {session.scenario.context}
Информация, которую ты знаешь: {character.knowledge}
{'Скрытая информация (не раскрывать явно): ' + character.secret_info if character.secret_info else ''}
Текущее эмоциональное состояние: {character.emotional_state}
ВАЖНО:
- Отвечай от лица персонажа, реалистично
- Реагируй на тактику ученика: хорошие аргументы смягчают позицию, давление усиливает сопротивление
- После ответа добавь блок [INSTRUCTOR_EVAL] с оценкой действий ученика (не показывается ему)
Верни JSON: {{
character_response: "ответ персонажа",
emotional_state_change: "как изменилось настроение",
instructor_eval: {{
technique_used: "...", effective: true/false, score_delta: -5..+10, tip: "..."
}}
}}"""
messages = [{"role": "system", "content": system_prompt}]
# История диалога
for turn in session.conversation_history[-10:]: # последние 10 реплик
messages.append({"role": turn["role"], "content": turn["content"]})
messages.append({"role": "user", "content": learner_input})
response = await self.client.chat.completions.create(
model="gpt-4o",
messages=messages,
response_format={"type": "json_object"}
)
return json.loads(response.choices[0].message.content)
async def evaluate_session(self, session: SimulationSession) -> dict:
"""Финальная оценка сессии симуляции"""
response = await self.client.chat.completions.create(
model="gpt-4o",
messages=[{
"role": "system",
"content": f"""Оцени результаты обучающей симуляции.
Цели обучения: {json.dumps(session.scenario.learning_objectives, ensure_ascii=False)}
Критерии успеха: {json.dumps(session.scenario.success_criteria, ensure_ascii=False)}
Проанализируй диалог и верни JSON:
{{
overall_score: 0-100,
objectives_achieved: [{{"objective": "...", "achieved": true/false, "evidence": "..."}}],
strengths: ["..."],
areas_for_improvement: ["..."],
specific_feedback: "подробный разбор ключевых моментов",
recommended_practice: "что отработать дополнительно"
}}"""
}, {
"role": "user",
"content": f"История диалога:\n{json.dumps(session.conversation_history, ensure_ascii=False, indent=2)}"
}],
response_format={"type": "json_object"}
)
return json.loads(response.choices[0].message.content)
Why is LLM-based dialogue generation better than scripted trees?
Scripted trees are finite: any unexpected input breaks the scenario, and the student gets 'I didn't understand'. LLM generation covers an infinite input space — the character can adequately respond to 'Are you sure?', 'Show me the research', or even 'Let's discuss a discount'. We use JSON mode (gpt-4o), which guarantees a structured output with action evaluation — no text parsing needed.
| Characteristic | Scripted simulations | AI simulations on LLM |
|---|---|---|
| Number of possible dialogues | 5–20 (limited by branches) | Theoretically infinite |
| Reaction to non-standard input | Error or 'I didn't understand' | Appropriate response within role |
| Emotional adaptation | No | Yes (via prompt and history) |
| Creation complexity | Low (diagrams) | Medium (prompts + testing) |
| Cost per session | Fixed ($200-$500 per role-play) | ~2-10 cents (tokens) |
For a character with long-term memory, we connect RAG with ChromaDB: key facts from the dialogue history are stored in a vector database and retrieved in subsequent encounters. This allows the simulation to remember the student's decisions across multiple sessions — critical for skill assessment and progress tracking.
Ready-made simulations by niche
SIMULATION_TEMPLATES = {
"sales_objection_handling": SimulationScenario(
title="Handling objections: 'It's expensive'",
learning_objectives=["Identify the true objection", "Justify the value", "Propose alternatives"],
characters=[SimulationCharacter(
name="Mikhail Ivanov",
role="Potential client, procurement department head",
personality="Pragmatic, values specifics, skeptical of salespeople",
objectives=["Get the best price", "Ensure supplier reliability"],
knowledge="Knows the market, compared competitors",
emotional_state="slightly_negative",
secret_info="Has budget but wants to test the seller's flexibility"
)],
context="Final stage of negotiations for an annual IT solution contract",
success_criteria=["Identified budget constraints", "Presented ROI calculation", "Didn't reduce price by more than 10%"]
),
"medical_consultation": SimulationScenario(
title="Primary consultation for a patient with back pain",
learning_objectives=["Take history", "Perform differential diagnosis", "Order examinations"],
characters=[SimulationCharacter(
name="Patient: Elena Smirnova, 42 years old",
role="Patient with lower back pain for 2 weeks",
personality="Anxious, has read a lot online about diagnoses",
objectives=["Get a specific diagnosis", "Find out if surgery is needed"],
knowledge="Pain worsens when bending, numbness in toe",
secret_info="Fell at work but is embarrassed to say"
)],
context="Primary consultation with a neurologist at a clinic",
success_criteria=["Asked about injuries", "Ordered MRI", "Explained next steps"]
)
}
Adaptive difficulty
async def adjust_difficulty(
session: SimulationSession,
current_score: float
) -> str:
"""Адаптируем поведение персонажа под уровень ученика"""
if current_score > 75:
return "more_resistant" # Персонаж жёстче
elif current_score < 40:
return "more_cooperative" # Персонаж мягче, даёт подсказки
else:
return "neutral" # Стандартное поведение
Adaptive difficulty is implemented by changing character parameters: resistance level, amount of hints, frequency of emotion changes. This allows using one scenario for both beginners and experienced employees — development savings reach 80%.
How is simulation effectiveness evaluated?
We implement metrics at each stage:
| Metric | Description | Target |
|---|---|---|
| completeness | Proportion of scenario objectives achieved | >80% |
| score | LLM evaluation of tactics and results | 0-100 |
| user_satisfaction | Post-session survey | >4.0 out of 5 |
| retention | Repeat session after one month | >60% |
In one project, an A/B test showed a 34% improvement in retention compared to a scripted trainer. Over 10,000 sessions were processed monthly with 99.9% uptime.
Checklist for launching your first simulation
- Define learning goals and target audience
- Write a Character card: role, personality, secret information
- Set up the system prompt with role and evaluation criteria
- Test 50+ dialogues for JSON correctness and latency
- Run an A/B test: control group on script, test group on AI
Our process and what's included
- Analysis: we break down learning goals, target audience, typical mistakes. Create a map of scenarios and characters.
- Design: write Character cards (role, personality, success metrics). Set up system prompts — this determines 80% of simulation quality.
- Implementation: build the backend on Python (FastAPI + asyncio), connect the LLM, version prompts via MLflow. For cheap scenarios we use Llama 3 via vLLM, for complex ones — GPT-4o.
- Testing: run 50+ dialogues per scenario, check JSON correctness, measure latency. Automatically generate robust tests.
- Deployment: containerize and deploy in Kubernetes, connect analytics (which sessions succeeded, where students get stuck).
Note: what's included in the result: documentation on scenarios, API specification, embeddable web component, analytics dashboard, 2 weeks of post-deployment support. Contact us — we'll evaluate your scenario in one day. We implement a turnkey simulation with 1-3 characters in a month.
Timeline: MVP for one scenario with one AI character — 2-3 weeks. Platform with a library of scenarios, analytics, and LMS integration — 2-3 months. Over 5 years of experience in AI solutions, 10+ implemented trainers for medical and sales departments. Contact us to discuss your training scenario — we'll propose the architecture and timeline for free.







