Loading

多轮对话与状态管理

多轮对话与状态管理

前言

在前三篇文章中,我们创建了能够理解问题、执行工具的智能体。但真实的对话往往是多轮的、有状态的复杂交互。想象一下这样的场景:

用户:"我想订一张去北京的机票。"
助手:"好的,请问您计划什么时间出发?"
用户:"下周五。"
助手:"请问从哪里出发?"
用户:"上海。"
助手:"经济舱还是商务舱?"
用户:"经济舱。"
助手:"已为您搜索到3个航班,价格分别是..."

这样的对话需要智能体能够:

  1. 记住上下文:知道用户在预订机票
  2. 管理对话状态:跟踪已收集的信息(目的地、时间)
  3. 引导对话流程:询问缺失的必要信息
  4. 处理异常情况:用户改变主意、提供错误信息等

这就是多轮对话与状态管理的核心。今天,我们将深入探索如何在Agent Framework中实现智能的对话状态管理。

一、对话状态管理基础

1.1 对话状态的核心要素

一个完整的对话状态通常包含以下要素:

  1. 对话历史(Conversation History)

    • 用户和助手的交互记录
    • 每轮对话的内容和时间戳
    • 对话的上下文信息
  2. 当前意图(Current Intent)

    • 用户当前的对话目的
    • 例如:预订机票、查询天气、技术支持
  3. 已收集信息(Collected Information)

    • 在多轮对话中收集的数据
    • 例如:目的地、出发时间、舱位等级
  4. 对话阶段(Conversation Stage)

    • 对话所处的流程阶段
    • 例如:信息收集、确认、执行、完成
  5. 用户偏好(User Preferences)

    • 用户的个性化设置
    • 例如:语言偏好、默认城市、历史选择

1.2 Agent Framework的对话管理

Agent Framework提供了完整的对话管理支持:

// 基本对话管理
var conversation = new Conversation
{
    Id = Guid.NewGuid().ToString(),
    CreatedAt = DateTime.UtcNow,
    Messages = new List<Message>()
};

// 添加消息
conversation.Messages.Add(new Message
{
    Role = "user",
    Content = "我想订一张去北京的机票",
    Timestamp = DateTime.UtcNow
});

// 处理消息
var response = await agent.ProcessAsync(conversation);
conversation.Messages.Add(new Message
{
    Role = "assistant",
    Content = response,
    Timestamp = DateTime.UtcNow
});

二、状态管理实现模式

2.1 基于内存的状态管理

对于简单的场景,可以使用内存状态管理:

// MemoryStateManager.cs
public class MemoryStateManager
{
    private readonly ConcurrentDictionary<string, ConversationState> _states;
    
    public MemoryStateManager()
    {
        _states = new ConcurrentDictionary<string, ConversationState>();
    }
    
    public ConversationState GetOrCreateState(string conversationId)
    {
        return _states.GetOrAdd(conversationId, id => new ConversationState
        {
            ConversationId = id,
            CreatedAt = DateTime.UtcNow,
            LastActivity = DateTime.UtcNow
        });
    }
    
    public void UpdateState(string conversationId, Action<ConversationState> updateAction)
    {
        var state = GetOrCreateState(conversationId);
        updateAction(state);
        state.LastActivity = DateTime.UtcNow;
    }
    
    public void RemoveState(string conversationId)
    {
        _states.TryRemove(conversationId, out _);
    }
    
    // 清理过期状态(例如24小时未活动)
    public void CleanupExpiredStates(TimeSpan expiration)
    {
        var cutoff = DateTime.UtcNow - expiration;
        var expired = _states.Where(kv => kv.Value.LastActivity < cutoff)
                            .Select(kv => kv.Key)
                            .ToList();
        
        foreach (var key in expired)
        {
            _states.TryRemove(key, out _);
        }
    }
}

public class ConversationState
{
    public string ConversationId { get; set; } = string.Empty;
    public DateTime CreatedAt { get; set; }
    public DateTime LastActivity { get; set; }
    
    // 当前意图
    public string CurrentIntent { get; set; } = string.Empty;
    public Dictionary<string, object> IntentParameters { get; set; } = new();
    
    // 对话历史
    public List<ConversationTurn> History { get; set; } = new();
    
    // 用户信息
    public string UserId { get; set; } = string.Empty;
    public Dictionary<string, object> UserPreferences { get; set; } = new();
    
    // 临时数据
    public Dictionary<string, object> TemporaryData { get; set; } = new();
    
    // 流程控制
    public string CurrentStage { get; set; } = string.Empty;
    public List<string> CompletedStages { get; set; } = new();
    
    // 错误处理
    public List<string> Errors { get; set; } = new();
    public int RetryCount { get; set; }
}

