diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c5a6b38 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +build +vendor +tests/TestSupport/temp +tests/temp +composer.lock +phpunit.xml +.env +.phpunit.cache/ +.phpunit.result.cache +.phpunit.cache/test-results +.php-cs-fixer.cache +phpstan.neon +tests/Support/temp/ +.idea/ diff --git a/MIGRATION.md b/MIGRATION.md new file mode 100644 index 0000000..ccb5cb4 --- /dev/null +++ b/MIGRATION.md @@ -0,0 +1,31 @@ +# CHANGELOG + +## [2.x] - 2025-02-01 + +If you are upgrading from a version `1.x` to `2.x`, please perform the following steps: + +### Breaking Changes + +### 1. Namespace Change +- **Old Namespace:** `DeepseekPhp` +- **New Namespace:** `DeepSeek` + +**Action Required:** +Update all imports in your codebase. + +##### Replace: +```php +use DeepseekPhp\Someclass; +``` + +##### With: +```php +use DeepSeek\Someclass; +``` + +### Migration Guide +1. Replace all occurrences of `DeepseekPhp` with `DeepSeek` in your code. +3. Run tests to ensure everything works as expected. + +If you encounter issues, please refer to our documentation or open an issue on GitHub. + diff --git a/README-AR.md b/README-AR.md new file mode 100644 index 0000000..cccbfc0 --- /dev/null +++ b/README-AR.md @@ -0,0 +1,306 @@ +

+

عميل DeepSeek بلغة PHP

+

🚀 حزمة PHP مفتوحة المصدر ومدعومة من المجتمع للتكامل مع واجهة DeepSeek API

+ +

+ + أحدث إصدار + + + إجمالي التحميلات + + + إصدار PHP + + + الترخيص + + + النجوم على GitHub + +

+ +[الإنجليزية](README.md) | [الصينية](README-CN.md) + +## فهرس المحتويات +- [✨ المميزات](#-المميزات) +- [📦 التثبيت](#-التثبيت) +- [🚀 البداية السريعة](#-البداية-السريعة) + - [الاستخدام الأساسي](#الاستخدام-الأساسي) + - [التكوين المتقدم](#التكوين-المتقدم) + - [تحذير هام عند استخدام وضع JSON](#-متطلب-وضع-json-في-deepseek) + - [الاستخدام مع عميل HTTP من Symfony](#الاستخدام-مع-عميل-http-من-symfony) + - [الحصول على قائمة النماذج](#الحصول-على-قائمة-النماذج) + - [استدعاء الدوال](#استدعاء-الدوال) + - [تكامل مع الأطر](#-تكامل-مع-الأطر) +- [🆕 دليل الترحيل](#-دليل-الترحيل) +- [📝 سجل التغييرات](#-سجل-التغييرات) +- [🧪 الاختبارات](#-الاختبارات) +- [🔒 الأمان](#-الأمان) +- [🤝 المساهمين](#-المساهمين) +- [📄 الرخصة](#-الرخصة) + +--- + +## ✨ المميزات + +- **تكامل API سلس**: واجهة تعتمد على PHP لميزات الذكاء الاصطناعي في DeepSeek. +- **نمط الباني السلس**: أساليب قابلة للسلسلة لبناء الطلبات بطريقة بديهية. +- **جاهز للمؤسسات**: تكامل مع عميل HTTP متوافق مع PSR-18. +- **مرونة النماذج**: دعم لعدة نماذج من DeepSeek (Coder, Chat, وغيرها). +- **جاهز للبث**: دعم مدمج للتعامل مع الردود في الوقت الفعلي. +- **العديد من عملاء HTTP**: يمكنك استخدام عميل `Guzzle http client` (افتراضي) أو `symfony http client` بسهولة. +- **متوافق مع الأطر**: حزم Laravel و Symfony متاحة. + +--- + +## 📦 التثبيت + +قم بتثبيت الحزمة عبر Composer: + +```bash +composer require deepseek-php/deepseek-php-client +``` + +**المتطلبات**: +- PHP 8.1+ + +--- + +## 🚀 البداية السريعة + +### الاستخدام الأساسي + +ابدأ مع سطرين من الكود فقط: + +```php +use DeepSeek\DeepSeekClient; + +$response = DeepSeekClient::build('your-api-key') + ->query('Explain quantum computing in simple terms') + ->run(); + +echo $response; +``` + +📌 الإعدادات الافتراضية المستخدمة: +- النموذج: `deepseek-chat` +- الحرارة: 0.8 + +### التكوين المتقدم + +```php +use DeepSeek\DeepSeekClient; +use DeepSeek\Enums\Models; + +$client = DeepSeekClient::build(apiKey:'your-api-key', baseUrl:'https://api.deepseek.com/v3', timeout:30, clientType:'guzzle'); + +$response = $client + ->withModel(Models::CODER->value) + ->withStream() + ->withTemperature(1.2) + ->setMaxTokens(8192) + ->setResponseFormat('text') + ->query('Explain quantum computing in simple terms') + ->run(); + +echo 'API Response:'.$response; +``` + + +## ⚠️ متطلب وضع JSON في DeepSeek + +عند استخدام: + +```php +->setResponseFormat('json_object') +``` + +يجب أن يحتوي الـ برومبت على **كلمة "json"** بشكل واضح. + +وإلا سيتم رفض الطلب من قبل وترجع رسالة الخطأ التالية: + +> `"Prompt must contain the word 'json' in some form to use 'response_format' of type 'json_object'"` + +--- + +### 🚫 استخدام غير صحيح + +```php +->setResponseFormat('json_object') +->query('اشرح الحوسبة الكمومية بطريقة مبسطة') +``` + +### ✅ استخدام صحيح + +```php +->setResponseFormat('json_object') +->query('أجب بصيغة JSON صحيحة. اشرح الحوسبة الكمومية بطريقة مبسطة.') +``` + +> ✅ **نصيحة**: للحصول على أفضل النتائج، قم أيضًا بإعطاء مثال على صيغة JSON في الرسالة. + +--- + +### الاستخدام مع عميل HTTP من Symfony +الحزمة مبنية مسبقاً مع `symfony Http client`، فإذا كنت بحاجة إلى استخدامها مع عميل HTTP الخاص بـ Symfony، فيمكن تحقيق ذلك بسهولة عن طريق تمرير `clientType:'symfony'` إلى دالة `build`. + +مثال باستخدام Symfony: + +```php +// مع القيم الافتراضية للـ baseUrl و timeout +$client = DeepSeekClient::build('your-api-key', clientType:'symfony') +// مع التخصيص +$client = DeepSeekClient::build(apiKey:'your-api-key', baseUrl:'https://api.deepseek.com/v3', timeout:30, clientType:'symfony'); + +$client->query('Explain quantum computing in simple terms') + ->run(); +``` + +### الحصول على قائمة النماذج + +```php +use DeepSeek\DeepSeekClient; + +$response = DeepSeekClient::build('your-api-key') + ->getModelsList() + ->run(); + +echo $response; // {"object":"list","data":[{"id":"deepseek-chat","object":"model","owned_by":"deepseek"},{"id":"deepseek-reasoner","object":"model","owned_by":"deepseek"}]} +``` + +### استدعاء الدوال + +يتيح **استدعاء الدوال** للنموذج استدعاء أدوات خارجية لتعزيز قدراته. +يمكنك الرجوع إلى الوثائق الخاصة باستدعاء الدوال في الملف: +[FUNCTION-CALLING.md](docs/FUNCTION-CALLING.md) + +--- + +هل ترغب في أن أضع النسخ الثلاث (الإنجليزية + العربية + الصينية) ضمن ملف Markdown موحد؟ + + +### 🛠 تكامل مع الأطر + +### [حزمة Deepseek لـ Laravel](https://github.com/deepseek-php/deepseek-laravel) + +--- + +## 🚧 دليل الترحيل + +هل تقوم بالترقية من الإصدار v1.x؟ اطلع على دليل الترحيل الشامل الخاص بنا للتغييرات الجذرية وتعليمات الترقية. + +--- + +## 📝 سجل التغييرات + +ملاحظات الإصدار التفصيلية متوفرة في [CHANGELOG.md](CHANGELOG.md) + +--- + +## 🧪 الاختبارات + +```bash +./vendor/bin/pest +``` + +تغطية الاختبارات ستتوفر في الإصدار v2.1. + +--- +
+ +# 🐘✨ **مجتمع DeepSeek PHP** ✨🐘 + +انقر على الزر أدناه أو [انضم هنا](https://t.me/deepseek_php_community) لتكون جزءًا من مجتمعنا المتنامي! + +[![Join Telegram](https://img.shields.io/badge/Join-Telegram-blue?style=for-the-badge&logo=telegram)](https://t.me/deepseek_php_community) + + +### **هيكل القناة** 🏗️ +- 🗨️ **عام** - دردشة يومية +- 💡 **الأفكار والاقتراحات** - تشكيل مستقبل المجتمع +- 📢 **الإعلانات والأخبار** - التحديثات والأخبار الرسمية +- 🚀 **الإصدارات والتحديثات** - تتبع الإصدارات ودعم الترحيل +- 🐞 **المشاكل وتقارير الأخطاء** - حل مشكلات جماعي +- 🤝 **طلبات السحب** - التعاون والمراجعة البرمجية + +
+ +--- + +## 🔒 الأمان + +**الإبلاغ عن الثغرات**: إلى [omaralwi2010@gmail.com](mailto:omaralwi2010@gmail.com) + +--- + +## 🤝 المساهمين + +شكراً جزيلاً لهؤلاء الأشخاص المذهلين الذين ساهموا في هذا المشروع! 🎉💖 + + + + + + + + + + +
+ + Omar AlAlwi +
+ Omar AlAlwi +
+
+ 🏆 المُنشئ +
+ + Ayman Alhattami +
+ Ayman Alhattami +
+
+ ⭐ مساهم +
+ + Mohammad Asaad +
+ Mohammad Asaad +
+
+ ⭐ مساهم +
+ + Opada Alzaiede +
+ Opada Alzaiede +
+
+ ⭐ مساهم +
+ + Hisham Bin Ateya +
+ Hisham Bin Ateya +
+
+ ⭐ مساهم +
+ + Vinchan +
+ Vinchan +
+
+ ⭐ مساهم +
+ +**هل ترغب في المساهمة؟** اطلع على [إرشادات المساهمة](./CONTRIBUTING.md) وقدم طلب سحب! 🚀 + +--- + +## 📄 الرخصة + +هذه الحزمة هي برنامج مفتوح المصدر مرخص بموجب [رخصة MIT](LICENSE.md). diff --git a/README-CN.md b/README-CN.md new file mode 100644 index 0000000..0458b5c --- /dev/null +++ b/README-CN.md @@ -0,0 +1,295 @@ +

