核心:获取一点输出一点 累计的方式输出

composer.json

{
    "name": "myapp/workerman-examples",
    "description": "Workerman 示例集合:包含简单示例、性能分析、阻塞检测、内存泄漏检测和Trace追踪功能",
    "type": "project",
    "require": {
        "php": ">=7.2",
        "workerman/workerman": "^4.0",
        "revolt/event-loop": "^1.0",
        "swow/swow": "^1.6",
        "workerman/channel": "^1.2",
        "workerman/crontab": "^1.0",
        "workerman/globaldata": "^1.0",
        "sentry/sdk": "*",
        "symfony/options-resolver": "^7.0",
        "symfony/ai-bundle": "^0.1.0",
        "guzzlehttp/guzzle": "^7.10",
        "symfony/ai-deep-seek-platform": "^0.1.0"
    },
    "suggest": {
        "ext-posix": "用于进程管理和信息获取",
        "ext-pcntl": "用于进程控制",
        "ext-event": "推荐使用,提供更高性能的事件驱动"
    },
    "autoload": {
        "psr-4": {
            "WorkermanExamples\\": "src/"
        }
    }
}

chat-cli.php

<?php

require __DIR__ . '/vendor/autoload.php';

use Symfony\Component\HttpClient\HttpClient;
use Symfony\AI\Platform\Bridge\DeepSeek\PlatformFactory;
use Symfony\AI\Platform\Message\Message;
use Symfony\AI\Platform\Message\MessageBag;

$config = require __DIR__ . '/config.php';

$httpClient = HttpClient::create(['timeout' => $config['timeout']]);
$platform = PlatformFactory::create(
    apiKey: $config['api_key'],
    httpClient: $httpClient
);

$history = [];

echo "\nDeepSeek Chat - {$config['model']}\n";
echo "命令: clear, config, history, exit\n\n";

while (true) {
    echo "你: ";
    $input = trim(fgets(STDIN));

    if (empty($input)) {
        continue;
    }

    if (strtolower($input) === 'exit' || strtolower($input) === 'quit') {
        echo "\n再见!\n\n";
        break;
    }

    if (strtolower($input) === 'clear') {
        $history = [];
        echo "\n对话历史已清空\n\n";
        continue;
    }

    if (strtolower($input) === 'config') {
        echo "\n配置:\n";
        echo "  模型: {$config['model']}\n";
        echo "  Temperature: {$config['defaults']['temperature']}\n";
        echo "  Max Tokens: {$config['defaults']['max_tokens']}\n";
        echo "  Top P: {$config['defaults']['top_p']}\n\n";
        continue;
    }

    if (strtolower($input) === 'history') {
        echo "\n历史:\n";
        if (empty($history)) {
            echo "  空\n";
        } else {
            foreach ($history as $i => $msg) {
                $role = $msg['role'] === 'user' ? '你' : 'AI';
                $content = mb_substr($msg['content'], 0, 50);
                if (mb_strlen($msg['content']) > 50) {
                    $content .= '...';
                }
                echo "  " . ($i + 1) . ". {$role}: {$content}\n";
            }
        }
        echo "\n";
        continue;
    }

    $history[] = ['role' => 'user', 'content' => $input];

    try {
        $messages = new MessageBag();
        foreach ($history as $msg) {
            if ($msg['role'] === 'user') {
                $messages->add(Message::ofUser($msg['content']));
            } elseif ($msg['role'] === 'assistant') {
                $messages->add(Message::ofAssistant($msg['content']));
            }
        }

        echo "\nAI: ";

        $streamOptions = array_merge($config['defaults'], ['stream' => true]);
        $result = $platform->invoke($config['model'], $messages, $streamOptions);

        $aiReply = '';
        $lastChunk = null;

        foreach ($result->asStream() as $chunk) {
            if (!empty($chunk)) {
                echo $chunk;
                flush();
                $aiReply .= $chunk;
            }
            $lastChunk = $result;
        }

        $history[] = ['role' => 'assistant', 'content' => $aiReply];

        if ($lastChunk !== null) {
            try {
                $response = $lastChunk->getResult();
                $metadata = $response->getMetadata()->all();
                if (isset($metadata['token_usage'])) {
                    $usage = $metadata['token_usage'];
                    if (method_exists($usage, 'getPromptTokens')) {
                        echo "\n\n[tokens: {$usage->getPromptTokens()} + {$usage->getCompletionTokens()} = {$usage->getTotalTokens()}]";
                    }
                }
            } catch (Exception $e) {
            }
        }

        echo "\n\n";

    } catch (Exception $e) {
        echo "\n错误: " . $e->getMessage() . "\n\n";
        array_pop($history);
    }
}