2.2 基于数据库的状态持久化

对于需要持久化的场景,可以使用数据库:

// DatabaseStateManager.cs
public class DatabaseStateManager
{
    private readonly AppDbContext _dbContext;
    private readonly ILogger<DatabaseStateManager> _logger;
    
    public DatabaseStateManager(AppDbContext dbContext, ILogger<DatabaseStateManager> logger)
    {
        _dbContext = dbContext;
        _logger = logger;
    }
    
    public async Task<ConversationState> GetStateAsync(string conversationId)
    {
        var state = await _dbContext.ConversationStates
            .FirstOrDefaultAsync(s => s.ConversationId == conversationId);
        
        if (state == null)
        {
            state = new ConversationStateEntity
            {
                ConversationId = conversationId,
                CreatedAt = DateTime.UtcNow,
                LastActivity = DateTime.UtcNow,
                StateData = "{}"
            };
            
            _dbContext.ConversationStates.Add(state);
            await _dbContext.SaveChangesAsync();
        }
        
        return DeserializeState(state.StateData);
    }
    
    public async Task UpdateStateAsync(string conversationId, Action<ConversationState> updateAction)
    {
        var state = await GetStateAsync(conversationId);
        updateAction(state);
        
        var entity = await _dbContext.ConversationStates
            .FirstOrDefaultAsync(s => s.ConversationId == conversationId);
        
        if (entity != null)
        {
            entity.StateData = SerializeState(state);
            entity.LastActivity = DateTime.UtcNow;
            await _dbContext.SaveChangesAsync();
        }
    }
    
    private ConversationState DeserializeState(string json)
    {
        return JsonSerializer.Deserialize<ConversationState>(json)
            ?? new ConversationState();
    }
    
    private string SerializeState(ConversationState state)
    {
        return JsonSerializer.Serialize(state);
    }
}

// 数据库实体
public class ConversationStateEntity
{
    [Key]
    public string ConversationId { get; set; } = string.Empty;
    
    public DateTime CreatedAt { get; set; }
    public DateTime LastActivity { get; set; }
    
    [Column(TypeName = "nvarchar(max)")]
    public string StateData { get; set; } = string.Empty;
    
    // 索引
    [Index]
    public string UserId { get; set; } = string.Empty;
    
    [Index]
    public DateTime ExpiresAt { get; set; }
}

三、对话流程设计模式

3.1 状态机模式

对于复杂的对话流程,状态机是最佳选择:

// ConversationStateMachine.cs
public class ConversationStateMachine
{
    private readonly Dictionary<string, ConversationState> _states;
    private readonly Dictionary<string, List<StateTransition>> _transitions;
    
    public ConversationStateMachine()
    {
        _states = new Dictionary<string, ConversationState>();
        _transitions = new Dictionary<string, List<StateTransition>>();
        
        InitializeStates();
        InitializeTransitions();
    }
    
    private void InitializeStates()
    {
        // 定义所有可能的状态
        _states["idle"] = new ConversationState { Name = "idle", Description = "空闲状态" };
        _states["collecting_info"] = new ConversationState { Name = "collecting_info", Description = "信息收集" };
        _states["confirming"] = new ConversationState { Name = "confirming", Description = "确认信息" };
        _states["executing"] = new ConversationState { Name = "executing", Description = "执行操作" };
        _states["completed"] = new ConversationState { Name = "completed", Description = "完成" };
        _states["error"] = new ConversationState { Name = "error", Description = "错误状态" };
    }
    
    private void InitializeTransitions()
    {
        // 定义状态转移
        AddTransition("idle", "collecting_info", 
            context => context.Intent != null && !string.IsNullOrEmpty(context.Intent));
        
        AddTransition("collecting_info", "confirming", 
            context => IsAllRequiredInfoCollected(context));
        
        AddTransition("confirming", "executing", 
            context => context.UserConfirmed == true);
        
        AddTransition("executing", "completed", 
            context => context.OperationCompleted == true);
        
        // 错误转移
        AddTransition("collecting_info", "error", 
            context => context.RetryCount > 3);
        
        // 重试转移
        AddTransition("error", "collecting_info", 
            context => context.UserWantsRetry == true);
    }
    
    public async Task<string> ProcessMessageAsync(
        string currentState, 
        ConversationContext context)
    {
        // 获取当前状态可能的转移
        if (!_transitions.TryGetValue(currentState, out var possibleTransitions))
            return currentState;
        
        // 检查每个转移条件
        foreach (var transition in possibleTransitions)
        {
            if (await transition.Condition(context))
            {
                // 执行转移前的钩子
                if (transition.BeforeTransition != null)
                    await transition.BeforeTransition(context);
                
                // 执行转移
                context.CurrentState = transition.TargetState;
                
                // 执行转移后的钩子
                if (transition.AfterTransition != null)
                    await transition.AfterTransition(context);
                
                return transition.TargetState;
            }
        }
        
        // 没有匹配的转移,保持当前状态
        return currentState;
    }
    
