本文目录导读:

在PHP项目中向大模型传递对话上下文,核心是维护一个结构化的消息数组,并按大模型的接口规范(通常是OpenAI格式)进行拼接,以下是几种常见的实现方案:
核心数据结构(OpenAI格式)
// 对话历史结构
$messages = [
['role' => 'system', 'content' => '你是一个AI助手'],
['role' => 'user', 'content' => '你好'],
['role' => 'assistant', 'content' => '你好!有什么可以帮助你的?'],
['role' => 'user', 'content' => '今天天气怎么样']
];
上下文存储方案
Session存储(简单场景)
session_start();
// 初始化或恢复对话
if (!isset($_SESSION['messages'])) {
$_SESSION['messages'] = [
['role' => 'system', 'content' => '你是一个专业的技术支持']
];
}
// 添加用户新消息
$_SESSION['messages'][] = [
'role' => 'user',
'content' => $_POST['message']
];
// 调用大模型
$response = callLLM($_SESSION['messages']);
// 保存AI回复
$_SESSION['messages'][] = [
'role' => 'assistant',
'content' => $response
];
// 限制上下文长度(避免Token溢出)
function trimContext(&$messages, $maxLength = 20) {
if (count($messages) > $maxLength) {
// 保留系统提示和最近的消息
$system = array_shift($messages);
$messages = array_slice($messages, -$maxLength + 1);
array_unshift($messages, $system);
}
}
数据库存储(持久化)
// 数据库表结构
// conversations: id, user_id, created_at
// messages: id, conversation_id, role, content, created_at
class ConversationManager {
private $db;
public function getContext($conversationId, $maxTokens = 4000) {
$messages = $this->db->query(
"SELECT role, content FROM messages
WHERE conversation_id = ?
ORDER BY created_at ASC",
[$conversationId]
);
// 计算token大致数量并裁剪
return $this->trimToTokenLimit($messages, $maxTokens);
}
private function trimToTokenLimit($messages, $maxTokens) {
$totalTokens = 0;
$trimmed = [];
// 从最新的消息开始保留
foreach (array_reverse($messages) as $msg) {
$tokens = strlen($msg['content']) / 4; // 粗略估算
if ($totalTokens + $tokens > $maxTokens) break;
$totalTokens += $tokens;
array_unshift($trimmed, $msg);
}
// 确保有系统提示
if ($messages[0]['role'] === 'system' && !in_array($messages[0], $trimmed)) {
array_unshift($trimmed, $messages[0]);
}
return $trimmed;
}
}
Redis缓存(高性能)
class RedisConversation {
private $redis;
private $prefix = 'conversation:';
public function addMessage($sessionId, $message) {
$key = $this->prefix . $sessionId;
// 使用List存储消息
$this->redis->rPush($key, json_encode($message));
// 设置过期时间(如1小时)
$this->redis->expire($key, 3600);
// 限制消息数量
$length = $this->redis->lLen($key);
if ($length > 50) { // 保留最近50条
$this->redis->lPop($key);
}
}
public function getContext($sessionId) {
$key = $this->prefix . $sessionId;
$messages = $this->redis->lRange($key, 0, -1);
return array_map(function($msg) {
return json_decode($msg, true);
}, $messages);
}
}
Token优化策略
智能上下文压缩
class ContextOptimizer {
// 摘要历史对话
public static function summarize($messages, $llmClient) {
$recentMessages = array_slice($messages, -10); // 保留最近10条
$historyToSummarize = array_slice($messages, 0, -10);
if (empty($historyToSummarize)) return $messages;
// 生成摘要
$summaryPrompt = [
['role' => 'system', 'content' => '请用中文总结以下对话的核心内容和关键信息:'],
['role' => 'user', 'content' => json_encode($historyToSummarize)]
];
$summary = $llmClient->chat($summaryPrompt);
// 构建新的上下文
return array_merge(
[['role' => 'system', 'content' => "历史对话摘要:{$summary}"]],
$recentMessages
);
}
// 滑动窗口裁剪
public static function slidingWindow($messages, $maxTokens = 4000) {
$systemPrompt = null;
if ($messages[0]['role'] === 'system') {
$systemPrompt = array_shift($messages);
}
$totalTokens = 0;
$context = [];
// 从最新消息开始添加
foreach (array_reverse($messages) as $msg) {
$tokens = self::estimateTokens(json_encode($msg));
if ($totalTokens + $tokens > $maxTokens) break;
$totalTokens += $tokens;
array_unshift($context, $msg);
}
if ($systemPrompt) {
array_unshift($context, $systemPrompt);
}
return $context;
}
private static function estimateTokens($text) {
// 更精确的token估算
$words = preg_split('/\s+/', $text);
$chars = mb_strlen($text, 'UTF-8');
// 中文字符约占1.5 tokens,英文单词约1 token
return ceil($chars * 0.75 + count($words) * 0.25);
}
}
完整调用示例
class LLMClient {
private $apiKey;
private $apiUrl = 'https://api.openai.com/v1/chat/completions';
public function chat($messages) {
$data = [
'model' => 'gpt-3.5-turbo',
'messages' => $messages,
'temperature' => 0.7
];
$ch = curl_init($this->apiUrl);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $this->apiKey,
'Content-Type: application/json'
],
CURLOPT_POSTFIELDS => json_encode($data),
CURLOPT_RETURNTRANSFER => true
]);
$response = curl_exec($ch);
return json_decode($response, true)['choices'][0]['message']['content'];
}
}
// 使用示例
$userId = $_SESSION['user_id'];
$conversationManager = new ConversationManager($db);
$optimizer = new ContextOptimizer();
// 获取历史上下文
$messages = $conversationManager->getContext($userId);
// 添加用户新消息
$messages[] = ['role' => 'user', 'content' => $_POST['message']];
// 优化上下文(如果必要)
if (count($messages) > 30) {
$messages = $optimizer->slidingWindow($messages, 4000);
}
// 调用大模型
$llm = new LLMClient();
$response = $llm->chat($messages);
// 保存AI回复
$messages[] = ['role' => 'assistant', 'content' => $response];
$conversationManager->saveMessages($userId, $messages);
注意事项
- Token限制:大模型有上下文窗口限制(如4K/8K/16K),需要合理裁剪
- 角色标识:确保
role字段正确(system/user/assistant) - 会话隔离:不同用户用不同sessionId/ConversationId
- 安全过滤:对用户输入进行XSS、SQL注入等安全过滤
- 性能考虑:长对话建议使用Redis或异步处理
选择哪种方案取决于你的具体需求:简单的单用户场景用Session,需要持久化的用数据库,高并发场景用Redis。