> For the complete documentation index, see [llms.txt](https://docs.convai.com/api-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.convai.com/api-docs/zh/cha-jian-yu-ji-cheng/unity-plugin-beta-overview/dynamic-context/use-cases.md).

# 用例

## 初学者

### 个性化对话

最常见的用法：在任何对话开始前，告诉角色它正在和谁交流。

**场景：** 玩家创建了一个角色并进入世界。NPC 应该用名字称呼他们，并识别他们的职业。

```csharp
using System.Collections;
using Convai.Runtime.Components;
using Convai.Runtime.Room;
using UnityEngine;

/// <summary>
/// 在会话开始时将玩家身份注入到活动的 Convai 会话中。
/// 角色在第一次交流之前会收到这些上下文，并且可以
/// 无需在控制台重新配置，就直接用玩家的名字和职业打招呼。
/// </summary>
public class PlayerGreeting : MonoBehaviour
{
    [SerializeField] private string _playerName = "Aria";
    [SerializeField] private string _playerClass = "游侠";

    private IConvaiRoomConnectionService _connectionService;

    private IEnumerator Start()
    {
        while (true)
        {
            ConvaiManager manager = ConvaiManager.ActiveManager;
            if (manager != null && manager.TryGetRoomConnectionService(out _connectionService))
                break;
            yield return null;
        }

        if (_connectionService.IsConnected)
            SendGreeting();
        else
            _connectionService.Connected += OnConnected;
    }

    private void OnConnected()
    {
        _connectionService.Connected -= OnConnected;
        SendGreeting();
    }

    private void SendGreeting()
    {
        string context =
            $"你正在与 {_playerName} 交谈，他/她是一名 1 级 {_playerClass}。 " +
            "热情地问候他们，并欢迎他们来到这个世界。";

        // replace — 确保不会沿用上一次会话中的旧上下文。
        // runLlm: true — 会触发即时的问候回复。
        bool sent = _connectionService.UpdateDynamicContext(context, mode: "replace", runLlm: "true");
        if (!sent)
            Debug.LogWarning("[PlayerGreeting] UpdateDynamicContext 返回 false。");
    }

    private void OnDestroy()
    {
        if (_connectionService != null)
            _connectionService.Connected -= OnConnected;
    }
}
```

`替换` 确保不会沿用上一次会话中的旧上下文。 `runLlm: "true"` 会触发即时的问候回复。

***

### 注入学习者档案

在会话开始时，推送学习者的姓名、角色和当前课程。会话中的每个角色都会立即个性化，无需重新配置控制台。

```csharp
using System.Collections;
using Convai.Runtime.Components;
using Convai.Runtime.Room;
using UnityEngine;

/// <summary>
/// 在会话开始时将当前学习者的档案注入到 Convai 会话中。
/// 无需重新配置控制台即可个性化会话中的每个角色。
/// </summary>
public class LearnerProfileInjector : MonoBehaviour
{
    private IConvaiRoomConnectionService _connectionService;

    private IEnumerator Start()
    {
        while (true)
        {
            ConvaiManager manager = ConvaiManager.ActiveManager;
            if (manager != null && manager.TryGetRoomConnectionService(out _connectionService))
                break;
            yield return null;
        }

        if (_connectionService.IsConnected)
            InjectProfile();
        else
            _connectionService.Connected += OnConnected;
    }

    private void OnConnected()
    {
        _connectionService.Connected -= OnConnected;
        InjectProfile();
    }

    private void InjectProfile()
    {
        LearnerProfile profile = LearnerProfileService.Current;
        if (profile == null)
        {
            Debug.LogWarning("[LearnerProfileInjector] 未找到活动的学习者档案。");
            return;
        }

        string context =
            $"你正在与 {profile.FullName} 交谈，他/她是一名 {profile.JobTitle} " +
            $"，隶属于 {profile.Department} 部门。 " +
            $"他们是一名 {profile.ExperienceLevel} 级学习者，正在完成 " +
            $"'{profile.CurrentCourseName}' 课程。 " +
            $"他们声明的学习目标：{profile.LearningGoal}。 " +
            "请用名字称呼他们，并根据其经验水平调整讲解。";

        // replace — 建立干净的会话基线。
        // runLlm: false — 档案是背景上下文，不是立即回复的提示。
        bool sent = _connectionService.UpdateDynamicContext(context, mode: "replace", runLlm: "false");
        if (!sent)
            Debug.LogWarning("[LearnerProfileInjector] UpdateDynamicContext 返回 false。");
    }

    private void OnDestroy()
    {
        if (_connectionService != null)
            _connectionService.Connected -= OnConnected;
    }
}
```

***

### 模块推进

随着受训者推进到新模块，将上下文替换为该模块的目标。角色会立即知道应该关注什么——无需重新配置。

```csharp
using Convai.Runtime.Room;
using UnityEngine;

/// <summary>
/// 当受训者推进到新的训练模块时，更新角色的上下文。
/// 角色的关注点会完全转向新模块——无需重新配置。
/// </summary>
public class TrainingModuleController : MonoBehaviour
{
    private IConvaiRoomConnectionService _connectionService;

    /// <summary>
    /// 使用活动的房间连接服务初始化此组件。
    /// 必须在 Convai 会话建立后由会话所有者调用。
    /// </summary>
    public void Initialize(IConvaiRoomConnectionService connectionService)
    {
        _connectionService = connectionService;
    }

    /// <summary>
    /// 用给定模块的目标和概念替换角色的模块上下文。
    /// </summary>
    public void AdvanceToModule(TrainingModule module)
    {
        string context =
            $"学习者现在开始第 {module.Number} 模块：'{module.Title}'。 " +
            $"学习目标：{string.Join(\"; \", module.Objectives)}。 " +
            $"关键概念：{string.Join(\", \", module.KeyConcepts)}。 " +
            $"预计时长：{module.EstimatedMinutes} 分钟。 " +
            "所有引导和问题都只聚焦于本模块内容。";

        // replace — 之前的模块上下文已不再相关。
        bool sent = _connectionService?.UpdateDynamicContext(context, mode: "replace", runLlm: "false") ?? false;
        if (!sent)
            Debug.LogWarning($"[TrainingModuleController] 未能推进到模块 '{module.Title}'。");
    }
}
```

***

### 语言导师熟练度等级

语言学习导师会根据学习者当前的熟练度水平调整词汇、句子复杂度和纠错方式。只需在会话开始时调用一次——或者在等级发生变化时调用。

```csharp
using Convai.Runtime.Room;
using UnityEngine;

/// <summary>
/// 根据学习者的 CEFR 等级设置语言导师的词汇和纠错方式。
/// 在会话开始时以及等级变化时调用（例如升级评估之后）。
/// </summary>
public class LanguageTutorLevelManager : MonoBehaviour
{
    private IConvaiRoomConnectionService _connectionService;

    /// <summary>
    /// 使用活动的房间连接服务初始化此组件。
    /// 必须在 Convai 会话建立后由会话所有者调用。
    /// </summary>
    public void Initialize(IConvaiRoomConnectionService connectionService)
    {
        _connectionService = connectionService;
    }

    /// <param name="cefrLevel">CEFR 熟练度等级：A1、A2、B1、B2、C1 或 C2。</param>
    /// <param name="nativeLanguage">学习者的母语。</param>
    /// <param name="targetLanguage">学习者正在学习的语言。</param>
    public void SetProficiencyLevel(string cefrLevel, string nativeLanguage, string targetLanguage)
    {
        string context =
            $"学习者的母语是 {nativeLanguage}。 " +
            $"他们正在以 CEFR {cefrLevel} 级学习 {targetLanguage}。 " +
            GetLevelInstructions(cefrLevel);

        bool sent = _connectionService?.UpdateDynamicContext(context, mode: "replace", runLlm: "false") ?? false;
        if (!sent)
            Debug.LogWarning($"[LanguageTutorLevelManager] 未能设置熟练度等级 '{cefrLevel}'。");
    }

    private static string GetLevelInstructions(string level) => level switch
    {
        "A1" or "A2" =>
            "仅使用简单的现在时和过去时。每句话少于十个词。 " +
            "避免使用习语。重复关键词汇。通过示范正确形式来温和纠正错误。",

        "B1" or "B2" =>
            "使用多样的句式并引入常见习语。 " +
            "通过改写温和地纠正语法错误。鼓励更长的回答。",

        "C1" or "C2" =>
            "以自然语速使用细腻的词汇和习语表达。 " +
            "只纠正重要错误。参与抽象或基于观点的讨论。",

        _ => "根据学习者明显的理解水平进行调整。"
    };
}
```

### 注入情绪状态

动态上下文不限于事实。普通语言指令同样可以用于即时塑造角色的语气和行为。

**场景：** 伙伴 NPC 的情绪应根据场景中的情况而变化——战斗前平静，战斗中紧张，战斗后释然。

```csharp
using Convai.Runtime.Room;
using UnityEngine;

/// <summary>
/// 在运行时控制伙伴 NPC 的情绪状态和行为语气。
/// 使用普通语言指令——不需要单独的情绪管线。
/// 新语气会在下一次交流中自然显现；不会触发即时回复。
/// </summary>
public class CompanionMoodController : MonoBehaviour
{
    private IConvaiRoomConnectionService _connectionService;

    /// <summary>
    /// 使用活动的房间连接服务初始化此组件。
    /// 必须在 Convai 会话建立后由会话所有者调用。
    /// </summary>
    public void Initialize(IConvaiRoomConnectionService connectionService)
    {
        _connectionService = connectionService;
    }

    /// <summary>
    /// 用给定指令替换角色当前的情绪状态。
    /// </summary>
    /// <param name="moodInstruction">
    /// 一条用普通语言写成的行为指令。
    /// 例如：“你感到附近有危险。说话时要紧迫，并保持回复简短。”
    /// </param>
    public void SetMood(string moodInstruction)
    {
        // replace — 当前情绪会替代之前的情绪；状态不会累积。
        // runLlm: false — 情绪是背景上下文；角色不应无提示地作出反应。
        bool sent = _connectionService?.UpdateDynamicContext(
            moodInstruction,
            mode: "replace",
            runLlm: "false") ?? false;

        if (!sent)
            Debug.LogWarning("[CompanionMoodController] UpdateDynamicContext 返回 false。");
    }
}

// 用法：
// _moodController.SetMood("你感到附近有危险。说话时要紧迫，并保持回复简短。");
// _moodController.SetMood("威胁已经过去。你感到放松，但仍保持警惕。");
```

`runLlm: "false"` 会静默更新角色的情绪倾向——新的语气会在下一次交流中自然显现，而不会触发不请自来的评论。

***

## 中级

### 响应式游戏状态

随着玩家推进，世界也会发生变化。动态上下文让这些变化能够实时传达给角色。

**场景：** 商人 NPC 应始终知道玩家当前的金币、最近发生的事件以及已完成的任务。

```csharp
using Convai.Runtime.Room;
using UnityEngine;

/// <summary>
/// 将商人 NPC 的上下文与玩家的实时游戏状态同步。
/// 被动状态变化（金币、世界事件）会静默发送。
/// 重大事件（任务完成）会触发角色的即时反应。
/// </summary>
public class ShopkeeperContextUpdater : MonoBehaviour
{
    private IConvaiRoomConnectionService _connectionService;

    private void Awake()
    {
        PlayerInventory.OnGoldChanged += OnGoldChanged;
        QuestManager.OnQuestCompleted += OnQuestCompleted;
        WorldEvents.OnMajorEventOccurred += OnWorldEvent;
    }

    private void OnDestroy()
    {
        PlayerInventory.OnGoldChanged -= OnGoldChanged;
        QuestManager.OnQuestCompleted -= OnQuestCompleted;
        WorldEvents.OnMajorEventOccurred -= OnWorldEvent;
    }

    /// <summary>
    /// 初始化此组件并推送一份初始状态快照。
    /// 必须在 Convai 会话建立后由会话所有者调用。
    /// </summary>
    public void Initialize(IConvaiRoomConnectionService connectionService)
    {
        if (connectionService == null)
        {
            Debug.LogError("[ShopkeeperContextUpdater] connectionService 不能为空。");
            return;
        }

        _connectionService = connectionService;

        // 推送初始快照——替换上一次会话中的任何上下文。
        string snapshot =
            $"玩家金币：{PlayerInventory.Gold}g。 " +
            $"已完成任务：{string.Join(\", \", QuestManager.CompletedQuestNames)}。 " +
            $"商人公会声望：{PlayerStats.MerchantReputation}/100。";

        bool sent = _connectionService.UpdateDynamicContext(snapshot, mode: "replace", runLlm: "false");
        if (!sent)
            Debug.LogWarning("[ShopkeeperContextUpdater] 未能发送初始快照。");
    }

    private void OnGoldChanged(int newGold)
    {
        // 静默追加——金币数量会影响未来回复，但不会触发不请自来的评论。
        bool sent = _connectionService?.UpdateDynamicContext(
            $"玩家金币已更新：{newGold}g。",
            mode: "append",
            runLlm: "false") ?? false;

        if (!sent)
            Debug.LogWarning("[ShopkeeperContextUpdater] 未能更新金币上下文。");
    }

    private void OnQuestCompleted(string questName)
    {
        // runLlm: true — 玩家带着已完成的任务回来，理应触发反应。
        bool sent = _connectionService?.UpdateDynamicContext(
            $"玩家刚完成了 '{questName}'，正回来领取奖励。",
            mode: "append",
            runLlm: "true") ?? false;

        if (!sent)
            Debug.LogWarning("[ShopkeeperContextUpdater] 未能发送任务完成上下文。");
    }

    private void OnWorldEvent(string eventDescription)
    {
        // 静默追加——世界新闻会丰富未来的对话，而不会打断当前流程。
        bool sent = _connectionService?.UpdateDynamicContext(
            $"世界事件：{eventDescription}。",
            mode: "append",
            runLlm: "false") ?? false;

        if (!sent)
            Debug.LogWarning("[ShopkeeperContextUpdater] 未能发送世界事件上下文。");
    }
}
```

被动状态（金钱、世界新闻）使用 `runLlm: "false"` ——它会影响未来回复，而不会触发不请自来的评论。任务完成使用 `runLlm: "true"` 因为此时预期会有反应。

***

### 静默事件累积

在高频事件场景中——快速掉落、连杀、奖励触发——对每个事件都作出反应会产生噪音。正确的模式是静默累积，只在发生有意义的事情时，或者用户发起对话时，再进行回应。

**场景：** 一位战斗解说员会在整场战斗中跟踪击杀事件。角色在行动期间保持安静，但当玩家最终开口时，对刚刚发生的一切都了如指掌。

```csharp
using Convai.Runtime.Room;
using UnityEngine;

/// <summary>
/// 在角色的上下文中静默累积战斗事件。
/// 角色在行动期间保持安静，但对发生的一切都了如指掌
///。只有当事件累积到有意义的一组时，或者
/// 战斗阶段自然结束时，它才会发言。
/// </summary>
public class CombatEventAccumulator : MonoBehaviour
{
    [SerializeField]
    [Tooltip("在触发角色回复前要累积的事件数量。")]
    private int _responseThreshold = 5;

    private IConvaiRoomConnectionService _connectionService;
    private int _pendingEventCount;

    /// <summary>
    /// 使用活动的房间连接服务初始化此组件。
    /// 必须在 Convai 会话建立后由会话所有者调用。
    /// </summary>
    public void Initialize(IConvaiRoomConnectionService connectionService)
    {
        if (connectionService == null)
        {
            Debug.LogError("[CombatEventAccumulator] connectionService 不能为空。");
            return;
        }
        _connectionService = connectionService;
    }

    /// <summary>
    /// 记录一次击杀事件。直到超过回复阈值之前都会静默累积。
    /// </summary>
    public void OnEnemyDefeated(string enemyName, string weaponUsed)
    {
        _pendingEventCount++;

        // 静默累积——角色知道发生了什么，但不会打断行动。
        _connectionService?.UpdateDynamicContext(
            $"玩家使用 {weaponUsed} 击败了 {enemyName}。",
            mode: "append",
            runLlm: "false");

        if (_pendingEventCount < _responseThreshold)
            return;

        // 超过阈值——有意义的一组事件值得被确认。
        bool sent = _connectionService?.UpdateDynamicContext(
            "玩家已经连续击杀了一段时间。简要肯定他们的表现。",
            mode: "append",
            runLlm: "true") ?? false;

        if (!sent)
            Debug.LogWarning("[CombatEventAccumulator] 未能触发连杀响应。");

        _pendingEventCount = 0;
    }

    /// <summary>
    /// 在战斗阶段结束时清空所有剩余的累积事件。
    /// </summary>
    public void OnCombatPhaseEnded()
    {
        if (_pendingEventCount == 0) return;

        bool sent = _connectionService?.UpdateDynamicContext(
            "战斗阶段结束。总结刚刚发生的事情，并给出简要评估。",
            mode: "append",
            runLlm: "true") ?? false;

        if (!sent)
            Debug.LogWarning("[CombatEventAccumulator] 未能触发阶段结束响应。");

        _pendingEventCount = 0;
    }
}
```

这能让角色的介入更有意义。它对事件流有完整认知，但只在恰当时刻发言，而不是对每个触发都作出反应。

***

### 由表现驱动的辅导难度

一个销售培训模拟器会跟踪受训者的实时表现分数。当分数变化到足以跨越一个难度层级时，角色的辅导策略会在会话中途更新——无需重启。

```csharp
using Convai.Runtime.Room;
using UnityEngine;

/// <summary>
/// 根据
/// 受训者的表现分数实时调整 AI 销售培训师的辅导难度。当分数跨越层级边界时，
/// 角色的策略会在会话中途更新——无需重启。
/// </summary>
public class SalesTrainerPerformanceAdapter : MonoBehaviour
{
    [SerializeField] private float _easyThreshold = 75f;
    [SerializeField] private float _hardThreshold = 40f;

    private IConvaiRoomConnectionService _connectionService;
    private string _currentDifficultyTier = "normal";

    /// <summary>
    /// 使用活动的房间连接服务初始化此组件。
    /// 必须在 Convai 会话建立后由会话所有者调用。
    /// </summary>
    public void Initialize(IConvaiRoomConnectionService connectionService)
    {
        if (connectionService == null)
        {
            Debug.LogError("[SalesTrainerPerformanceAdapter] connectionService 不能为空。");
            return;
        }
        _connectionService = connectionService;
    }

    /// <summary>
    /// 评估受训者当前分数，并在
    /// 难度层级变化或识别出新的技能缺口时更新辅导策略。
    /// </summary>
    /// <param name="newScore">当前表现分数（0–100）。</param>
    /// <param name="lastMissedSkill">最近识别出的技能缺口；如果没有则为 null。</param>
    public void OnPerformanceScoreUpdated(float newScore, string lastMissedSkill)
    {
        string newTier = newScore >= _easyThreshold ? "easy"
            : newScore <= _hardThreshold ? "hard"
            : "normal";

        bool tierChanged = newTier != _currentDifficultyTier;
        bool hasNewSkillGap = !string.IsNullOrEmpty(lastMissedSkill);

        // 仅当有意义的变化发生时才推送更新。
        if (!tierChanged && !hasNewSkillGap)
            return;

        _currentDifficultyTier = newTier;

        string difficultyInstruction = newTier switch
        {
            "easy" => "受训者表现良好。提高异议复杂度。引入边缘案例。",
            "hard" => "受训者表现吃力。简化异议。给出对话提示。保持鼓励。",
            _ => "维持当前难度。保持异议的真实感。"
        };

        string context = $"受训者表现评分：{newScore:F0}/100。 {difficultyInstruction}";

        if (hasNewSkillGap)
            context += $" 受训者一直遗漏：{lastMissedSkill}。巧妙地探查这一领域。";

        // replace — 新的难度状态会完全取代之前的状态。
        bool sent = _connectionService?.UpdateDynamicContext(context, mode: "replace", runLlm: "false") ?? false;
        if (!sent)
            Debug.LogWarning("[SalesTrainerPerformanceAdapter] 未能更新辅导上下文。");
    }
}
```

***

### 分支式合规情景状态

一种合规培训模拟，其中每个受训者决策都会推进一个场景分支。AI 同事 NPC 始终完全了解做出了哪些决定、这些决定的后果，以及累积的合规评分。

```csharp
using System.Collections.Generic;
using Convai.Runtime.Room;
using UnityEngine;

/// <summary>
/// 跟踪分支式合规情景中的受训者决策。
/// AI 同事 NPC 始终完全了解当前场景分支、
/// 累积的决策历史和合规评分——无需在
/// 决策之间重新配置。
/// </summary>
public class ComplianceScenarioTracker : MonoBehaviour
{
    private IConvaiRoomConnectionService _connectionService;
    private readonly List<string> _decisionLog = new();
    private string _currentBranch;
    private int _complianceScore = 100;

    /// <summary>
    /// 使用活动的房间连接服务初始化此组件。
    /// 必须在 Convai 会话建立后由会话所有者调用。
    /// </summary>
    public void Initialize(IConvaiRoomConnectionService connectionService)
    {
        if (connectionService == null)
        {
            Debug.LogError("[ComplianceScenarioTracker] connectionService 不能为空。");
            return;
        }
        _connectionService = connectionService;
    }

    /// <summary>
    /// 记录受训者的一个决策，推进场景分支，并推送
    /// 更新后的状态。影响低于 -20 的会被视为严重错误，并触发
    /// 角色的即时反应。
    /// </summary>
    public void OnTraineeDecision(ScenarioDecision decision)
    {
        _complianceScore += decision.ComplianceImpact;
        _currentBranch = decision.ResultingBranchName;
        _decisionLog.Add($"'{decision.Description}' ({decision.ComplianceImpact:+#;-#;0} 分)");

        bool isError = decision.ComplianceImpact < 0;
        bool isCriticalError = decision.ComplianceImpact < -20;

        string context =
            $"当前场景分支：{_currentBranch}。 " +
            $"受训者合规评分：{_complianceScore}/100。 " +
            $"决策历史：{string.Join(\" → \", _decisionLog)}。 " +
            $"最近一次决策后果：{decision.ConsequenceDescription}。 " +
            (isError
                ? "受训者犯了合规错误。自然地提及它——不要说教。"
                : "受训者做出了正确的合规决策。肯定他们的判断。");

        // replace — 每次决策都会用完整的场景状态替换之前的快照。
        bool sent = _connectionService?.UpdateDynamicContext(
            context,
            mode: "replace",
            runLlm: isCriticalError ? "true" : "false") ?? false;

        if (!sent)
            Debug.LogWarning("[ComplianceScenarioTracker] 未能更新场景上下文。");
    }

    /// <summary>
    /// 标记场景完成，并触发角色进行结构化复盘。
    /// </summary>
    public void OnScenarioCompleted()
    {
        string debrief =
            $"情景已完成。最终合规评分：{_complianceScore}/100。 " +
            $"请给出简短复盘：肯定优势，指出最重要的错误，" +
            $"并给出一条可执行的建议。 " +
            $"决策日志：{string.Join(\"; \", _decisionLog)}。";

        bool sent = _connectionService?.UpdateDynamicContext(debrief, mode: "append", runLlm: "true") ?? false;
        if (!sent)
            Debug.LogWarning("[ComplianceScenarioTracker] 未能发送场景复盘。");
    }
}
```

***

### 医疗模拟患者生命体征监测

一种临床培训模拟，患者 NPC 的生命体征会实时变化。角色会静默吸收常规波动，但当数值跨越具有临床意义的阈值时会升级响应——而不会提前泄露诊断结果给受训者。

```csharp
using System.Collections.Generic;
using Convai.Runtime.Room;
using UnityEngine;

/// <summary>
/// 将实时患者生命体征与 Convai 角色上下文同步。
/// 角色会静默吸收常规波动，并且只在
/// 数值跨越具有临床意义的阈值时才升级响应——而不会泄露诊断结果。
/// </summary>
public class PatientVitalsSimulator : MonoBehaviour
{
    // 升级触发的临床阈值。
    private const float HypotensionThreshold = 90f;
    private const int BradycardiaThreshold = 50;
    private const float HypoxiaThreshold = 90f;

    private IConvaiRoomConnectionService _connectionService;
    private bool _criticalStateActive;

    /// <summary>
    /// 使用活动的房间连接服务初始化此组件。
    /// 必须在 Convai 会话建立后由会话所有者调用。
    /// </summary>
    public void Initialize(IConvaiRoomConnectionService connectionService)
    {
        if (connectionService == null)
        {
            Debug.LogError("[PatientVitalsSimulator] connectionService 不能为空。");
            return;
        }
        _connectionService = connectionService;
    }

    /// <summary>
    /// 将更新后的患者生命体征推送到角色上下文。
    /// 当临床阈值被跨越时触发升级或恢复响应。
    /// </summary>
    public void OnVitalsUpdated(PatientVitals vitals)
    {
        // 始终静默更新——使用替换而非追加（生命体征是当前状态，不是日志）。
        string vitalsContext =
            $"当前患者生命体征—— " +
            $"血压：{vitals.SystolicBP}/{vitals.DiastolicBP} mmHg， " +
            $"心率：{vitals.HeartRate} 次/分， " +
            $"SpO2: {vitals.OxygenSaturation}%, " +
            $"体温: {vitals.Temperature:F1}°C, " +
            $"呼吸频率: {vitals.RespiratoryRate}/分钟。";

        _connectionService?.UpdateDynamicContext(vitalsContext, mode: "replace", runLlm: "false");

        bool isCritical =
            vitals.SystolicBP < HypotensionThreshold ||
            vitals.HeartRate < BradycardiaThreshold ||
            vitals.OxygenSaturation < HypoxiaThreshold;

        if (isCritical && !_criticalStateActive)
        {
            _criticalStateActive = true;

            bool sent = _connectionService?.UpdateDynamicContext(
                $"临床警报：{BuildCriticalFindings(vitals)}。 " +
                "将其表现为病情恶化的患者。表达与严重程度相符的痛苦。 " +
                "不要告诉受训者该怎么做——让他们自己诊断并采取行动。",
                mode: "append",
                runLlm: "true") ?? false;

            if (!sent)
                Debug.LogWarning("[PatientVitalsSimulator] 未能发送关键升级。");
        }
        else if (!isCritical && _criticalStateActive)
        {
            _criticalStateActive = false;

            bool sent = _connectionService?.UpdateDynamicContext(
                "患者生命体征已稳定。表现出如释重负。让受训者评估下一步。",
                mode: "append",
                runLlm: "true") ?? false;

            if (!sent)
                Debug.LogWarning("[PatientVitalsSimulator] 未能发送稳定上下文。");
        }
    }

    private static string BuildCriticalFindings(PatientVitals vitals)
    {
        var findings = new List<string>();

        if (vitals.SystolicBP < HypotensionThreshold)
            findings.Add($"低血压（血压 {vitals.SystolicBP}/{vitals.DiastolicBP}）");
        if (vitals.HeartRate < BradycardiaThreshold)
            findings.Add($"心动过缓（心率 {vitals.HeartRate} 次/分）");
        if (vitals.OxygenSaturation < HypoxiaThreshold)
            findings.Add($"低氧血症（SpO2 {vitals.OxygenSaturation}%）");

        return string.Join(" and ", findings);
    }
}
```

***

## 高级

### 跨场景能力差距追踪器

一个多场景训练平台会追踪受训者在会话中所有场景里展示或遗漏了哪些能力。每个新场景都会基于已累计的能力画像进行调整，因此 AI 可以自然地探查薄弱环节，而受训者却不会察觉。

```csharp
using System.Collections.Generic;
using System.Linq;
using Convai.Runtime.Room;
using UnityEngine;

/// <summary>
/// 追踪受训者在多个训练场景中的能力表现。
/// 每个新场景都会基于已累计的能力画像进行调整，从而允许
/// AI 自然地探查薄弱环节，而受训者不会察觉。
/// 通过 DontDestroyOnLoad 在场景加载之间持久化。
/// </summary>
public sealed class CompetencyGapTracker : MonoBehaviour
{
    public static CompetencyGapTracker Instance { get; private set; }

    private IConvaiRoomConnectionService _connectionService;
    private readonly Dictionary<string, CompetencyRecord> _competencies = new();

    private void Awake()
    {
        if (Instance != null && Instance != this)
        {
            Destroy(gameObject);
            return;
        }
        Instance = this;
        DontDestroyOnLoad(gameObject);
    }

    /// <summary>
    /// 使用一个活动房间连接服务初始化此追踪器。
    /// 必须在 Convai 会话建立后由会话所有者调用。
    /// </summary>
    public void Initialize(IConvaiRoomConnectionService connectionService)
    {
        if (connectionService == null)
        {
            Debug.LogError("[CompetencyGapTracker] connectionService 不能为空。");
            return;
        }
        _connectionService = connectionService;
    }

    /// <summary>
    /// 记录某项能力的通过或失败观察。
    /// 某项能力在影响场景注入之前，至少需要两次观察记录。
    /// </summary>
    /// <param name="competencyId">稳定标识符（例如 "active-listening"）。</param>
    /// <param name="competencyName">传递给角色的可读名称。</param>
    /// <param name="demonstrated">如果受训者展示了这项能力，则为 True。</param>
    public void RecordObservation(string competencyId, string competencyName, bool demonstrated)
    {
        if (!_competencies.TryGetValue(competencyId, out var record))
            record = _competencies[competencyId] = new CompetencyRecord(competencyName);

        record.AddObservation(demonstrated);
    }

    /// <summary>
    /// 将累计的能力情报推送到下一个场景的上下文中。
    /// 角色会自然地创造机会，让受训者练习薄弱领域。
    /// </summary>
    public void OnNewScenarioStarted(string scenarioName, string scenarioContext)
    {
        string gaps = BuildGapSummary();
        string strengths = BuildStrengthSummary();

        string context =
            $"新场景：'{scenarioName}'。{scenarioContext} " +
            $"受训者在此前场景中的优势：{strengths}。 " +
            $"本场景需要探查的能力差距：{gaps}。 " +
            $"创造自然机会来练习薄弱领域。 " +
            "不要让人明显看出你在针对薄弱项。";

        // 替换——每个场景都以全新的、特定于场景的上下文开始。
        bool sent = _connectionService?.UpdateDynamicContext(context, mode: "replace", runLlm: "false") ?? false;
        if (!sent)
            Debug.LogWarning($"[CompetencyGapTracker] 未能为场景 '{scenarioName}' 注入上下文。");
    }

    /// <summary>
    /// 基于完整的能力记录触发一次完整的会话总结。
    /// 在会话中的所有场景都完成时调用。
    /// </summary>
    public void OnSessionCompleted()
    {
        string report = string.Join("; ", _competencies.Values
            .OrderBy(r => r.SuccessRate)
            .Select(r => $"{r.Name}: {r.SuccessRate:P0}（{r.TotalObservations} 次观察）"));

        bool sent = _connectionService?.UpdateDynamicContext(
            "训练会话已完成。请提供结构化总结：整体表现概述，" +
            "两个关键优势，两个优先发展领域，以及一个具体的下一步。 " +
            $"能力数据：{report}",
            mode: "replace",
            runLlm: "true") ?? false;

        if (!sent)
            Debug.LogWarning("[CompetencyGapTracker] 未能触发会话总结。");
    }

    // 少于两次观察记录的能力会被排除——数据不足。
    private string BuildGapSummary()
    {
        var gaps = _competencies.Values
            .Where(r => r.TotalObservations >= 2 && r.SuccessRate < 0.6f)
            .Select(r => $"{r.Name}（{r.SuccessRate:P0}）")
            .ToList();

        return gaps.Count > 0 ? string.Join(", ", gaps) : "目前尚未发现明显差距。";
    }

    private string BuildStrengthSummary()
    {
        var strengths = _competencies.Values
            .Where(r => r.TotalObservations >= 2 && r.SuccessRate >= 0.8f)
            .Select(r => $"{r.Name}（{r.SuccessRate:P0}）")
            .ToList();

        return strengths.Count > 0 ? string.Join(", ", strengths) : "数据不足。";
    }

    private sealed class CompetencyRecord
    {
        public string Name { get; }
        public int TotalObservations { get; private set; }
        private int _demonstratedCount;

        public float SuccessRate => TotalObservations == 0 ? 0f
            : (float)_demonstratedCount / TotalObservations;

        public CompetencyRecord(string name) => Name = name;

        public void AddObservation(bool demonstrated)
        {
            TotalObservations++;
            if (demonstrated) _demonstratedCount++;
        }
    }
}
```

**跨场景使用：**

```csharp
// 在场景中——记录你观察到的内容
CompetencyGapTracker.Instance.RecordObservation(
    "active-listening", "Active Listening", demonstrated: true);

CompetencyGapTracker.Instance.RecordObservation(
    "objection-handling", "Objection Handling", demonstrated: false);

// 在下一个场景前注入累计情报
CompetencyGapTracker.Instance.OnNewScenarioStarted(
    "难缠的客户——退款请求",
    "客户对延迟发货感到不满，并要求全额退款。");

// 在会话结束时——触发完整总结
CompetencyGapTracker.Instance.OnSessionCompleted();
```

***

### 实时程序化评估

一种高保真程序训练器（飞行模拟器、外科训练器、工业安全）会依据既定清单监控每个动作。动作会被悄然累积。当同一步骤反复失败时，AI 教官会以苏格拉底式提问介入——从不给出答案，而是引导受训者思考。

```csharp
using System.Collections.Generic;
using System.Linq;
using Convai.Runtime.Room;
using UnityEngine;

/// <summary>
/// 用于高保真程序训练模拟的 AI 教官
/// （飞行、外科、工业安全）。悄然监控受训者的每个动作
/// 仅当检测到重复错误模式时才以苏格拉底式引导介入
/// ——在整个过程中保持独立解决问题的能力。
/// </summary>
public class ProceduralAssessmentInstructor : MonoBehaviour
{
    [SerializeField]
    [Tooltip("在同一步骤上失败多少次后触发教练式干预。")]
    private int _errorPatternThreshold = 2;

    [SerializeField]
    [Tooltip("两次教练式干预之间的最短秒数。防止过于频繁的打断。")]
    private float _interventionCooldownSeconds = 30f;

    private IConvaiRoomConnectionService _connectionService;
    private readonly Dictionary<string, int> _stepErrorCounts = new();
    private readonly List<string> _completedSteps = new();
    private float _lastInterventionTime = float.MinValue;
    private string _currentProcedureName;

    /// <summary>
    /// 使用活动的房间连接服务初始化此组件。
    /// 必须在 Convai 会话建立后由会话所有者调用。
    /// </summary>
    public void Initialize(IConvaiRoomConnectionService connectionService)
    {
        if (connectionService == null)
        {
            Debug.LogError("[ProceduralAssessmentInstructor] connectionService 不能为空。");
            return;
        }
        _connectionService = connectionService;
    }

    /// <summary>
    /// 标记程序开始。重置所有跟踪状态，并指示
    /// 角色保持静默观察，直到需要介入为止。
    /// </summary>
    public void BeginProcedure(string procedureName)
    {
        _currentProcedureName = procedureName;
        _stepErrorCounts.Clear();
        _completedSteps.Clear();
        _lastInterventionTime = float.MinValue;

        bool sent = _connectionService?.UpdateDynamicContext(
            $"受训者正在开始 '{procedureName}' 程序。 " +
            "除非被请求帮助或出现关键错误，否则请静默观察。 " +
            "不要主动提示下一步——受训者必须展示独立回忆能力。",
            mode: "replace",
            runLlm: "false") ?? false;

        if (!sent)
            Debug.LogWarning($"[ProceduralAssessmentInstructor] 未能开始程序 '{procedureName}'。");
    }

    /// <summary>
    /// 记录一次步骤尝试。正确步骤会被静默确认。
    /// 对同一步骤的重复失败会触发一次苏格拉底式介入
    /// 前提是已达到错误阈值且冷却时间已结束。
    /// </summary>
    /// <param name="stepId">此步骤的稳定标识符。</param>
    /// <param name="stepDescription">传递给角色的可读描述。</param>
    /// <param name="wasCorrect">该步骤是否执行正确。</param>
    /// <param name="wasInSequence">该步骤是否按正确顺序执行。</param>
    public void OnStepAttempted(string stepId, string stepDescription, bool wasCorrect, bool wasInSequence)
    {
        if (wasCorrect && wasInSequence)
        {
            _completedSteps.Add(stepDescription);

            // 静默确认正确进展——无需角色回应。
            _connectionService?.UpdateDynamicContext(
                $"步骤已正确完成：{stepDescription}。 " +
                $"进度：已完成 {_completedSteps.Count} 个步骤。",
                mode: "append",
                runLlm: "false");

            return;
        }

        _stepErrorCounts.TryGetValue(stepId, out int errorCount);
        _stepErrorCounts[stepId] = ++errorCount;

        // 静默累积错误，用于上下文 grounding。
        _connectionService?.UpdateDynamicContext(
            $"步骤 '{stepDescription}' 出错 " +
            $"（错误次数：{errorCount}，按顺序：{wasInSequence}）。",
            mode: "append",
            runLlm: "false");

        bool patternDetected = errorCount >= _errorPatternThreshold;
        bool cooldownElapsed = Time.realtimeSinceStartup - _lastInterventionTime > _interventionCooldownSeconds;

        if (!patternDetected || !cooldownElapsed)
            return;

        _lastInterventionTime = Time.realtimeSinceStartup;

        // 以苏格拉底式提问介入——在不透露答案的情况下引导思考。
        bool sent = _connectionService?.UpdateDynamicContext(
            $"受训者已在 '{stepDescription}' 上失败 {errorCount} 次。 " +
            "请用一个苏格拉底式问题引导他们思考，但不要透露答案。 " +
            "语气冷静且具有建设性。",
            mode: "append",
            runLlm: "true") ?? false;

        if (!sent)
            Debug.LogWarning("[ProceduralAssessmentInstructor] 未能触发教练式干预。");
    }

    /// <summary>
    /// 标记程序结束，并从角色处触发结构化总结。
    /// </summary>
    public void OnProcedureCompleted(bool passed, float elapsedSeconds)
    {
        int totalErrors = _stepErrorCounts.Values.Sum();
        string worstStep = _stepErrorCounts.Count > 0
            ? _stepErrorCounts.OrderByDescending(kv => kv.Value).First().Key
            : "none";

        // 替换——总结上下文会覆盖所有已累积的步骤级上下文。
        bool sent = _connectionService?.UpdateDynamicContext(
            $"程序 '{_currentProcedureName}' 已完成。结果：{(passed ? "通过" : "失败")}。 " +
            $"耗时：{elapsedSeconds:F0} 秒。总错误数：{totalErrors}。 " +
            $"最有问题的步骤：'{worstStep}'（{_stepErrorCounts.GetValueOrDefault(worstStep, 0)} 次错误）。 " +
            "请给出一句总体评价、最需要解决的一个错误，以及一件他们做得好的地方。",
            mode: "replace",
            runLlm: "true") ?? false;

        if (!sent)
            Debug.LogWarning("[ProceduralAssessmentInstructor] 未能触发程序总结。");
    }
}
```

**此模式所强制体现的内容：**

* 教官能够完整掌握每一个动作，而无需不断打断——角色始终在倾听
* 苏格拉底式阈值可防止教官沦为提示机器——只有在真正检测到学习障碍时才会发声
* 冷却时间可防止过于频繁的干预破坏独立思考
* 最终总结会调用累计的事件日志，形成有依据、内容充实的评估，而不是泛泛的概述

***

### 具有自主响应阈值的反应式角色

最复杂的模式：角色会监控持续不断的游戏状态更新，并自主决定何时发声——无需等待用户主动发起。这是一种反应式架构。角色始终在监听，但只有当某些内容越过有意义的阈值时才会浮现出来。

**场景：** 一个同伴 NPC 会在整个会话中被动跟踪玩家的健康值，在中度下降时发出警告，并在关键等级时紧急介入——这一切都无需玩家输入。

```csharp
using Convai.Runtime.Room;
using UnityEngine;

/// <summary>
/// 一个被动监控游戏状态并自主响应的同伴 NPC
/// 当越过有意义的阈值时响应——无需等待玩家发言。
/// 静默更新使角色始终保持最新信息；runLlm: true 仅保留
/// 给真正需要主动响应的时刻。
/// </summary>
public class ReactiveCompanion : MonoBehaviour
{
    [SerializeField]
    [Tooltip("同伴首次表达担忧时的健康百分比。")]
    private float _warningThreshold = 50f;

    [SerializeField]
    [Tooltip("同伴紧急介入时的健康百分比。")]
    private float _criticalThreshold = 20f;

    private IConvaiRoomConnectionService _connectionService;
    private float _lastHealth = 100f;

    // 标志位可防止对同一次阈值越界重复介入。
    private bool _warningIssued;
    private bool _criticalIssued;

    /// <summary>
    /// 使用活动的房间连接服务初始化此组件。
    /// 必须在 Convai 会话建立后由会话所有者调用。
    /// </summary>
    public void Initialize(IConvaiRoomConnectionService connectionService)
    {
        if (connectionService == null)
        {
            Debug.LogError("[ReactiveCompanion] connectionService 不能为空。");
            return;
        }
        _connectionService = connectionService;
    }

    /// <summary>
    /// 被动跟踪玩家健康，并在定义阈值时触发主动响应。
    /// </summary>
    public void OnPlayerHealthChanged(float newHealth)
    {
        // 让角色静默获知状态——替换而不是追加（当前状态，而非日志）。
        _connectionService?.UpdateDynamicContext(
            $"玩家健康：{newHealth:F0}%。",
            mode: "replace",
            runLlm: "false");

        // 在向下穿过警告阈值时仅提醒一次。
        if (newHealth <= _warningThreshold && _lastHealth > _warningThreshold && !_warningIssued)
        {
            _warningIssued = true;

            bool sent = _connectionService?.UpdateDynamicContext(
                "玩家健康已降至 50% 以下。表达担忧，并建议他们寻找掩护或治疗。",
                mode: "append",
                runLlm: "true") ?? false;

            if (!sent)
                Debug.LogWarning("[ReactiveCompanion] 未能发送健康警告。");
        }

        // 关键介入——在每次越过阈值时触发一次，直到生命值恢复。
        if (newHealth <= _criticalThreshold && !_criticalIssued)
        {
            _criticalIssued = true;

            bool sent = _connectionService?.UpdateDynamicContext(
                "危急：玩家健康值极低。请紧急反应——告诉他们立即治疗。",
                mode: "append",
                runLlm: "true") ?? false;

            if (!sent)
                Debug.LogWarning("[ReactiveCompanion] 未能发送关键健康警告。");
        }

        // 当健康恢复到警告阈值以上时，重置两个标志位。
        if (newHealth > _warningThreshold)
        {
            _warningIssued = false;
            _criticalIssued = false;
        }

        _lastHealth = newHealth;
    }

    /// <summary>
    /// 当玩家进入已知危险区域时，立即触发主动警告。
    /// </summary>
    public void OnPlayerEnteredDangerZone(string zoneName)
    {
        bool sent = _connectionService?.UpdateDynamicContext(
            $"玩家已进入 {zoneName}，这是一个已知危险区域。请主动提醒他们。",
            mode: "append",
            runLlm: "true") ?? false;

        if (!sent)
            Debug.LogWarning("[ReactiveCompanion] 未能发送危险区域警告。");
    }

    /// <summary>
    /// 在玩家闲置太久后，自然地提示角色进行问候。
    /// 仅注入意图——具体措辞由 LLM 决定。
    /// </summary>
    public void OnPlayerIdleTooLong()
    {
        bool sent = _connectionService?.UpdateDynamicContext(
            "玩家已经异常长时间处于闲置状态。请自然地向他们打个招呼。",
            mode: "append",
            runLlm: "true") ?? false;

        if (!sent)
            Debug.LogWarning("[ReactiveCompanion] 未能发送闲置检查问候。");
    }
}
```

通过静默更新 `runLlm: "false"` 始终保持角色的知识最新。 `runLlm: "true"` 仅保留给真正需要主动响应的时刻。阈值逻辑确保角色只介入一次——而不是反复介入——直到情况得到解决。


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.convai.com/api-docs/zh/cha-jian-yu-ji-cheng/unity-plugin-beta-overview/dynamic-context/use-cases.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