    private void AddTransition(
        string fromState, 
        string toState, 
        Func<ConversationContext, Task<bool>> condition)
    {
        if (!_transitions.ContainsKey(fromState))
            _transitions[fromState] = new List<StateTransition>();
        
        _transitions[fromState].Add(new StateTransition
        {
            SourceState = fromState,
            TargetState = toState,
            Condition = condition
        });
    }
    
    private bool IsAllRequiredInfoCollected(ConversationContext context)
    {
        // 根据意图检查必要信息
        return context.Intent switch
        {
            "book_flight" => 
                !string.IsNullOrEmpty(context.Destination) &&
                !string.IsNullOrEmpty(context.DepartureDate) &&
                !string.IsNullOrEmpty(context.DepartureCity),
            
            "query_weather" => 
                !string.IsNullOrEmpty(context.City),
            
            _ => true
        };
    }
}

public class StateTransition
{
    public string SourceState { get; set; } = string.Empty;
    public string TargetState { get; set; } = string.Empty;
    public Func<ConversationContext, Task<bool>> Condition { get; set; }
    public Func<ConversationContext, Task> BeforeTransition { get; set; }
    public Func<ConversationContext, Task> AfterTransition { get; set; }
}

3.2 机票预订示例实现

让我们实现一个完整的机票预订对话系统:

// FlightBookingAgent.cs
public class FlightBookingAgent
{
    private readonly IAIAgent _agent;
    private readonly ConversationStateMachine _stateMachine;
    private readonly IStateManager _stateManager;
    
    public FlightBookingAgent(
        IAIAgent agent, 
        ConversationStateMachine stateMachine,
        IStateManager stateManager)
    {
        _agent = agent;
        _stateMachine = stateMachine;
        _stateManager = stateManager;
    }
    
    public async Task<string> ProcessMessageAsync(
        string conversationId, 
        string userMessage)
    {
        // 获取或创建对话状态
        var context = await _stateManager.GetOrCreateContextAsync(conversationId);
        
        // 添加用户消息到历史
        context.AddUserMessage(userMessage);
        
        // 处理消息
        var response = await ProcessWithStateMachineAsync(context);
        
        // 添加助手消息到历史
        context.AddAssistantMessage(response);
        
        // 保存状态
        await _stateManager.SaveContextAsync(conversationId, context);
        
        return response;
    }
    
    private async Task<string> ProcessWithStateMachineAsync(ConversationContext context)
    {
        // 更新状态
        var newState = await _stateMachine.ProcessMessageAsync(
            context.CurrentState, 
            context);
        
        context.CurrentState = newState;
        
        // 根据状态生成响应
        return context.CurrentState switch
        {
            "idle" => await GenerateWelcomeMessage(context),
            "collecting_info" => await CollectBookingInfo(context),
            "confirming" => await ConfirmBooking(context),
            "executing" => await ExecuteBooking(context),
            "completed" => await GenerateCompletionMessage(context),
            "error" => await HandleError(context),
            _ => "抱歉,我遇到了一个未知状态。"
        };
    }
    
    private async Task<string> GenerateWelcomeMessage(ConversationContext context)
    {
        // 分析用户意图
        var intent = await DetectIntent(context.LastUserMessage);
        context.Intent = intent;
        
        return intent switch
        {
            "book_flight" => "您好!我可以帮您预订机票。请问您想去哪里?",
            "check_flight" => "您好!我可以帮您查询航班信息。请提供相关信息。",
            _ => "您好!我可以帮您预订机票或查询航班信息。请问您需要什么帮助?"
        };
    }
    
    private async Task<string> CollectBookingInfo(ConversationContext context)
    {
        // 检查已收集的信息
        var missingInfo = GetMissingBookingInfo(context);
        
        if (missingInfo.Count == 0)
        {
            // 信息收集完成,转移到确认状态
            context.CurrentState = "confirming";
            return await ConfirmBooking(context);
        }
        
        // 询问缺失的信息
        var nextQuestion = missingInfo[0];
        return GenerateQuestion(nextQuestion, context);
    }
    
    private List<string> GetMissingBookingInfo(ConversationContext context)
    {
        var requiredInfo = new List<string>
        {
            "destination",     // 目的地
            "departure_city",  // 出发城市
            "departure_date",  // 出发日期
            "return_date",     // 返程日期(可选)
            "passenger_count", // 乘客人数
            "cabin_class"      // 舱位等级
        };
        
        var missing = new List<string>();
        
        foreach (var info in requiredInfo)
        {
            if (!context.CollectedInfo.ContainsKey(info))
                missing.Add(info);
        }
        
        return missing;
    }
    