+

DeepSeek PHP 客户端

+

🚀 由社区驱动的 PHP SDK,用于集成 DeepSeek AI API

+ +

+ + 最新版本 + + + 总下载次数 + + + PHP 版本 + + + 许可证 + + + GitHub 收藏数 + +

+ +[EN](README.md) | [AR](README-AR.md) + +## 目录 +- [✨ 特性](#-特性) +- [📦 安装](#-安装) +- [🚀 快速入门](#-快速入门) + - [基本用法](#基本用法) + - [高级配置](#高级配置) + - [使用 JSON 模式的重要警告](#-deepseek-json-模式使用要求) + - [使用 Symfony HttpClient](#使用-symfony-httpclient) + - [获取模型列表](#获取模型列表) + - [函数调用](#函数调用) + - [框架集成](#-框架集成) +- [🆕 迁移指南](#-迁移指南) +- [📝 更新日志](#-更新日志) +- [🧪 测试](#-测试) +- [🔒 安全](#-安全) +- [🤝 贡献者](#-贡献者) +- [📄 许可](#-许可) + +--- + +## ✨ 特性 + +- **无缝 API 集成**: DeepSeek AI 功能的 PHP 优先接口 +- **构建器模式**: 直观的链接请求构建方法 +- **企业级别**: 符合 PSR-18 规范 +- **模型灵活性**: 支持多种 DeepSeek 模型(Coder、Chat 等) +- **流式传输**: 内置对实时响应处理的支持 +- **框架友好**: 提供 Laravel 和 Symfony 包 + +--- + +## 📦 安装 + +通过 Composer 安装: + +```bash +composer require deepseek-php/deepseek-php-client +``` + +**要求**: +- PHP 8.1+ + +--- + +## 🚀 快速入门 + +### 基本用法 + +只需两行代码即可开始: + +```php +use DeepSeek\DeepSeekClient; + +$response = DeepSeekClient::build('your-api-key') + ->query('Explain quantum computing in simple terms') + ->run(); + +echo $response; +``` + +📌 默认配置: +- Model: `deepseek-chat` +- Temperature: 0.8 + +### Advanced Configuration + +```php +use DeepSeek\DeepSeekClient; +use DeepSeek\Enums\Models; + +$client = DeepSeekClient::build(apiKey:'your-api-key', baseUrl:'https://api.deepseek.com/v3', timeout:30, clientType:'guzzle'); + +$response = $client + ->withModel(Models::CODER->value) + ->withStream() + ->withTemperature(1.2) + ->setMaxTokens(8192) + ->setResponseFormat('text') + ->query('Explain quantum computing in simple terms') + ->run(); + +echo 'API Response:'.$response; +``` + + +## ⚠️ DeepSeek JSON 模式使用要求 + +当使用: + +```php +->setResponseFormat('json_object') +``` + +你的提示语(prompt)**必须包含 "json" 这个词**,否则 API 会返回以下错误: + +> `"Prompt must contain the word 'json' in some form to use 'response_format' of type 'json_object'"` + +--- + +### 🚫 错误示例 + +```php +->setResponseFormat('json_object') +->query('用简单的语言解释量子计算') +``` + +### ✅ 正确示例 + +```php +->setResponseFormat('json_object') +->query('请以有效的 JSON 格式回答,并用简单语言解释量子计算。') +``` + +> ✅ **建议**:为了获得更好的结果,最好也在提示中提供一个 JSON 示例,并强调 “只返回 JSON”。 + + +--- + +### Use with Symfony HttpClient +the package already built with `symfony Http client`, if you need to use package with `symfony` Http Client , it is easy to achieve that, just pass `clientType:'symfony'` with `build` function. + +ex with symfony: + +```php +// with defaults baseUrl and timeout +$client = DeepSeekClient::build('your-api-key', clientType:'symfony') +// with customization +$client = DeepSeekClient::build(apiKey:'your-api-key', baseUrl:'https://api.deepseek.com/v3', timeout:30, clientType:'symfony'); + +$client->query('Explain quantum computing in simple terms') + ->run(); +``` + +### 获取模型列表 + +```php +use DeepSeek\DeepSeekClient; + +$response = DeepSeekClient::build('your-api-key') + ->getModelsList() + ->run(); + +echo $response; // {"object":"list","data":[{"id":"deepseek-chat","object":"model","owned_by":"deepseek"},{"id":"deepseek-reasoner","object":"model","owned_by":"deepseek"}]} +``` + +### 函数调用 + +**函数调用**允许模型调用外部工具以增强其功能。 +你可以在文档中查看有关函数调用的详细信息: +[FUNCTION-CALLING.md](docs/FUNCTION-CALLING.md) + + +### 🛠 框架集成 + +### [Laravel Deepseek Package](https://github.com/deepseek-php/deepseek-laravel) + + +# 🐘✨ **DeepSeek PHP Community** ✨🐘 + +Click the button bellow or [join here](https://t.me/deepseek_php_community) to be part of our growing community! + +[![Join Telegram](https://img.shields.io/badge/Join-Telegram-blue?style=for-the-badge&logo=telegram)](https://t.me/deepseek_php_community) + +### **Channel Structure** 🏗️ +- 🗨️ **General** - Daily chatter +- 💡 **Ideas & Suggestions** - Shape the community's future +- 📢 **Announcements & News** - Official updates & news +- 🚀 **Releases & Updates** - Version tracking & migration support +- 🐞 **Issues & Bug Reports** - Collective problem-solving +- 🤝 **Pull Requests** - Code collaboration & reviews + +--- + +## 🚧 迁移指南 + +从 v1.x 升级?请查看我们全面的 [迁移指南](MIGRATION.md) 了解重大变更和升级说明。 + +--- + +## 📝 更新日志 + +详细的发布说明可在 [CHANGELOG.md](CHANGELOG.md) 查看。 + +--- + +## 🧪 测试 + +```bash +./vendor/bin/pest +``` + +测试覆盖范围涵盖 v2.1。 + +--- + +## 🔒 安全 + +**报告漏洞**: [omaralwi2010@gmail.com](mailto:omaralwi2010@gmail.com) + +--- + +## 🤝 贡献者 + +非常感谢为这个项目做出贡献的人! 🎉💖 + + + + + + + + + + +
+ + Omar AlAlwi +
+ Omar AlAlwi +
+
+ 🏆 Creator +
+ + Ayman Alhattami +
+ Ayman Alhattami +
+
+ ⭐ Contributor +
+ + Mohammad Asaad +
+ Mohammad Asaad +
+
+ ⭐ Contributor +
+ + Opada Alzaiede +
+ Opada Alzaiede +
+
+ ⭐ Contributor +
+ + Hisham Bin Ateya +
+ Hisham Bin Ateya +
+
+ ⭐ Contributor +
+ + 陈文锋 +
+ 陈文锋 +
+
+ ⭐ Contributor +
+ +**想要贡献?** 查看 [contributing guidelines](./CONTRIBUTING.md) 并提交拉取请求! 🚀 + +--- + +## 📄 许可 + +基于 [MIT License](LICENSE.md) 开源协议。 diff --git a/README.md b/README.md index 841b218..262f8ca 100644 --- a/README.md +++ b/README.md @@ -1,127 +1,302 @@

