如何将多轮对话与状态管理整合为一个?
摘要:多轮对话与状态管理 前言 在前三篇文章中,我们创建了能够理解问题、执行工具的智能体。但真实的对话往往是多轮的、有状态的复杂交互。想象一下这样的场景: 用户:"我想订一张去北京的机票。" 助
多轮对话与状态管理
前言
在前三篇文章中,我们创建了能够理解问题、执行工具的智能体。但真实的对话往往是多轮的、有状态的复杂交互。想象一下这样的场景:
用户:"我想订一张去北京的机票。"
助手:"好的,请问您计划什么时间出发?"
用户:"下周五。"
助手:"请问从哪里出发?"
用户:"上海。"
助手:"经济舱还是商务舱?"
用户:"经济舱。"
助手:"已为您搜索到3个航班,价格分别是..."
这样的对话需要智能体能够:
记住上下文:知道用户在预订机票
管理对话状态:跟踪已收集的信息(目的地、时间)
引导对话流程:询问缺失的必要信息
处理异常情况:用户改变主意、提供错误信息等
这就是多轮对话与状态管理的核心。今天,我们将深入探索如何在Agent Framework中实现智能的对话状态管理。
一、对话状态管理基础
1.1 对话状态的核心要素
一个完整的对话状态通常包含以下要素:
对话历史(Conversation History)
用户和助手的交互记录
每轮对话的内容和时间戳
对话的上下文信息
当前意图(Current Intent)
用户当前的对话目的
例如:预订机票、查询天气、技术支持
已收集信息(Collected Information)
在多轮对话中收集的数据
例如:目的地、出发时间、舱位等级
对话阶段(Conversation Stage)
对话所处的流程阶段
例如:信息收集、确认、执行、完成
用户偏好(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);
}
}
九、总结与下一步
通过本文的学习,我们已经掌握了多轮对话与状态管理的核心技术:
✅ 对话状态管理:内存和持久化状态管理实现
✅ 状态机设计:复杂对话流程的状态机模式
✅ 上下文优化:Token限制管理和记忆压缩
✅ 错误处理:健壮的对话错误恢复机制
✅ 性能监控:对话质量和性能评估
✅ 完整示例:智能客服系统的实现
关键收获:
状态机是管理复杂对话流程的最佳模式
上下文窗口管理直接影响对话质量
错误处理和恢复是生产系统的关键
性能监控帮助优化对话体验
下一篇文章预告:
在第五篇文章中,我们将探索记忆与持久化。智能体不仅需要在单次对话中保持状态,还需要在多次对话间保持记忆,实现真正的个性化服务。
我们将学习:
短期记忆和长期记忆的实现
向量数据库集成用于记忆检索
个性化记忆管理策略
隐私保护和数据安全
这将让我们的智能体真正"认识"用户,提供更加个性化和连贯的服务体验。
实践建议:
从简单的状态机开始,逐步增加复杂性
监控对话质量,持续优化状态转移逻辑
实现完善的错误处理和恢复机制
考虑用户的隐私和数据安全
相关资源:
Agent Framework对话管理文档
状态机设计模式
对话系统设计最佳实践
"好的对话系统不是回答每个问题,而是理解整个对话,并在合适的时机提供合适的帮助。"