    private string GenerateQuestion(string infoType, ConversationContext context)
    {
        return infoType switch
        {
            "destination" => "请问您想去哪里?",
            "departure_city" => "请问您从哪里出发?",
            "departure_date" => "请问您计划什么时间出发?",
            "return_date" => "请问您需要返程吗?如果需要,请提供返程日期。",
            "passenger_count" => "请问有几位乘客?",
            "cabin_class" => "请问您需要经济舱、商务舱还是头等舱?",
            _ => "请提供更多信息。"
        };
    }
    
    private async Task<string> ConfirmBooking(ConversationContext context)
    {
        // 生成确认信息
        var bookingSummary = GenerateBookingSummary(context);
        
        return $"""
            {bookingSummary}
            
            请确认以上信息是否正确?如果正确,请回复"确认";如果需要修改,请告诉我需要修改的内容。
            """;
    }
    
    private async Task<string> ExecuteBooking(ConversationContext context)
    {
        try
        {
            // 调用机票预订API
            var bookingResult = await BookFlightAsync(context);
            
            if (bookingResult.Success)
            {
                context.BookingId = bookingResult.BookingId;
                context.CurrentState = "completed";
                return $"预订成功!您的预订编号是:{bookingResult.BookingId}。我们已将预订详情发送到您的邮箱。";
            }
            else
            {
                context.CurrentState = "error";
                context.LastError = bookingResult.Error;
                return $"预订失败:{bookingResult.Error}。请稍后重试或联系客服。";
            }
        }
        catch (Exception ex)
        {
            context.CurrentState = "error";
            context.LastError = ex.Message;
            return $"系统错误:{ex.Message}。请稍后重试。";
        }
    }
    
    private async Task<BookingResult> BookFlightAsync(ConversationContext context)
    {
        // 模拟机票预订API调用
        await Task.Delay(1000);
        
        return new BookingResult
        {
            Success = true,
            BookingId = $"FLT{Guid.NewGuid():N}".Substring(0, 8).ToUpper(),
            TotalPrice = CalculatePrice(context),
            BookingTime = DateTime.UtcNow
        };
    }
    
    private string GenerateBookingSummary(ConversationContext context)
    {
        return $"""
            预订信息汇总:
            - 目的地:{context.CollectedInfo.GetValueOrDefault("destination", "未指定")}
            - 出发城市:{context.CollectedInfo.GetValueOrDefault("departure_city", "未指定")}
            - 出发日期:{context.CollectedInfo.GetValueOrDefault("departure_date", "未指定")}
            - 返程日期:{context.CollectedInfo.GetValueOrDefault("return_date", "单程")}
            - 乘客人数:{context.CollectedInfo.GetValueOrDefault("passenger_count", "1")}
            - 舱位等级:{context.CollectedInfo.GetValueOrDefault("cabin_class", "经济舱")}
            """;
    }
}

四、上下文窗口优化

4.1 Token限制管理

LLM通常有上下文窗口限制,需要智能管理:

// ContextWindowManager.cs
public class ContextWindowManager
{
    private readonly int _maxTokens;
    private readonly ITokenizer _tokenizer;
    
    public ContextWindowManager(int maxTokens = 4000)
    {
        _maxTokens = maxTokens;
        _tokenizer = new Tokenizer(); // 实际使用合适的Tokenizer
    }
    
    public ConversationContext OptimizeContext(
        ConversationContext originalContext, 
        string currentMessage)
    {
        var optimized = originalContext.Clone();
        
        // 计算当前消息的token数
        var currentMessageTokens = _tokenizer.CountTokens(currentMessage);
        
        // 计算可用token数
        var availableTokens = _maxTokens - currentMessageTokens - 500; // 预留响应空间
        
        // 如果历史太长,进行压缩
        if (_tokenizer.CountTokens(optimized.GetFullHistory()) > availableTokens)
        {
            optimized = CompressHistory(optimized, availableTokens);
        }
        
        return optimized;
    }
    
    private ConversationContext CompressHistory(
        ConversationContext context, 
        int targetTokenCount)
    {
        var compressed = context.Clone();
        
        // 策略1:保留最近的对话
        compressed.History = compressed.History
            .TakeLast(10) // 保留最近10轮
            .ToList();
        
        var currentTokens = _tokenizer.CountTokens(compressed.GetFullHistory());
        
        // 如果还是太长,策略2:总结早期对话
        if (currentTokens > targetTokenCount)
        {
            compressed = SummarizeEarlyHistory(compressed, targetTokenCount);
        }
        
        // 如果还是太长,策略3:移除最不重要的对话
        if (currentTokens > targetTokenCount)
        {
            compressed = RemoveUnimportantTurns(compressed, targetTokenCount);
        }
        
        return compressed;
    }
    