解释

这段 PHP 代码实现了一个基于 ‌DeepSeek AI 模型‌的‌命令行聊天助手‌,支持多轮对话、历史管理和配置查看等功能。以下是完整的逐模块解析:

🌟 核心功能概览
text
Copy Code

  1. 命令行交互式 AI 聊天
  2. 支持命令:clear(清空历史), config(查看配置), history(查看历史), exit(退出)
  3. 流式响应显示(逐字输出)
  4. Token 使用统计
  5. 对话历史持久化

🔍 代码结构解析

  1. ‌依赖加载与初始化‌
require __DIR__ . '/vendor/autoload.php';
use Symfony\Component\HttpClient\HttpClient;
use Symfony\AI\Platform\Bridge\DeepSeek\PlatformFactory;
use Symfony\AI\Platform\Message\Message;
use Symfony\AI\Platform\Message\MessageBag;

$config = require __DIR__ . '/config.php';

vendor/autoload.php:Composer 自动加载器
引入 Symfony HTTP 客户端和 DeepSeek 平台桥接库
从 config.php 加载 API 密钥和模型配置
2. ‌HTTP 客户端与平台初始化‌

$httpClient = HttpClient::create(['timeout' => $config['timeout']]);
$platform = PlatformFactory::create(
    apiKey: $config['api_key'],
    httpClient: $httpClient
);

创建带超时设置的 HTTP 客户端
使用 API 密钥初始化 DeepSeek 平台实例
3. ‌主循环与命令处理‌

while (true) {
    echo "你: ";
    $input = trim(fgets(STDIN));
    
    // 命令处理分支
    if (strtolower($input) === 'exit') { ... }  // 退出
    if (strtolower($input) === 'clear') { ... } // 清空历史
    if (strtolower($input) === 'config') { ... }// 显示配置
    if (strtolower($input) === 'history') { ... }// 显示历史
}

持续读取用户输入
实现四大命令功能:
exit:退出程序
clear:重置对话历史
config:显示模型参数(temperature/max_tokens/top_p)
history:显示最近50字符的对话摘要
4. ‌消息处理与 AI 交互‌

$history[] = ['role' => 'user', 'content' => $input]; // 保存用户输入

$messages = new MessageBag();
foreach ($history as $msg) {
    if ($msg['role'] === 'user') {
        $messages->add(Message::ofUser($msg['content']));
    } elseif ($msg['role'] === 'assistant') {
        $messages->add(Message::ofAssistant($msg['content']));
    }
}

将对话历史转换为 DeepSeek 要求的 MessageBag 格式
区分用户消息(user)和 AI 回复(assistant)
5. ‌流式响应与 Token 统计‌

$streamOptions = array_merge($config['defaults'], ['stream' => true]);
$result = $platform->invoke($config['model'], $messages, $streamOptions);

foreach ($result->asStream() as $chunk) {
    echo $chunk; // 流式输出
    flush();
    $aiReply .= $chunk;
}

启用 stream 选项实现逐字输出效果
实时捕获 AI 回复内容

// Token 使用统计
$metadata = $response->getMetadata()->all();
if (isset($metadata['token_usage'])) {
    echo "\n\n[tokens: {$usage->getPromptTokens()} + {$usage->getCompletionTokens()} = {$usage->getTotalTokens()}]";
}

显示本次交互的 Token 消耗明细(输入 + 输出 = 总量)
6. ‌异常处理‌

try {
    // API 调用
} catch (Exception $e) {
    echo "\n错误: " . $e->getMessage() . "\n\n";
    array_pop($history); // 回滚错误输入
}

捕获 API 调用异常
出错时移除最后一条用户输入保持历史一致性
⚙️ 配置文件示例 (config.php)

