智能聊天机器人 #
项目概述 #
本节将构建一个完整的智能聊天机器人,具备以下功能:
- 多轮对话管理
- 上下文记忆
- 流式输出
- 用户偏好学习
- 函数调用能力
架构设计 #
text
┌─────────────────────────────────────────────────────────────┐
│ 聊天机器人架构 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 前端 UI │────▶│ Web API │────▶│ Kernel │ │
│ └─────────────┘ └─────────────┘ └──────┬──────┘ │
│ │ │
│ ┌────────────────────────────────────────┤ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Plugins │ │ Memory │ │ History │ │
│ │ (工具插件) │ │ (记忆系统) │ │ (对话历史) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
项目结构 #
text
ChatBot/
├── Controllers/
│ └── ChatController.cs
├── Services/
│ ├── IChatService.cs
│ ├── ChatService.cs
│ ├── IHistoryService.cs
│ └── HistoryService.cs
├── Plugins/
│ ├── WeatherPlugin.cs
│ ├── CalculatorPlugin.cs
│ └── SearchPlugin.cs
├── Models/
│ ├── ChatRequest.cs
│ ├── ChatResponse.cs
│ └── ChatMessage.cs
├── Hubs/
│ └── ChatHub.cs
└── Program.cs
核心代码实现 #
1. 服务接口 #
csharp
public interface IChatService
{
Task<ChatResponse> ChatAsync(string userId, string message);
IAsyncEnumerable<string> ChatStreamAsync(string userId, string message);
Task ClearHistoryAsync(string userId);
}
2. 聊天服务实现 #
csharp
public class ChatService : IChatService
{
private readonly Kernel _kernel;
private readonly IChatCompletionService _chatService;
private readonly IHistoryService _historyService;
private readonly SemanticTextMemory _memory;
private readonly ILogger<ChatService> _logger;
public ChatService(
Kernel kernel,
IHistoryService historyService,
SemanticTextMemory memory,
ILogger<ChatService> logger)
{
_kernel = kernel;
_chatService = kernel.GetRequiredService<IChatCompletionService>();
_historyService = historyService;
_memory = memory;
_logger = logger;
}
public async Task<ChatResponse> ChatAsync(string userId, string message)
{
var chatHistory = await BuildChatHistoryAsync(userId, message);
var settings = new OpenAIPromptExecutionSettings
{
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
};
var response = await _chatService.GetChatMessageContentAsync(
chatHistory,
settings,
_kernel
);
await SaveConversationAsync(userId, message, response.Content!);
await LearnUserPreferencesAsync(userId, message, response.Content!);
return new ChatResponse
{
Message = response.Content!,
Timestamp = DateTime.UtcNow
};
}
public async IAsyncEnumerable<string> ChatStreamAsync(
string userId,
string message)
{
var chatHistory = await BuildChatHistoryAsync(userId, message);
var settings = new OpenAIPromptExecutionSettings
{
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
};
var fullResponse = new StringBuilder();
await foreach (var chunk in _chatService.GetStreamingChatMessageContentsAsync(
chatHistory, settings, _kernel))
{
if (chunk.Content != null)
{
fullResponse.Append(chunk.Content);
yield return chunk.Content;
}
}
await SaveConversationAsync(userId, message, fullResponse.ToString());
}
private async Task<ChatHistory> BuildChatHistoryAsync(string userId, string message)
{
var chatHistory = new ChatHistory();
chatHistory.AddSystemMessage(await BuildSystemPromptAsync(userId));
var history = await _historyService.GetHistoryAsync(userId, limit: 10);
foreach (var msg in history)
{
chatHistory.AddUserMessage(msg.UserMessage);
chatHistory.AddAssistantMessage(msg.AssistantMessage);
}
var relevantMemories = _memory.SearchAsync(
"user-preferences",
message,
limit: 3
);
var contextBuilder = new StringBuilder();
await foreach (var memory in relevantMemories)
{
contextBuilder.AppendLine($"- {memory.Metadata.Text}");
}
if (contextBuilder.Length > 0)
{
chatHistory.AddSystemMessage($"用户偏好信息:\n{contextBuilder}");
}
chatHistory.AddUserMessage(message);
return chatHistory;
}
private async Task<string> BuildSystemPromptAsync(string userId)
{
var userProfile = await _historyService.GetUserProfileAsync(userId);
return $"""
你是一个友好的 AI 助手。
用户信息:
- 用户ID: {userId}
- 对话次数: {userProfile?.ConversationCount ?? 0}
你的职责:
1. 友好地回答用户问题
2. 记住用户的偏好和重要信息
3. 在需要时使用工具帮助用户
4. 保持对话的连贯性
""";
}
private async Task SaveConversationAsync(
string userId,
string userMessage,
string assistantMessage)
{
await _historyService.SaveMessageAsync(userId, userMessage, assistantMessage);
}
private async Task LearnUserPreferencesAsync(
string userId,
string userMessage,
string assistantMessage)
{
var extractPrompt = $"""
从以下对话中提取用户的偏好或重要信息。
如果没有重要信息,返回"无"。
用户: {userMessage}
助手: {assistantMessage}
""";
var info = await _kernel.InvokePromptAsync(extractPrompt);
if (info.ToString() != "无")
{
await _memory.SaveInformationAsync(
"user-preferences",
text: info.ToString(),
id: $"{userId}-{Guid.NewGuid()}"
);
}
}
public async Task ClearHistoryAsync(string userId)
{
await _historyService.ClearHistoryAsync(userId);
}
}
3. 插件实现 #
csharp
public class WeatherPlugin
{
private readonly IWeatherService _weatherService;
public WeatherPlugin(IWeatherService weatherService)
{
_weatherService = weatherService;
}
[KernelFunction("get_weather")]
[Description("获取指定城市的天气信息")]
public async Task<string> GetWeatherAsync(
[Description("城市名称")] string city)
{
var weather = await _weatherService.GetWeatherAsync(city);
return $"{city}天气:{weather.Condition},气温{weather.Temperature}°C";
}
}
public class CalculatorPlugin
{
[KernelFunction("calculate")]
[Description("执行数学计算")]
public double Calculate(
[Description("数学表达式")] string expression)
{
try
{
var result = new DataTable().Compute(expression, null);
return Convert.ToDouble(result);
}
catch
{
throw new KernelException("无法计算该表达式");
}
}
}
public class SearchPlugin
{
private readonly ISearchService _searchService;
public SearchPlugin(ISearchService searchService)
{
_searchService = searchService;
}
[KernelFunction("web_search")]
[Description("搜索网络信息")]
public async Task<string> SearchAsync(
[Description("搜索关键词")] string query)
{
var results = await _searchService.SearchAsync(query);
return string.Join("\n", results.Select(r => $"- {r.Title}: {r.Snippet}"));
}
}
4. API 控制器 #
csharp
[ApiController]
[Route("api/[controller]")]
public class ChatController : ControllerBase
{
private readonly IChatService _chatService;
public ChatController(IChatService chatService)
{
_chatService = chatService;
}
[HttpPost]
public async Task<ActionResult<ChatResponse>> Chat([FromBody] ChatRequest request)
{
var response = await _chatService.ChatAsync(request.UserId, request.Message);
return Ok(response);
}
[HttpPost("stream")]
public async Task StreamChat([FromBody] ChatRequest request)
{
Response.Headers.Append("Content-Type", "text/event-stream");
Response.Headers.Append("Cache-Control", "no-cache");
await foreach (var chunk in _chatService.ChatStreamAsync(
request.UserId, request.Message))
{
await Response.WriteAsync($"data: {chunk}\n\n");
await Response.Body.FlushAsync();
}
await Response.WriteAsync("data: [DONE]\n\n");
}
[HttpDelete("history/{userId}")]
public async Task<IActionResult> ClearHistory(string userId)
{
await _chatService.ClearHistoryAsync(userId);
return NoContent();
}
}
5. SignalR Hub #
csharp
public class ChatHub : Hub
{
private readonly IChatService _chatService;
public ChatHub(IChatService chatService)
{
_chatService = chatService;
}
public async IAsyncEnumerable<string> StreamChat(
string userId,
string message,
[EnumeratorCancellation] CancellationToken cancellationToken)
{
await foreach (var chunk in _chatService.ChatStreamAsync(userId, message))
{
cancellationToken.ThrowIfCancellationRequested();
yield return chunk;
}
}
}
6. 程序配置 #
csharp
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddSignalR();
builder.Services.AddKernel()
.AddAzureOpenAIChatCompletion(
deploymentName: builder.Configuration["AzureOpenAI:DeploymentName"]!,
endpoint: builder.Configuration["AzureOpenAI:Endpoint"]!,
apiKey: builder.Configuration["AzureOpenAI:ApiKey"]!
)
.AddOpenAITextEmbeddingGeneration(
modelId: "text-embedding-3-small",
apiKey: builder.Configuration["OpenAI:ApiKey"]!
);
builder.Services.AddSingleton<ISearchService, SearchService>();
builder.Services.AddSingleton<IWeatherService, WeatherService>();
builder.Services.AddScoped<IHistoryService, HistoryService>();
builder.Services.AddScoped<IChatService, ChatService>();
builder.Services.AddSingleton<SemanticTextMemory>(sp =>
{
var embeddingService = sp.GetRequiredService<ITextEmbeddingGenerationService>();
return new SemanticTextMemory(new VolatileMemoryStore(), embeddingService);
});
builder.Services.AddTransient<WeatherPlugin>();
builder.Services.AddTransient<CalculatorPlugin>();
builder.Services.AddTransient<SearchPlugin>();
var app = builder.Build();
app.MapControllers();
app.MapHub<ChatHub>("/chat");
app.Run();
前端示例 #
html
<!DOCTYPE html>
<html>
<head>
<title>智能聊天机器人</title>
<script src="https://cdn.jsdelivr.net/npm/microsoft/signalr"></script>
</head>
<body>
<div id="chat-container">
<div id="messages"></div>
<input type="text" id="user-input" placeholder="输入消息...">
<button onclick="sendMessage()">发送</button>
</div>
<script>
const connection = new signalR.HubConnectionBuilder()
.withUrl("/chat")
.build();
let currentAssistantMessage = "";
connection.on("ReceiveMessage", (chunk) => {
if (chunk === "[DONE]") {
addMessage("assistant", currentAssistantMessage);
currentAssistantMessage = "";
return;
}
currentAssistantMessage += chunk;
updateLastMessage(currentAssistantMessage);
});
async function sendMessage() {
const input = document.getElementById("user-input");
const message = input.value;
if (!message) return;
addMessage("user", message);
input.value = "";
await connection.invoke("StreamChat", "user-1", message);
}
function addMessage(role, content) {
const div = document.createElement("div");
div.className = `message ${role}`;
div.textContent = content;
document.getElementById("messages").appendChild(div);
}
function updateLastMessage(content) {
const messages = document.getElementById("messages");
const lastMessage = messages.lastElementChild;
if (lastMessage && lastMessage.classList.contains("assistant")) {
lastMessage.textContent = content;
} else {
addMessage("assistant", content);
}
}
connection.start();
</script>
</body>
</html>
最佳实践 #
1. 错误处理 #
csharp
public async Task<ChatResponse> ChatAsync(string userId, string message)
{
try
{
// 处理逻辑
}
catch (HttpOperationException ex)
{
_logger.LogError(ex, "API 调用失败");
return new ChatResponse
{
Message = "抱歉,服务暂时不可用,请稍后再试。",
Timestamp = DateTime.UtcNow
};
}
catch (Exception ex)
{
_logger.LogError(ex, "处理消息失败");
throw;
}
}
2. 限流保护 #
csharp
public class RateLimitingFilter : IFunctionFilter
{
private readonly IMemoryCache _cache;
private readonly int _maxRequestsPerMinute;
public async Task OnFunctionInvocationAsync(
FunctionInvocationContext context,
Func<FunctionInvocationContext, Task> next)
{
var userId = context.Arguments["userId"]?.ToString();
var cacheKey = $"rate_limit_{userId}";
var count = _cache.GetOrCreate(cacheKey, entry =>
{
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(1);
return 0;
});
if (count >= _maxRequestsPerMinute)
{
throw new KernelException("请求过于频繁,请稍后再试");
}
_cache.Set(cacheKey, count + 1);
await next(context);
}
}
下一步 #
现在你已经掌握了聊天机器人的开发,接下来学习 文档问答系统,构建企业级知识库!
最后更新:2026-04-04