    private ConversationContext SummarizeEarlyHistory(
        ConversationContext context, 
        int targetTokenCount)
    {
        if (context.History.Count <= 5)
            return context;
        
        // 将早期对话合并为摘要
        var earlyTurns = context.History.Take(context.History.Count - 5).ToList();
        var recentTurns = context.History.Skip(context.History.Count - 5).ToList();
        
        var summary = $"早期对话摘要:用户咨询了关于{GetMainTopics(earlyTurns)}的内容。";
        
        var compressed = context.Clone();
        compressed.History = new List<ConversationTurn>
        {
            new ConversationTurn
            {
                Role = "system",
                Content = summary,
                IsSummary = true
            }
        };
        
        compressed.History.AddRange(recentTurns);
        
        return compressed;
    }
    
    private string GetMainTopics(List<ConversationTurn> turns)
    {
        // 简单实现,实际可以使用NLP提取主题
        var topics = new List<string>();
        
        foreach (var turn in turns)
        {
            if (turn.Role == "user")
            {
                var content = turn.Content.ToLower();
                
                if (content.Contains("机票") || content.Contains("航班"))
                    topics.Add("机票预订");
                else if (content.Contains("酒店"))
                    topics.Add("酒店预订");
                else if (content.Contains("天气"))
                    topics.Add("天气查询");
            }
        }
        
        return string.Join("、", topics.Distinct());
    }
}

4.2 对话记忆管理

// ConversationMemoryManager.cs
public class ConversationMemoryManager
{
    private readonly IMemoryStore _memoryStore;
    
    public ConversationMemoryManager(IMemoryStore memoryStore)
    {
        _memoryStore = memoryStore;
    }
    
    public async Task<string> GetRelevantMemoryAsync(
        string conversationId, 
        string currentMessage)
    {
        // 从记忆存储中检索相关记忆
        var memories = await _memoryStore.SearchAsync(
            conversationId, 
            currentMessage, 
            limit: 5);
        
        if (!memories.Any())
            return string.Empty;
        
        // 格式化记忆
        var memoryText = "相关记忆:\n";
        foreach (var memory in memories)
        {
            memoryText += $"- {memory.Content}({memory.CreatedAt:yyyy-MM-dd HH:mm})\n";
        }
        
        return memoryText;
    }
    
    public async Task SaveMemoryAsync(
        string conversationId, 
        ConversationTurn turn, 
        string[] tags)
    {
        var memory = new ConversationMemory
        {
            ConversationId = conversationId,
            Content = turn.Content,
            Role = turn.Role,
            Timestamp = turn.Timestamp,
            Tags = tags,
            Importance = CalculateImportance(turn)
        };
        
        await _memoryStore.SaveAsync(memory);
    }
    
    private double CalculateImportance(ConversationTurn turn)
    {
        // 基于内容计算重要性
        var importance = 1.0;
        
        // 包含关键信息的对话更重要
        if (turn.Content.Contains("确认") || turn.Content.Contains("同意"))
            importance *= 2.0;
        
        // 包含用户偏好的对话更重要
        if (turn.Content.Contains("喜欢") || turn.Content.Contains("偏好"))
            importance *= 1.5;
        
        // 包含联系信息的对话更重要
        if (turn.Content.Contains("@") || turn.Content.Contains("电话"))
            importance *= 3.0;
        
        return importance;
    }
}

五、错误处理与恢复

5.1 对话错误处理

// ConversationErrorHandler.cs
public class ConversationErrorHandler
{
    private readonly ILogger<ConversationErrorHandler> _logger;
    
    public ConversationErrorHandler(ILogger<ConversationErrorHandler> logger)
    {
        _logger = logger;
    }
    
    public async Task<string> HandleErrorAsync(
        ConversationContext context, 
        Exception exception)
    {
        // 记录错误
        _logger.LogError(exception, 
            "对话处理错误: ConversationId={ConversationId}, State={State}", 
            context.ConversationId, 
            context.CurrentState);
        
        // 根据错误类型处理
        return exception switch
        {
            TimeoutException => await HandleTimeoutError(context),
            InvalidOperationException => await HandleInvalidOperationError(context, exception),
            ArgumentException => await HandleArgumentError(context, exception),
            _ => await HandleGenericError(context, exception)
        };
    }
    