- - Gpdf - -

- -# Deepseek PHP Client +

DeepSeek PHP Client

+

🚀 Community-Driven PHP Client for DeepSeek AI API Integration

+ +

+ + Latest Version + + + Total Downloads + + + PHP Version + + + License + + + GitHub Stars + +

+ +[AR](README-AR.md) | [CN](README-CN.md) ## Table of Contents -- [Overview](#Overview) - - [Features](#key-Features) -- [Installation](#installation) -- [Quick Start Guide](#quick-start-guide) - - [Basic Usage](#basic-usage) - - [Advanced Usage](#advanced-usage) -- [Testing](#testing) -- [Contributors](#contributors-) -- [License](#license) +- [✨ Features](#-features) +- [📦 Installation](#-installation) +- [🚀 Quick Start](#-quick-start) + - [Basic Usage](#basic-usage) + - [Advanced Configuration](#advanced-configuration) + - [important warning with json mode](#-deepseek-json-mode-requirement) + - [Use with Symfony HttpClient](#use-with-symfony-httpclient) + - [Get Models List](#get-models-list) + - [Function Calling](#function-calling) + - [Framework Integration](#-framework-integration) +- [🆕 Migration Guide](#-migration-guide) +- [📝 Changelog](#-changelog) +- [🧪 Testing](#-testing) +- [🔒 Security](#-security) +- [🤝 Contributors](#-contributors) +- [📄 License](#-license) --- -## Overview -**Deepseek PHP Client** is a robust and community-driven PHP client library for seamless integration with the [Deepseek](https://www.deepseek.com/) API, offering efficient access to advanced AI and data processing capabilities -### Key Features -- **Easy Integration:** Simplifies interaction with the Deepseek API using a PHP client. -- **Method Chaining:** Supports fluent method chaining for building requests. -- **Customizable:** Allows setting different models, query roles, and streaming options. -- **PSR-18 Compliance:** Utilizes PSR-18 HTTP client for making API requests. +## ✨ Features + +- **Seamless API Integration**: PHP-first interface for DeepSeek's AI capabilities. +- **Fluent Builder Pattern**: Chainable methods for intuitive request building. +- **Enterprise Ready**: PSR-18 compliant HTTP client integration. +- **Model Flexibility**: Support for multiple DeepSeek models (Coder, Chat, etc.). +- **Streaming Ready**: Built-in support for real-time response handling. +- **Many Http Clients**: easy to use `Guzzle http client` (default) , or `symfony http client`. +- **Framework Friendly**: Laravel & Symfony packages available. --- -## Installation +## 📦 Installation -You can install the package via Composer: +Require the package via Composer: ```bash composer require deepseek-php/deepseek-php-client ``` -**Ensure your project meets the following requirements:** -- PHP 8.1 or later +**Requirements**: +- PHP 8.1+ --- -## Quick Start Guide +## 🚀 Quick Start ### Basic Usage -```php -use DeepseekPhp\DeepseekClient; +Get started with just two lines of code: -$apiKey = 'your-api-key'; +```php +use DeepSeek\DeepSeekClient; -$response = DeepseekClient::build($apiKey) - ->query('Hello Deepseek, how are you today?') +$response = DeepSeekClient::build('your-api-key') + ->query('Explain quantum computing in simple terms') ->run(); -echo 'API Response:'.$response; +echo $response; ``` -**Note**: in easy mode it will take defaults for all configs [Check Default Values](https://github.com/deepseek-php/deepseek-php-client/blob/master/src/Enums/Configs/DefaultConfigs.php) +📌 Defaults used: +- Model: `deepseek-chat` +- Temperature: 0.8 -### Advanced Usage +### Advanced Configuration ```php -use DeepseekPhp\DeepseekClient; -use DeepseekPhp\Enums\Queries\QueryRoles; -use DeepseekPhp\Enums\Models; +use DeepSeek\DeepSeekClient; +use DeepSeek\Enums\Models; -$apiKey = 'your-api-key'; +$client = DeepSeekClient::build(apiKey:'your-api-key', baseUrl:'https://api.deepseek.com/v3', timeout:30, clientType:'guzzle'); -$response = DeepseekClient::build($apiKey, 'https://api.deepseek.com/v2', 500) - ->query('System setup query', 'system') - ->query('User input message', 'user') +$response = $client ->withModel(Models::CODER->value) + ->withStream() + ->setTemperature(1.2) + ->setMaxTokens(8192) + ->setResponseFormat('text') // or "json_object" with careful . + ->query('Explain quantum computing in simple terms') ->run(); echo 'API Response:'.$response; ``` +## ⚠️ DeepSeek JSON Mode Requirement + +When using: + +```php +->setResponseFormat('json_object') +``` + +Your prompt **must contain the word `"json"`** in some form. Otherwise, the API will reject the request with the following error: + +> `"Prompt must contain the word 'json' in some form to use 'response_format' of type 'json_object'"` + +--- + +### 🚫 Incorrect Usage + +```php +->setResponseFormat('json_object') +->query('Explain quantum computing in simple terms') +``` + +### ✅ Correct Usage + +```php +->setResponseFormat('json_object') +->query('Respond in valid JSON format. Explain quantum computing in simple terms.') +``` + +> ✅ **Tip**: For best results, also provide a JSON example or explicitly say: +> *"Respond only in valid JSON."* + + +--- + +### Use with Symfony HttpClient +the package already built with `symfony Http client`, if you need to use package with `symfony` Http Client , it is easy to achieve that, just pass `clientType:'symfony'` with `build` function. + +ex with symfony: + +```php +// with defaults baseUrl and timeout +$client = DeepSeekClient::build('your-api-key', clientType:'symfony') +// with customization +$client = DeepSeekClient::build(apiKey:'your-api-key', baseUrl:'https://api.deepseek.com/v3', timeout:30, clientType:'symfony'); + +$client->query('Explain quantum computing in simple terms') + ->run(); +``` + +### Get Models List + +```php +use DeepSeek\DeepSeekClient; + +$response = DeepSeekClient::build('your-api-key') + ->getModelsList() + ->run(); + +echo $response; // {"object":"list","data":[{"id":"deepseek-chat","object":"model","owned_by":"deepseek"},{"id":"deepseek-reasoner","object":"model","owned_by":"deepseek"}]} +``` + + +### Function Calling + +Function Calling allows the model to call external tools to enhance its capabilities.[[1]](https://api-docs.deepseek.com/guides/function_calling) + +You Can check the documentation for function calling in [FUNCTION-CALLING.md](docs/FUNCTION-CALLING.md) + + +### 🛠 Framework Integration + +### [Laravel Deepseek Package](https://github.com/deepseek-php/deepseek-laravel) + +--- + +## 🚧 Migration Guide + +Upgrading from v1.x? Check our comprehensive [Migration Guide](MIGRATION.md) for breaking changes and upgrade instructions. + +--- + +## 📝 Changelog + +Detailed release notes available in [CHANGELOG.md](CHANGELOG.md) + +--- + +## 🧪 Testing + +```bash +./vendor/bin/pest +``` + +Test coverage coming in v2.1. + --- +
+ +# 🐘✨ **DeepSeek PHP Community** ✨🐘 + +Click the button bellow or [join here](https://t.me/deepseek_php_community) to be part of our growing community! + +[![Join Telegram](https://img.shields.io/badge/Join-Telegram-blue?style=for-the-badge&logo=telegram)](https://t.me/deepseek_php_community) -## Testing -tests will come soon . +### **Channel Structure** 🏗️ +- 🗨️ **General** - Daily chatter +- 💡 **Ideas & Suggestions** - Shape the community's future +- 📢 **Announcements & News** - Official updates & news +- 🚀 **Releases & Updates** - Version tracking & migration support +- 🐞 **Issues & Bug Reports** - Collective problem-solving +- 🤝 **Pull Requests** - Code collaboration & reviews -## Changelog +
-See [CHANGELOG](CHANGELOG.md) for recent changes. +--- + +## 🔒 Security + +**Report Vulnerabilities**: to [omaralwi2010@gmail.com](mailto:omaralwi2010@gmail.com) + +--- -## Contributors ✨ +## 🤝 Contributors -Thanks to these wonderful people for contributing to this project! 💖 +A huge thank you to these amazing people who have contributed to this project! 🎉💖 + + + + -
- Omar AlAlwi + Omar AlAlwi
- Omar AlAlwi + Omar AlAlwi