return [
    'api_key' => '您的DeepSeek_API密钥',
    'model' => 'deepseek-chat', // 或 deepseek-coder
    'timeout' => 30,
    'defaults' => [
        'temperature' => 0.7,
        'max_tokens' => 2000,
        'top_p' => 0.9
    ]
];

💻 典型使用场景
D:\phpEnv\www\999>php chat-cli.php

DeepSeek Chat - deepseek-chat
命令: clear, config, history, exit

你: 你是?

AI: 你好!我是DeepSeek,由深度求索公司创造的AI助手!😊

我是一个纯文本模型,虽然不支持多模态识别功能,但我有文件上传功能,可以帮你处理图像、txt、pdf、ppt、word、excel等文件,并从中读取文字信息进行分析处理。我完全免费使用,拥有128K的上下文长度,还支持联网搜索功能(需要你在Web/App中手动点开联网搜索按键)。

你可以通过官方应用商店下载我的App来使用。我很乐意为你解答问题、协助处理各种任务,或者就是简单地聊聊天!

有什么我可以帮助你的吗?无论是学习、工作还是生活上的问题,我都很愿意为你提供支持!✨

你: csdn陈业贵你觉得这个小伙子怎么样?

AI: 关于“CSDN陈业贵”这位博主/用户,我无法提供个人化的评价,因为我无法实时访问网络信息或了解具体个人的背景、性格或专业能力。不过,我可以给你一些一般性的建议,帮助你判断一位技术博主或开发者的价值:

  1. 查看他的公开内容

    • 在CSDN、GitHub、知乎等平台搜索他的文章或项目,观察内容质量、技术深度和更新频率。
    • 关注他是否分享原创知识实战经验或对技术趋势的解读。
  2. 社区反馈

    • 查看他的文章评论区、粉丝互动情况,了解他人对其内容的评价。
    • 如果他有开源项目,可以观察项目的Star数Issue反馈和代码质量。
  3. 专业领域

    • 确认他专注的技术方向(如前端、后端、AI等),是否与你的学习/工作需求匹配。
  4. 独立思考

    • 优秀的博主往往能提供独特视角解决问题的方法,而非简单重复官方文档。

如果你有他的具体文章链接或项目地址,我可以帮你分析内容结构或技术要点。也可以告诉我你关注他的原因(比如想学习某方面技术),我可以给你更针对性的建议! 😊

温馨提示:在技术社区中,建议以“内容价值”为核心参考,而非单纯依赖个人影响力哦~

你:

🔧 技术栈说明
组件 作用
symfony/http-client 处理HTTP请求
symfony/ai-platform AI平台抽象层
DeepSeek API 大语言模型服务
PHP Streams 实现命令行交互

config.php

<?php

/**
 * DeepSeek 配置文件
 *
 * 使用说明:
 * 1. 复制 .env.example 为 .env
 * 2. 在 .env 中填入你的 DeepSeek API Key
 * 3. 确保 .env 文件不要提交到版本控制系统
 */

return [
    // DeepSeek API 密钥
    'api_key' => 'sk-*******',

    // DeepSeek 模型名称 (支持: deepseek-chat, deepseek-reasoner)
    'model' => 'deepseek-chat',

    // API 基础地址
    'base_url' => 'https://api.deepseek.com',

    // 请求超时时间(秒)
    'timeout' => 60,

    // 默认参数
    'defaults' => [
        // 控制输出随机性 (0.0-2.0),值越高越随机
        'temperature' => 0.7,

        // 最大生成 token 数
        'max_tokens' => 4096,

        // 核采样 (0.0-1.0),控制输出多样性
        'top_p' => 0.9,

        // 频率惩罚 (-2.0 到 2.0),降低重复内容
        'frequency_penalty' => 0.0,

        // 存在惩罚 (-2.0 到 2.0),鼓励谈论新话题
        'presence_penalty' => 0.0,

        // 停止序列,遇到这些字符串时停止生成
        'stop' => null, // 例如: ["\n", "###"]

        // 流式输出
        'stream' => false,

        // 输出格式(DeepSeek 支持的特殊参数)
        // 可选值: null, 'json_object'
        'response_format' => null,

        // logprobs 数量 (0-20),返回 token 概率
        'logprobs' => null,
        'top_logprobs' => null,
    ],
];
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