    private async Task<string> HandleTimeoutError(ConversationContext context)
    {
        context.RetryCount++;
        
        if (context.RetryCount <= 3)
        {
            return "处理超时,正在重试...";
        }
        else
        {
            context.CurrentState = "error";
            return "抱歉,系统暂时繁忙,请稍后再试。";
        }
    }
    
    private async Task<string> HandleInvalidOperationError(
        ConversationContext context, 
        InvalidOperationException exception)
    {
        // 检查是否是状态错误
        if (exception.Message.Contains("state", StringComparison.OrdinalIgnoreCase))
        {
            // 重置到安全状态
            context.CurrentState = "idle";
            context.RetryCount = 0;
            
            return "对话状态出现问题,已重置。请重新开始。";
        }
        
        return await HandleGenericError(context, exception);
    }
    
    private async Task<string> HandleArgumentError(
        ConversationContext context, 
        ArgumentException exception)
    {
        // 提取缺失的参数
        var paramName = exception.ParamName ?? "参数";
        
        return $"缺少必要的{paramName}信息。请提供{paramName}。";
    }
    
    private async Task<string> HandleGenericError(
        ConversationContext context, 
        Exception exception)
    {
        context.CurrentState = "error";
        context.LastError = exception.Message;
        
        // 避免向用户泄露技术细节
        return "抱歉,处理您的请求时出现了问题。我们的技术团队已经收到通知。";
    }
    
    public async Task<bool> CanRecoverAsync(ConversationContext context)
    {
        // 检查是否可以恢复
        if (context.CurrentState == "error" && context.RetryCount < 3)
        {
            // 检查错误是否可恢复
            var recoverableErrors = new[]
            {
                "timeout", "busy", "temporarily unavailable"
            };
            
            if (recoverableErrors.Any(e => 
                context.LastError?.Contains(e, StringComparison.OrdinalIgnoreCase) == true))
            {
                return true;
            }
        }
        
        return false;
    }
}

六、性能优化与监控

6.1 对话性能监控

// ConversationPerformanceMonitor.cs
public class ConversationPerformanceMonitor
{
    private readonly IMetrics _metrics;
    
    public ConversationPerformanceMonitor(IMetrics metrics)
    {
        _metrics = metrics;
    }
    
    public IDisposable MeasureConversation(string conversationId)
    {
        var stopwatch = Stopwatch.StartNew();
        
        return new DisposableAction(() =>
        {
            stopwatch.Stop();
            
            // 记录性能指标
            _metrics.RecordHistogram(
                "conversation.duration", 
                stopwatch.ElapsedMilliseconds);
            
            _metrics.IncrementCounter("conversation.completed");
        });
    }
    
    public void RecordStateTransition(
        string fromState, 
        string toState, 
        TimeSpan duration)
    {
        _metrics.RecordHistogram(
            $"conversation.state_transition.{fromState}.{toState}.duration",
            duration.TotalMilliseconds);
        
        _metrics.IncrementCounter(
            $"conversation.state_transition.{fromState}.{toState}.count");
    }
    
    public void RecordError(string errorType, string conversationId)
    {
        _metrics.IncrementCounter($"conversation.error.{errorType}");
        
        // 记录错误上下文
        _metrics.SetGauge(
            $"conversation.error.last_occurrence.{errorType}",
            DateTime.UtcNow.Ticks);
    }
}

private class DisposableAction : IDisposable
{
    private readonly Action _action;
    
    public DisposableAction(Action action)
    {
        _action = action;
    }
    
    public void Dispose()
    {
        _action();
    }
}

6.2 对话质量评估

// ConversationQualityAssessor.cs
public class ConversationQualityAssessor
{
    public ConversationQualityScore AssessQuality(ConversationContext context)
    {
        var score = new ConversationQualityScore();
        
        // 1. 完成度评分
        score.CompletionScore = CalculateCompletionScore(context);
        
        // 2. 效率评分(对话轮次)
        score.EfficiencyScore = CalculateEfficiencyScore(context);
        
        // 3. 用户满意度(基于交互模式)
        score.SatisfactionScore = CalculateSatisfactionScore(context);
        
        // 4. 错误率评分
        score.ErrorScore = CalculateErrorScore(context);
        
        // 综合评分
        score.OverallScore = (score.CompletionScore * 0.4 +
                             score.EfficiencyScore * 0.3 +
                             score.SatisfactionScore * 0.2 +
                             score.ErrorScore * 0.1);
        
        return score;
    }
    
    private double CalculateCompletionScore(ConversationContext context)
    {
        if (context.CurrentState == "completed")
            return 1.0;
        
        if (context.CurrentState == "executing")
            return 0.7;
        
        if (context.CurrentState == "confirming")
            return 0.5;
        
        return 0.3;
    }
    