🏆 Creator
- ayman alhattami + Ayman Alhattami
- ayman alhattami + Ayman Alhattami

- 🏆 Contributer + ⭐ Contributor +
+ + Mohammad Asaad +
+ Mohammad Asaad +
+
+ ⭐ Contributor +
+ + Opada Alzaiede +
+ Opada Alzaiede +
+
+ ⭐ Contributor +
+ + Hisham Bin Ateya +
+ Hisham Bin Ateya +
+
+ ⭐ Contributor +
+ + Vinchan +
+ Vinchan +
+
+ ⭐ Contributor
-Want to contribute? Check out the [contributing guidelines](./CONTRIBUTING.md) and submit a pull request! 🚀 +**Want to contribute?** Check out the [contributing guidelines](./CONTRIBUTING.md) and submit a pull request! 🚀 -## Security - -If you discover any security-related issues, please email creator : `omaralwi2010@gmail.com`. +--- -## License +## 📄 License -The MIT License (MIT). See [LICENSE](LICENSE.md) for more information. +This package is open-source software licensed under the [MIT License](LICENSE.md). diff --git a/composer.json b/composer.json index 210ec89..e9e0a67 100644 --- a/composer.json +++ b/composer.json @@ -3,43 +3,36 @@ "description": "deepseek PHP client is a robust and community-driven PHP client library for seamless integration with the Deepseek API, offering efficient access to advanced AI and data processing capabilities.", "keywords": [ "deepseek", - "deepseek-php-client", - "deepseek-api", - "php-deepseek", - "deepseek-integration", - "openai", + "ai", "sdk", - "codex", - "GPT-3", - "DALL-E", "api", + "php", + "symfony", "client", - "deepseek-sdk", + "llm", + "nlp", + "openai", + "symfony-deepseek", + "deepseek-symfony", + "symfony-http-client", + "symfony-client", + "machine-learning", "php-sdk", - "php-ai", - "ai-for-php", "ai-sdk", "ai-api", - "natural", - "language", - "processing", - "deepseek-php-library", - "deepseek-client", "natural-language-processing", - "ai-integration", - "machine-learning", - "php-machine-learning", - "php-iot", - "nlp", - "data-processing", - "deep-learning", - "deepseek-library", - "php-library", "api-integration", "php-api-client", "deepseek-ai", - "php-openai-alternative", - "ai-client-library" + "ai-client-library", + "text-generation", + "generative-ai", + "api-wrapper", + "rest-client", + "developer-tools", + "php-ai-integration", + "deepseek-api", + "openai-alternative" ], "homepage": "https://github.com/deepseek-php/deepseek-php-client", "license": "MIT", @@ -56,15 +49,17 @@ "role": "creator" } ], - "version": "1.0.0.0", + "version": "2.0.6", "require": { "php": "^8.1.0", + "nyholm/psr7": "^1.8", "php-http/discovery": "^1.20.0", "php-http/multipart-stream-builder": "^1.4.2", "psr/http-client": "^1.0.3", - "psr/http-client-implementation": "^1.0.1", + "psr/http-client-implementation": "1.0", "psr/http-factory-implementation": "*", - "psr/http-message": "^1.1.0|^2.0.0" + "psr/http-message": "^1.1.0|^2.0.0", + "symfony/http-client": "^6.4" }, "require-dev": { "guzzlehttp/guzzle": "^7.9.2", @@ -72,21 +67,21 @@ "laravel/pint": "^1.18.1", "mockery/mockery": "^1.6.12", "nunomaduro/collision": "^7.11.0|^8.5.0", - "pestphp/pest": "^2.36.0|^3.5.0", - "pestphp/pest-plugin-arch": "^2.7|^3.0", - "pestphp/pest-plugin-type-coverage": "^2.8.7|^3.1.0", + "pestphp/pest": "^2.36", + "pestphp/pest-plugin-arch": "^2.7", + "pestphp/pest-plugin-type-coverage": "^2.8.7", "phpstan/phpstan": "^1.12.7", "roave/security-advisories": "dev-latest", "symfony/var-dumper": "^6.4.11|^7.1.5" }, "autoload": { "psr-4": { - "DeepseekPhp\\": "src/" + "DeepSeek\\": "src/" } }, "autoload-dev": { "psr-4": { - "DeepseekPhp\\Tests\\": "tests/" + "Tests\\": "tests/" } }, "minimum-stability": "dev", @@ -107,9 +102,10 @@ "config": { "sort-packages": true, "preferred-install": "dist", + "optimize-autoloader": true, "allow-plugins": { "pestphp/pest-plugin": true, "php-http/discovery": true } } -} +} \ No newline at end of file diff --git a/docs/FUNCTION-CALLING.md b/docs/FUNCTION-CALLING.md new file mode 100644 index 0000000..4d115cf --- /dev/null +++ b/docs/FUNCTION-CALLING.md @@ -0,0 +1,192 @@ +## Function Calling + +Function Calling allows the model to call external tools to enhance its capabilities.[[1]](https://api-docs.deepseek.com/guides/function_calling) + +#### 1. Define the tools used by the model and pass them with each message passed to the model, Receive query messages from the end user and pass them to the model with the defined tools. +- example function `get_weather($city)`. +```php +function get_weather($city) +{ + $city = strtolower($city); + $city = match($city){ + "cairo" => ["temperature"=> 22, "condition" => "Sunny"], + "gharbia" => ["temperature"=> 23, "condition" => "Sunny"], + "sharkia" => ["temperature"=> 24, "condition" => "Sunny"], + "beheira" => ["temperature"=> 21, "condition" => "Sunny"], + default => "not found city name." + }; + return json_encode($city); +} +``` +The user requests the weather in Cairo. +```php +$client = DeepSeekClient::build('your-api-key') + ->query('What is the weather like in Cairo?') + ->setTools([ + [ + "type" => "function", + "function" => [ + "name" => "get_weather", + "description" => "Get the current weather in a given city", + "parameters" => [ + "type" => "object", + "properties" => [ + "city" => [ + "type" => "string", + "description" => "The city name", + ], + ], + "required" => ["city"], + ], + ], + ], + ] +); + +$response = $client->run(); + +``` + +Output response like. +```json +{ + "id": "chat_12345", + "object": "chat.completion", + "created": 1677654321, + "model": "deepseek-chat", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": null, + "tool_calls": [ + { + "id": "call_12345", + "type": "function", + "function": { + "name": "get_weather", + "arguments": "{\"city\": \"Cairo\"}" + } + } + ] + }, + "finish_reason": "tool_calls" + } + ] +} +``` + +#### 2. Receive the response and check if it has called one or more tools to execute it in the system ,And execute the tool called by the model. +The deepseek api responds to the system and requests the execution of the tool responsible for fetching the weather status. +```php + +$response = $client->run(); + +$response = json_decode($response, true); +$message = $response['choices'][0]['message']; +$firstFunction = $message['tool_calls'][0]; +if ($firstFunction['function']['name'] == "get_weather") +{ + $weather_data = get_weather($firstFunction['function']['arguments']['city']); +} + +``` + +#### 3. Coordinate the results and send the previous response with the results of the executed tools. +Formats the response, and sends it back to the form. +```php +$response2 = $client->queryToolCall( + $message['tool_calls'], + $message['content'], + $message['role'] + )->queryTool( + $firstFunction['id'], + $weather_data +); +``` + +Request like +```json +{ + "messages": [ + { + "role": "user", + "content": "What is the weather like in Cairo?" + }, + { + "content": "What is the weather like in Cairo?", + "tool_calls": [ + { + "id": "930c60df-3ec75f81e00e", + "type": "function", + "function": { + "name": "get_weather", + "arguments": { + "city": "Cairo" + } + } + } + ], + "role": "assistant" + }, + { + "role": "tool", + "tool_call_id": "930c60df-3ec75f81e00e", + "content": "{\"temperature\":22,\"condition\":\"Sunny\"}" + } + ], + "model": "deepseek-chat", + "stream": false, + "temperature": 1.3, + "tools": [ + { + "type": "function", + "function": { + "name": "get_weather", + "description": "Get the current weather in a given city", + "parameters": { + "type": "object", + "properties": { + "city": { + "type": "string", + "description": "The city name" + } + }, + "required": [ + "city" + ] + } + } + } + ] +} +``` + +#### 4. Receive the final response from the model and pass it to the end user. +The deepseek api responds with the final response, which is the weather status according to the data passed to it in the example. +```php + +$response2 = $response2->run(); +echo $response2; +``` +Output response like :- +```json +{ + "id": "chat_67890", + "object": "chat.completion", + "created": 1677654322, + "model": "deepseek-chat", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": "The weather in Cairo is 22℃." + }, + "finish_reason": "stop" + } + ] +} +``` + diff --git a/src/Contracts/ClientContract.php b/src/Contracts/ClientContract.php new file mode 100644 index 0000000..12172e3 --- /dev/null +++ b/src/Contracts/ClientContract.php @@ -0,0 +1,14 @@ +httpClient = $httpClient; + $this->model = null; + $this->stream = false; + $this->requestMethod = 'POST'; + $this->endpointSuffixes = EndpointSuffixes::CHAT->value; + $this->temperature = (float) TemperatureValues::GENERAL_CONVERSATION->value; + $this->maxTokens = (int) TemperatureValues::MAX_TOKENS->value; + $this->responseFormatType = TemperatureValues::RESPONSE_FORMAT_TYPE->value; + $this->tools = null; + } + + public function run(): string + { + $requestData = [ + QueryFlags::MESSAGES->value => $this->queries, + QueryFlags::MODEL->value => $this->model, + QueryFlags::STREAM->value => $this->stream, + QueryFlags::TEMPERATURE->value => $this->temperature, + QueryFlags::MAX_TOKENS->value => $this->maxTokens, + QueryFlags::TOOLS->value => $this->tools, + QueryFlags::RESPONSE_FORMAT->value => [ + 'type' => $this->responseFormatType + ], + ]; + + $this->setResult((new Resource($this->httpClient, $this->endpointSuffixes))->sendRequest($requestData, $this->requestMethod)); + return $this->getResult()->getContent(); + } + + /** + * Create a new DeepSeekClient instance with the given API key. + * + * @param string $apiKey The API key for authentication. + * @param string|null $baseUrl The base URL for the API (optional). + * @param int|null $timeout The timeout duration for requests in seconds (optional). + * @return self A new instance of the DeepSeekClient. + */ + public static function build(string $apiKey, ?string $baseUrl = null, ?int $timeout = null, ?string $clientType = null): self + { + $clientType = $clientType ?? ClientTypes::GUZZLE->value; + + $httpClient = ApiFactory::build() + ->setBaseUri($baseUrl) + ->setTimeout($timeout) + ->setKey($apiKey) + ->run($clientType); + + return new self($httpClient); + } + + /** + * Add a query to the accumulated queries list. + * + * @param string $content + * @param string|null $role + * @return self The current instance for method chaining. + */ + public function query(string $content, ?string $role = "user"): self + { + $this->queries[] = $this->buildQuery($content, $role); + return $this; + } + + /** + * Reset a queries list to empty. + * + * @return self The current instance for method chaining. + */ + public function resetQueries() + { + $this->queries = []; + return $this; + } + + /** + * get list of available models . + * + * @return self The current instance for method chaining. + */ + public function getModelsList(): self + { + $this->endpointSuffixes = EndpointSuffixes::MODELS_LIST->value; + $this->requestMethod = 'GET'; + return $this; + } + + /** + * Set the model to be used for API requests. + * + * @param string|null $model The model name (optional). + * @return self The current instance for method chaining. + */ + public function withModel(?string $model = null): self + { + $this->model = $model; + return $this; + } + + /** + * Enable or disable streaming for API responses. + * + * @param bool $stream Whether to enable streaming (default: true). + * @return self The current instance for method chaining. + */ + public function withStream(bool $stream = true): self + { + $this->stream = $stream; + return $this; + } + + public function setTemperature(float $temperature): self + { + $this->temperature = $temperature; + return $this; + } + + public function setMaxTokens(int $maxTokens): self + { + $this->maxTokens = $maxTokens; + return $this; + } + + public function setResponseFormat(string $type): self + { + $this->responseFormatType = $type; + return $this; + } + + public function buildQuery(string $content, ?string $role = null): array + { + return [ + 'role' => $role ?: QueryRoles::USER->value, + 'content' => $content + ]; + } + + /** + * set result model + * @param \DeepSeek\Contracts\Models\ResultContract $result + * @return self The current instance for method chaining. + */ + public function setResult(ResultContract $result) + { + $this->result = $result; + return $this; + } + + /** + * response result model + * @return \DeepSeek\Contracts\Models\ResultContract + */ + public function getResult(): ResultContract + { + return $this->result; + } +} diff --git a/src/DeepseekClient.php b/src/DeepseekClient.php deleted file mode 100644 index 33214f1..0000000 --- a/src/DeepseekClient.php +++ /dev/null @@ -1,134 +0,0 @@ -httpClient = $httpClient; - $this->model = null; - $this->stream = false; - } - - public function run(): string - { - $requestData = [ - QueryFlags::MESSAGES->value => $this->queries, - QueryFlags::MODEL->value => $this->model, - QueryFlags::STREAM->value => $this->stream, - ]; - // Clear queries after sending - $this->queries = []; - return (new Resource($this->httpClient))->sendRequest($requestData); - } - - /** - * Create a new DeepseekClient instance with the given API key. - * - * @param string $apiKey The API key for authentication. - * @param string|null $baseUrl The base URL for the API (optional). - * @param int|null $timeout The timeout duration for requests in seconds (optional). - * @return self A new instance of the DeepseekClient. - */ - public static function build(string $apiKey, ?string $baseUrl = null, ?int $timeout = null): self - { - $httpClient = ApiFactory::build() - ->setBaseUri($baseUrl) - ->setTimeout($timeout) - ->setKey($apiKey) - ->run(); - - return new self($httpClient); - } - - /** - * Add a query to the accumulated queries list. - * - * @param string $content - * @param string|null $role - * @return self The current instance for method chaining. - */ - public function query(string $content, ?string $role = null): self - { - $this->queries[] = $this->buildQuery($content, $role); - return $this; - } - - /** - * Set the model to be used for API requests. - * - * @param string|null $model The model name (optional). - * @return self The current instance for method chaining. - */ - public function withModel(?string $model = null): self - { - $this->model = $model; - return $this; - } - - /** - * Enable or disable streaming for API responses. - * - * @param bool $stream Whether to enable streaming (default: true). - * @return self The current instance for method chaining. - */ - public function withStream(bool $stream = true): self - { - $this->stream = $stream; - return $this; - } - - protected function buildQuery(string $content, ?string $role = null): array - { - return [ - 'role' => $role ?: QueryRoles::USER->value, - 'content' => $content - ]; - } - -} diff --git a/src/Enums/Configs/DefaultConfigs.php b/src/Enums/Configs/DefaultConfigs.php index 871cf8d..204c9d1 100644 --- a/src/Enums/Configs/DefaultConfigs.php +++ b/src/Enums/Configs/DefaultConfigs.php @@ -1,11 +1,11 @@ baseUrl = $baseUrl ? trim($baseUrl) : DefaultConfigs::BASE_URL->value; return $this; } - /** - * Set the API key for authentication. - * - * @param string $apiKey The API key to set. - * @return self The instance of the self for method chaining. - */ public function setKey(string $apiKey): self { $this->apiKey = trim($apiKey); return $this; } - /** - * Set the timeout for the API request. - * - * If no timeout is provided, the default timeout value from the configuration is used. - * - * @param int|null $timeout The timeout value in seconds (optional). - * @return self The instance of the self for method chaining. - */ public function setTimeout(?int $timeout = null): self { $this->timeout = $timeout ?: (int)DefaultConfigs::TIMEOUT->value; return $this; } - /** - * Build and return the Guzzle Client instance. - * - * This method creates and configures a new Guzzle HTTP client instance - * using the provided base URL, timeout, and headers. - * - * @return Client A Guzzle client instance configured for the API. - */ - public function run(): Client + public function initialize(): self { - $clientConfig = [ + if (!isset($this->baseUrl)) { + $this->setBaseUri(); + } + + if (!isset($this->apiKey)) { + throw new RuntimeException('API key must be set using setKey() before initialization.'); + } + + if (!isset($this->timeout)) { + $this->setTimeout(); + } + + $this->clientConfig = [ HeaderFlags::BASE_URL->value => $this->baseUrl, HeaderFlags::TIMEOUT->value => $this->timeout, HeaderFlags::HEADERS->value => [ HeaderFlags::AUTHORIZATION->value => 'Bearer ' . $this->apiKey, - HeaderFlags::CONTENT_TYPE->value => "application/json", + HeaderFlags::CONTENT_TYPE->value => 'application/json', ], ]; - return new Client($clientConfig); + return $this; + } + + public function run(?string $clientType = null): ClientInterface + { + $clientType = $clientType ?? ClientTypes::GUZZLE->value; + + if (!isset($this->clientConfig)) { + $this->initialize(); + } + + return match (strtolower($clientType)) { + ClientTypes::GUZZLE->value => new Client($this->clientConfig), + ClientTypes::SYMFONY->value => new Psr18Client(HttpClient::create($this->clientConfig)), + default => throw new InvalidArgumentException("Unsupported client type: {$clientType}") + }; } } diff --git a/src/Models/BadResult.php b/src/Models/BadResult.php new file mode 100644 index 0000000..5bc9608 --- /dev/null +++ b/src/Models/BadResult.php @@ -0,0 +1,11 @@ +statusCode = $statusCode; + $this->content = $content; + } + protected function setStatusCode(int $statusCode) + { + $this->statusCode = $statusCode; + } + public function getStatusCode(): int + { + return $this->statusCode; + } + protected function setContent(string $content): void + { + $this->content = $content; + } + public function getContent(): string + { + return $this->content; + } + public function setResponse(ResponseInterface $response): static + { + $this->response = $response; + $this->setStatusCode($this->getResponse()->getStatusCode()); + $this->setContent($this->getResponse()->getBody()->getContents()); + return $this; + } + public function getResponse(): ResponseInterface + { + return $this->response; + } + public function isSuccess(): bool + { + return ($this->getStatusCode() === HTTPState::OK->value); + } +} + diff --git a/src/Models/SuccessResult.php b/src/Models/SuccessResult.php new file mode 100644 index 0000000..2cd0fd3 --- /dev/null +++ b/src/Models/SuccessResult.php @@ -0,0 +1,11 @@ +client = $client; + $this->endpointSuffixes = $endpointSuffixes ?: EndpointSuffixes::CHAT->value; + $this->requestFactory = $requestFactory ?: new Psr17Factory(); + $this->streamFactory = $streamFactory ?: new Psr17Factory(); } - /** - * Send a request to the API endpoint. - * - * This method sends a POST request to the API endpoint, including the query data - * and custom headers, and returns the response body as a string. - * - * @param array $requestData The data to send in the request. - * @return string The response body. - * - * @throws \RuntimeException If the request fails. - */ - public function sendRequest(array $requestData): string + public function sendRequest(array $requestData, ?string $requestMethod = 'POST'): ResultContract { try { - $response = $this->client->post($this->getEndpointSuffix(), [ - 'json' => $this->resolveHeaders($requestData), - ]); - - return $response->getBody()->getContents(); - } catch (GuzzleException $e) { - throw new \RuntimeException("Deepseek API request failed: " . $e->getMessage()); + $request = $this->requestFactory->createRequest( + $requestMethod, + $this->getEndpointSuffix() + ); + + if ($requestMethod === 'POST') { + $request = $request + ->withHeader('Content-Type', 'application/json') + ->withBody( + $this->streamFactory->createStream( + json_encode($this->resolveHeaders($requestData)) + )); + } + + $response = $this->client->sendRequest($request); + + return (new SuccessResult())->setResponse($response); + } catch (BadResponseException $badResponse) { + return (new BadResult())->setResponse($badResponse->getResponse()); + } catch (GuzzleException $error) { + return new FailureResult($error->getCode(), $error->getMessage()); + } catch (\Exception $error) { + return new FailureResult($error->getCode(), '{"error":"'.$error->getMessage().'"}'); } } @@ -104,7 +117,7 @@ public function prepareCustomHeaderParams(array $query): array */ public function getEndpointSuffix(): string { - return EndpointSuffixes::CHAT->value; + return $this->endpointSuffixes; } /** diff --git a/src/Traits/Client/HasToolsFunctionCalling.php b/src/Traits/Client/HasToolsFunctionCalling.php new file mode 100644 index 0000000..97d53fc --- /dev/null +++ b/src/Traits/Client/HasToolsFunctionCalling.php @@ -0,0 +1,66 @@ +tools = $tools; + return $this; + } + + /** + * Add a query tool calls to the accumulated queries list. + * + * @param array $toolCalls The tool calls generated by the model, such as function calls. + * @param string $content + * @param string|null $role + * @return self The current instance for method chaining. + */ + public function queryToolCall(array $toolCalls, string $content, ?string $role = null): self + { + $this->queries[] = $this->buildToolCallQuery($toolCalls, $content, $role); + return $this; + } + + public function buildToolCallQuery(array $toolCalls, string $content, ?string $role = null): array + { + $query = [ + 'role' => $role ?: QueryRoles::ASSISTANT->value, + 'tool_calls' => $toolCalls, + 'content' => $content, + ]; + return $query; + } + + /** + * Add a query tool to the accumulated queries list. + * + * @param string $toolCallId + * @param string $content + * @param string|null $role + * @return self The current instance for method chaining. + */ + public function queryTool(string $toolCallId, string $content , ?string $role = null): self + { + $this->queries[] = $this->buildToolQuery($toolCallId, $content, $role); + return $this; + } + + public function buildToolQuery(string $toolCallId, string $content, ?string $role): array + { + $query = [ + 'role' => $role ?: QueryRoles::TOOL->value, + 'tool_call_id' => $toolCallId, + 'content' => $content, + ]; + return $query; + } +} diff --git a/src/Traits/Queries/HasQueryParams.php b/src/Traits/Queries/HasQueryParams.php index 453c7bb..bb2a1a8 100644 --- a/src/Traits/Queries/HasQueryParams.php +++ b/src/Traits/Queries/HasQueryParams.php @@ -1,9 +1,9 @@ value: - return (string) $value; - case DataTypes::INTEGER->value: - return (int) $value; - case DataTypes::FLOAT->value: - return (float) $value; - case DataTypes::ARRAY->value: - return (array) $value; - case DataTypes::OBJECT->value: - return (object) $value; - case DataTypes::BOOL->value: - return (bool) $value; - case DataTypes::JSON->value: - return json_decode((string) $value, true); - default: - return $value; - } + return match ($type) { + DataTypes::STRING->value => (string)$value, + DataTypes::INTEGER->value => (int)$value, + DataTypes::FLOAT->value => (float)$value, + DataTypes::ARRAY->value => (array)$value, + DataTypes::OBJECT->value => (object)$value, + DataTypes::BOOL->value => (bool)$value, + DataTypes::JSON->value => json_decode((string)$value, true), + default => $value, + }; } /** @@ -63,13 +55,10 @@ private function convertValue($value, string $type): mixed */ private function getDefaultForKey(string $key): mixed { - switch ($key) { - case QueryFlags::MODEL->value: - return $this->getDefaultModel(); - case QueryFlags::STREAM->value: - return $this->getDefaultStream(); - default: - return null; - } + return match ($key) { + QueryFlags::MODEL->value => $this->getDefaultModel(), + QueryFlags::STREAM->value => $this->getDefaultStream(), + default => null, + }; } } diff --git a/src/Traits/Resources/HasChat.php b/src/Traits/Resources/HasChat.php index 29ae330..02cabdd 100644 --- a/src/Traits/Resources/HasChat.php +++ b/src/Traits/Resources/HasChat.php @@ -1,8 +1,8 @@ $this->stream, ]; $this->queries = []; - return (new Chat($this->httpClient))->sendRequest($requestData); + $this->setResult((new Chat($this->httpClient))->sendRequest($requestData)); + return $this->getResult()->getContent(); } } diff --git a/src/Traits/Resources/HasCoder.php b/src/Traits/Resources/HasCoder.php index 600b92b..9a684eb 100644 --- a/src/Traits/Resources/HasCoder.php +++ b/src/Traits/Resources/HasCoder.php @@ -1,8 +1,8 @@ $this->stream, ]; $this->queries = []; - return (new Coder($this->httpClient))->sendRequest($requestData); + $this->setResult((new Coder($this->httpClient))->sendRequest($requestData)); + return $this->getResult()->getContent(); } } diff --git a/tests/DeepseekTest.php b/tests/DeepseekTest.php deleted file mode 100644 index e97639e..0000000 --- a/tests/DeepseekTest.php +++ /dev/null @@ -1,9 +0,0 @@ -query('Hello DeepSeek, how are you today?') + ->setTemperature(1.5); + + // Act + $response = $client->run(); + $result = $client->getResult(); + + // Assert + expect($response)->not->toBeEmpty($response) + ->and($result->getStatusCode())->toEqual(HTTPState::OK->value); +}); + +test('Run query with valid API Key & insufficient balance should return 402', function () { + // Arrange + $apiKey = "insufficient-balance-api-key"; + $client = DeepSeekClient::build($apiKey) + ->query('Hello DeepSeek, how are you today?') + ->setTemperature(1.5); + + // Act + $response = $client->run(); + $result = $client->getResult(); + + // Assert + expect($response)->not->toBeEmpty($response) + ->and($result->getStatusCode())->toEqual(HTTPState::PAYMENT_REQUIRED->value); +}); + +test('Run query with invalid API key should return 401', function () { + // Arrange + $apiKey = "insufficient-balance-api-key"; + $client = DeepSeekClient::build($apiKey) + ->query('Hello DeepSeek, how are you today?') + ->setTemperature(1.5); + + // Act + $response = $client->run(); + $result = $client->getResult(); + + // Assert + expect($response)->not->toBeEmpty($response) + ->and($result->getStatusCode())->toEqual(HTTPState::UNAUTHORIZED->value); +}); diff --git a/tests/Feature/ExampleTest.php b/tests/Feature/ExampleTest.php new file mode 100644 index 0000000..61cd84c --- /dev/null +++ b/tests/Feature/ExampleTest.php @@ -0,0 +1,5 @@ +toBeTrue(); +}); diff --git a/tests/Feature/FunctionCallingTest.php b/tests/Feature/FunctionCallingTest.php new file mode 100644 index 0000000..041e153 --- /dev/null +++ b/tests/Feature/FunctionCallingTest.php @@ -0,0 +1,157 @@ + ["temperature"=> 22, "condition" => "Sunny"], + "gharbia" => ["temperature"=> 23, "condition" => "Sunny"], + "sharkia" => ["temperature"=> 24, "condition" => "Sunny"], + "beheira" => ["temperature"=> 21, "condition" => "Sunny"], + default => "not found city name." + }; + return json_encode($city); +} + +test('Test function calling with fake responses.', function () { + // Arrange + $fake = new FakeResponse(); + + /** @var DeepSeekClient&LegacyMockInterface&MockInterface */ + $mockClient = Mockery::mock(DeepSeekClient::class); + + $mockClient->shouldReceive('build')->andReturn($mockClient); + $mockClient->shouldReceive('setTools')->andReturn($mockClient); + $mockClient->shouldReceive('query')->andReturn($mockClient); + $mockClient->shouldReceive('run')->once()->andReturn($fake->toolFunctionCalling()); + + // Act + $response = $mockClient::build('your-api-key') + ->query('What is the weather like in Cairo?') + ->setTools([ + [ + "type" => "function", + "function" => [ + "name" => "get_weather", + "description" => "Get the current weather in a given city", + "parameters" => [ + "type" => "object", + "properties" => [ + "city" => [ + "type" => "string", + "description" => "The city name", + ], + ], + "required" => ["city"], + ], + ], + ], + ] + )->run(); + + // Assert + expect($fake->toolFunctionCalling())->toEqual($response); + + //------------------------------------------ + + // Arrange + $response = json_decode($response, true); + $message = $response['choices'][0]['message']; + + $firstFunction = $message['tool_calls'][0]; + if ($firstFunction['function']['name'] == "get_weather") + { + $weather_data = get_weather($firstFunction['function']['arguments']['city']); + } + + $mockClient->shouldReceive('queryCallTool')->andReturn($mockClient); + $mockClient->shouldReceive('queryTool')->andReturn($mockClient); + $mockClient->shouldReceive('run')->andReturn($fake->resultToolFunctionCalling()); + + // Act + $response2 = $mockClient->queryCallTool( + $message['tool_calls'], + $message['content'], + $message['role'] + )->queryTool( + $firstFunction['id'], + $weather_data, + 'tool' + )->run(); + + // Assert + expect($fake->resultToolFunctionCalling())->toEqual($response2); +}); + +test('Test function calling use base data with real responses.', function () { + // Arrange + $client = DeepSeekClient::build('your-api-key') + ->query('What is the weather like in Cairo?') + ->setTools([ + [ + "type" => "function", + "function" => [ + "name" => "get_weather", + "description" => "Get the current weather in a given city", + "parameters" => [ + "type" => "object", + "properties" => [ + "city" => [ + "type" => "string", + "description" => "The city name", + ], + ], + "required" => ["city"], + ], + ], + ], + ] + ); + + // Act + $response = $client->run(); + $result = $client->getResult(); + + // Assert + expect($response)->not()->toBeEmpty($response) + ->and($result->getStatusCode())->toEqual(HTTPState::OK->value); + + //----------------------------------------------------------------- + + // Arrange + $response = json_decode($response, true); + + $message = $response['choices'][0]['message']; + $firstFunction = $message['tool_calls'][0]; + if ($firstFunction['function']['name'] == "get_weather") + { + $args = json_decode($firstFunction['function']['arguments'], true); + $weather_data = get_weather($args['city']); + } + + $client2 = $client->queryToolCall( + $message['tool_calls'], + $message['content'], + $message['role'] + )->queryTool( + $firstFunction['id'], + $weather_data, + 'tool' + ); + + // Act + $response2 = $client2->run(); + $result2 = $client2->getResult(); + + // Assert + expect($response2)->not()->toBeEmpty($response2) + ->and($result2->getStatusCode())->toEqual(HTTPState::OK->value); +}); diff --git a/tests/Pest.php b/tests/Pest.php new file mode 100644 index 0000000..5949c61 --- /dev/null +++ b/tests/Pest.php @@ -0,0 +1,45 @@ +in('Feature'); + +/* +|-------------------------------------------------------------------------- +| Expectations +|-------------------------------------------------------------------------- +| +| When you're writing tests, you often need to check that values meet certain conditions. The +| "expect()" function gives you access to a set of "expectations" methods that you can use +| to assert different things. Of course, you may extend the Expectation API at any time. +| +*/ + +expect()->extend('toBeOne', function () { + return $this->toBe(1); +}); + +/* +|-------------------------------------------------------------------------- +| Functions +|-------------------------------------------------------------------------- +| +| While Pest is very powerful out-of-the-box, you may have some testing code specific to your +| project that you don't want to repeat in every file. Here you can also expose helpers as +| global functions to help you to reduce the number of lines of code in your test files. +| +*/ + +function something() +{ + // .. +} diff --git a/tests/TestCase.php b/tests/TestCase.php new file mode 100644 index 0000000..cfb05b6 --- /dev/null +++ b/tests/TestCase.php @@ -0,0 +1,10 @@ +toBeTrue(); +});