    private double CalculateEfficiencyScore(ConversationContext context)
    {
        var turnCount = context.History.Count / 2; // 用户和助手各算一轮
        
        if (turnCount <= 5)
            return 1.0;
        
        if (turnCount <= 10)
            return 0.8;
        
        if (turnCount <= 15)
            return 0.6;
        
        return 0.4;
    }
    
    private double CalculateSatisfactionScore(ConversationContext context)
    {
        // 基于用户行为推测满意度
        var score = 0.5; // 基准分
        
        // 积极信号
        if (context.History.Any(t => 
            t.Role == "user" && 
            t.Content.Contains("谢谢", StringComparison.OrdinalIgnoreCase)))
            score += 0.2;
        
        if (context.History.Any(t => 
            t.Role == "user" && 
            t.Content.Contains("很好", StringComparison.OrdinalIgnoreCase)))
            score += 0.1;
        
        // 消极信号
        if (context.History.Any(t => 
            t.Role == "user" && 
            t.Content.Contains("不好", StringComparison.OrdinalIgnoreCase)))
            score -= 0.2;
        
        if (context.History.Any(t => 
            t.Role == "user" && 
            t.Content.Contains("太慢", StringComparison.OrdinalIgnoreCase)))
            score -= 0.1;
        
        return Math.Clamp(score, 0.0, 1.0);
    }
    
    private double CalculateErrorScore(ConversationContext context)
    {
        var errorCount = context.Errors.Count;
        var totalTurns = context.History.Count / 2;
        
        if (totalTurns == 0)
            return 1.0;
        
        var errorRate = (double)errorCount / totalTurns;
        
        if (errorRate == 0)
            return 1.0;
        
        if (errorRate <= 0.1)
            return 0.8;
        
        if (errorRate <= 0.2)
            return 0.6;
        
        return 0.4;
    }
}

七、完整示例:智能客服系统

让我们把所有内容整合起来,创建一个完整的智能客服系统:

// SmartCustomerService.cs
public class SmartCustomerService
{
    private readonly IAIAgent _agent;
    private readonly ConversationStateMachine _stateMachine;
    private readonly IStateManager _stateManager;
    private readonly ConversationErrorHandler _errorHandler;
    private readonly ConversationPerformanceMonitor _performanceMonitor;
    
    public SmartCustomerService(
        IAIAgent agent,
        ConversationStateMachine stateMachine,
        IStateManager stateManager,
        ConversationErrorHandler errorHandler,
        ConversationPerformanceMonitor performanceMonitor)
    {
        _agent = agent;
        _stateMachine = stateMachine;
        _stateManager = stateManager;
        _errorHandler = errorHandler;
        _performanceMonitor = performanceMonitor;
    }
    
    public async Task<ServiceResponse> ProcessRequestAsync(ServiceRequest request)
    {
        using var performanceScope = _performanceMonitor.MeasureConversation(request.ConversationId);
        
        try
        {
            // 获取对话上下文
            var context = await _stateManager.GetOrCreateContextAsync(request.ConversationId);
            
            // 处理消息
            var responseText = await ProcessMessageWithStateAsync(context, request.Message);
            
            // 保存上下文
            await _stateManager.SaveContextAsync(request.ConversationId, context);
            
            // 记录成功
            _performanceMonitor.RecordStateTransition(
                context.PreviousState, 
                context.CurrentState, 
                context.LastStateTransitionDuration);
            
            return new ServiceResponse
            {
                Success = true,
                Message = responseText,
                ConversationId = request.ConversationId,
                CurrentState = context.CurrentState,
                SuggestedActions = GetSuggestedActions(context)
            };
        }
        catch (Exception ex)
        {
            // 处理错误
            var errorResponse = await _errorHandler.HandleErrorAsync(
                await _stateManager.GetOrCreateContextAsync(request.ConversationId), 
                ex);
            
            // 记录错误
            _performanceMonitor.RecordError(ex.GetType().Name, request.ConversationId);
            
            return new ServiceResponse
            {
                Success = false,
                Message = errorResponse,
                ConversationId = request.ConversationId,
                CurrentState = "error",
                Error = ex.Message
            };
        }
    }
    
    private async Task<string> ProcessMessageWithStateAsync(
        ConversationContext context, 
        string message)
    {
        // 记录当前状态
        context.PreviousState = context.CurrentState;
        var transitionStartTime = DateTime.UtcNow;
        
        // 处理消息
        var response = await _agent.ProcessAsync(context, message);
        
        // 更新上下文
        context.AddUserMessage(message);
        context.AddAssistantMessage(response);
        
        // 更新状态
        if (context.CurrentState != context.PreviousState)
        {
            context.LastStateTransitionDuration = DateTime.UtcNow - transitionStartTime;
        }
        
        return response;
    }
    
    private List<SuggestedAction> GetSuggestedActions(ConversationContext context)
    {
        var actions = new List<SuggestedAction>();
        
        // 根据状态提供建议操作
        switch (context.CurrentState)
        {
            case "collecting_info":
                var missingInfo = GetMissingInformation(context);
                foreach (var info in missingInfo)
                {
                    actions.Add(new SuggestedAction
                    {
                        Type = "provide_info",
                        Label = $"提供{info}",
                        Value = info
                    });
                }
                break;
                
            case "confirming":
                actions.Add(new SuggestedAction
                {
                    Type = "confirm",
                    Label = "确认信息",
                    Value = "confirm"
                });
                actions.Add(new SuggestedAction
                {
                    Type = "modify",
                    Label = "修改信息",
                    Value = "modify"
                });
                break;
                
            case "completed":
                actions.Add(new SuggestedAction
                {
                    Type = "new_request",
                    Label = "开始新的咨询",
                    Value = "new"
                });
                break;
        }
        
        // 总是提供这些通用操作
        actions.Add(new SuggestedAction
        {
            Type = "help",
            Label = "获取帮助",
            Value = "help"
        });
        
        actions.Add(new SuggestedAction
        {
            Type = "cancel",
            Label = "取消当前操作",
            Value = "cancel"
        });
        
        return actions;
    }
}

八、测试与验证

8.1 对话流程测试

// ConversationFlowTests.cs
public class ConversationFlowTests
{
    [Fact]
    public async Task FlightBooking_CompleteFlow_Success()
    {
        // 准备
        var service = CreateCustomerService();
        var conversationId = Guid.NewGuid().ToString();
        
        // 执行完整的预订流程
        var response1 = await service.ProcessRequestAsync(new ServiceRequest
        {
            ConversationId = conversationId,
            Message = "我想订一张去北京的机票"
        });
        
        Assert.Contains("去哪里", response1.Message);
        Assert.Equal("collecting_info", response1.CurrentState);
        
        var response2 = await service.ProcessRequestAsync(new ServiceRequest
        {
            ConversationId = conversationId,
            Message = "下周五"
        });
        
        Assert.Contains("从哪里出发", response2.Message);
        
        // 继续完整的对话流程...
    }
    
    [Fact]
    public async Task Conversation_ErrorRecovery_Works()
    {
        // 测试错误恢复机制
        var service = CreateCustomerServiceWithFaultyAgent();
        var conversationId = Guid.NewGuid().ToString();
        
        // 第一次请求应该失败
        var response1 = await service.ProcessRequestAsync(new ServiceRequest
        {
            ConversationId = conversationId,
            Message = "测试消息"
        });
        
        Assert.False(response1.Success);
        Assert.Equal("error", response1.CurrentState);
        
        // 第二次请求应该触发恢复
        var response2 = await service.ProcessRequestAsync(new ServiceRequest
        {
            ConversationId = conversationId,
            Message = "重新开始"
        });
        
        Assert.True(response2.Success);
        Assert.Equal("idle", response2.CurrentState);
    }
}

九、总结与下一步

通过本文的学习,我们已经掌握了多轮对话与状态管理的核心技术:

  1. 对话状态管理:内存和持久化状态管理实现
  2. 状态机设计:复杂对话流程的状态机模式
  3. 上下文优化:Token限制管理和记忆压缩
  4. 错误处理:健壮的对话错误恢复机制
  5. 性能监控:对话质量和性能评估
  6. 完整示例:智能客服系统的实现

关键收获:

  • 状态机是管理复杂对话流程的最佳模式
  • 上下文窗口管理直接影响对话质量
  • 错误处理和恢复是生产系统的关键
  • 性能监控帮助优化对话体验

下一篇文章预告:
在第五篇文章中,我们将探索记忆与持久化。智能体不仅需要在单次对话中保持状态,还需要在多次对话间保持记忆,实现真正的个性化服务。

我们将学习:

  • 短期记忆和长期记忆的实现
  • 向量数据库集成用于记忆检索
  • 个性化记忆管理策略
  • 隐私保护和数据安全

这将让我们的智能体真正"认识"用户,提供更加个性化和连贯的服务体验。


实践建议:

  1. 从简单的状态机开始,逐步增加复杂性
  2. 监控对话质量,持续优化状态转移逻辑
  3. 实现完善的错误处理和恢复机制
  4. 考虑用户的隐私和数据安全

相关资源:


"好的对话系统不是回答每个问题,而是理解整个对话,并在合适的时机提供合适的帮助。"

posted @ 2026-03-13 10:22  饭勺oO  阅读(3267)  评论(0)    收藏  举报