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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+[الإنجليزية](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) لتكون جزءًا من مجتمعنا المتنامي!
+
+[](https://t.me/deepseek_php_community)
+
+
+### **هيكل القناة** 🏗️
+- 🗨️ **عام** - دردشة يومية
+- 💡 **الأفكار والاقتراحات** - تشكيل مستقبل المجتمع
+- 📢 **الإعلانات والأخبار** - التحديثات والأخبار الرسمية
+- 🚀 **الإصدارات والتحديثات** - تتبع الإصدارات ودعم الترحيل
+- 🐞 **المشاكل وتقارير الأخطاء** - حل مشكلات جماعي
+- 🤝 **طلبات السحب** - التعاون والمراجعة البرمجية
+
+
+
+---
+
+## 🔒 الأمان
+
+**الإبلاغ عن الثغرات**: إلى [omaralwi2010@gmail.com](mailto:omaralwi2010@gmail.com)
+
+---
+
+## 🤝 المساهمين
+
+شكراً جزيلاً لهؤلاء الأشخاص المذهلين الذين ساهموا في هذا المشروع! 🎉💖
+
+
+
+**هل ترغب في المساهمة؟** اطلع على [إرشادات المساهمة](./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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+[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!
+
+[](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)
+
+---
+
+## 🤝 贡献者
+
+非常感谢为这个项目做出贡献的人! 🎉💖
+
+
+
+**想要贡献?** 查看 [contributing guidelines](./CONTRIBUTING.md) 并提交拉取请求! 🚀
+
+---
+
+## 📄 许可
+
+基于 [MIT License](LICENSE.md) 开源协议。
diff --git a/README.md b/README.md
index a8311b9..262f8ca 100644
--- a/README.md
+++ b/README.md
@@ -1,22 +1,26 @@
DeepSeek PHP Client
- 🚀 Community-Driven PHP SDK for DeepSeek AI API Integration
+ 🚀 Community-Driven PHP Client for DeepSeek AI API Integration
+
+
+
-
-
+
+
-
+
+[AR](README-AR.md) | [CN](README-CN.md)
## Table of Contents
- [✨ Features](#-features)
@@ -24,6 +28,10 @@
- [🚀 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)
@@ -36,12 +44,13 @@
## ✨ 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
-- **Framework Friendly**: Laravel & Symfony packages available
+- **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.
---
@@ -71,7 +80,7 @@ $response = DeepSeekClient::build('your-api-key')
->query('Explain quantum computing in simple terms')
->run();
-echo $response; // "Quantum computing uses qubits to..."
+echo $response;
```
📌 Defaults used:
@@ -84,21 +93,93 @@ echo $response; // "Quantum computing uses qubits to..."
use DeepSeek\DeepSeekClient;
use DeepSeek\Enums\Models;
-$response = DeepSeekClient::build('your-api-key')
- ->withBaseUrl('https://api.deepseek.com/v2')
- ->withModel(Models::CODER)
- ->withTemperature(1.2)
+$client = DeepSeekClient::build(apiKey:'your-api-key', baseUrl:'https://api.deepseek.com/v3', timeout:30, clientType:'guzzle');
+
+$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)
-### [Symfony Deepseek Package](https://github.com/deepseek-php/deepseek-symfony)
-
---
## 🚧 Migration Guide
@@ -116,11 +197,31 @@ Detailed release notes available in [CHANGELOG.md](CHANGELOG.md)
## 🧪 Testing
```bash
-composer test
+./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!
+
+[](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
+
+
+
---
## 🔒 Security
@@ -173,9 +274,18 @@ A huge thank you to these amazing people who have contributed to this project!
-
+
+
+ Hisham Bin Ateya
+
+
+ ⭐ Contributor
+ |
+
+
+
- Hisham Abdullah
+ Vinchan
⭐ Contributor
diff --git a/composer.json b/composer.json
index 7e39749..e9e0a67 100644
--- a/composer.json
+++ b/composer.json
@@ -7,11 +7,15 @@
"sdk",
"api",
"php",
+ "symfony",
"client",
"llm",
"nlp",
"openai",
- "qwen",
+ "symfony-deepseek",
+ "deepseek-symfony",
+ "symfony-http-client",
+ "symfony-client",
"machine-learning",
"php-sdk",
"ai-sdk",
@@ -45,15 +49,17 @@
"role": "creator"
}
],
- "version": "2.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",
@@ -61,9 +67,9 @@
"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"
@@ -75,7 +81,7 @@
},
"autoload-dev": {
"psr-4": {
- "DeepSeek\\Tests\\": "tests/"
+ "Tests\\": "tests/"
}
},
"minimum-stability": "dev",
@@ -96,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/DeepseekClientContract.php b/src/Contracts/ClientContract.php
similarity index 52%
rename from src/Contracts/DeepseekClientContract.php
rename to src/Contracts/ClientContract.php
index 3e5316e..12172e3 100644
--- a/src/Contracts/DeepseekClientContract.php
+++ b/src/Contracts/ClientContract.php
@@ -2,13 +2,13 @@
namespace DeepSeek\Contracts;
-use DeepSeek\DeepSeekClient;
-
-interface DeepseekClientContract
+interface ClientContract
{
- public static function build(string $apiKey): self;
public function run(): string;
+ public static function build(string $apiKey, ?string $baseUrl = null, ?int $timeout = null): self;
public function query(string $content, ?string $role = "user"): self;
+ public function getModelsList(): self;
public function withModel(?string $model = null): self;
public function withStream(bool $stream = true): self;
+ public function buildQuery(string $content, ?string $role = null): array;
}
diff --git a/src/Contracts/Factories/ApiFactoryContract.php b/src/Contracts/Factories/ApiFactoryContract.php
index 80c9c53..7799e7e 100644
--- a/src/Contracts/Factories/ApiFactoryContract.php
+++ b/src/Contracts/Factories/ApiFactoryContract.php
@@ -4,6 +4,7 @@
use DeepSeek\Factories\ApiFactory;
use GuzzleHttp\Client;
+use Psr\Http\Client\ClientInterface;
interface ApiFactoryContract
{
@@ -39,9 +40,9 @@ public function setKey(string $apiKey): ApiFactory;
public function setTimeout(?int $timeout = null): ApiFactory;
/**
- * Build and return the Guzzle Client instance.
+ * Build and return http Client instance.
*
- * @return Client
+ * @return ClientInterface
*/
- public function run(): Client;
+ public function run(?string $clientType = null): ClientInterface;
}
diff --git a/src/DeepSeekClient.php b/src/DeepSeekClient.php
index 19d58df..62e39f5 100644
--- a/src/DeepSeekClient.php
+++ b/src/DeepSeekClient.php
@@ -2,20 +2,23 @@
namespace DeepSeek;
-use DeepSeek\Contracts\DeepseekClientContract;
+use DeepSeek\Contracts\ClientContract;
use DeepSeek\Contracts\Models\ResultContract;
+use DeepSeek\Enums\Requests\ClientTypes;
+use DeepSeek\Enums\Requests\EndpointSuffixes;
use DeepSeek\Resources\Resource;
use Psr\Http\Client\ClientInterface;
use DeepSeek\Factories\ApiFactory;
use DeepSeek\Enums\Queries\QueryRoles;
use DeepSeek\Enums\Requests\QueryFlags;
-use DeepSeek\Enums\Requests\HeaderFlags;
use DeepSeek\Enums\Configs\TemperatureValues;
use DeepSeek\Traits\Resources\{HasChat, HasCoder};
+use DeepSeek\Traits\Client\HasToolsFunctionCalling;
-class DeepSeekClient implements DeepseekClientContract
+class DeepSeekClient implements ClientContract
{
use HasChat, HasCoder;
+ use HasToolsFunctionCalling;
/**
* PSR-18 HTTP client for making requests.
@@ -46,6 +49,8 @@ class DeepSeekClient implements DeepseekClientContract
protected bool $stream;
protected float $temperature;
+ protected int $maxTokens;
+ protected string $responseFormatType;
/**
* response result contract
@@ -53,6 +58,16 @@ class DeepSeekClient implements DeepseekClientContract
*/
protected ResultContract $result;
+ protected string $requestMethod;
+
+ protected ?string $endpointSuffixes;
+
+ /**
+ * Array of tools for using function calling.
+ * @var array|null $tools
+ */
+ protected ?array $tools;
+
/**
* Initialize the DeepSeekClient with a PSR-compliant HTTP client.
*
@@ -63,7 +78,12 @@ public function __construct(ClientInterface $httpClient)
$this->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
@@ -73,10 +93,14 @@ public function run(): string
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
+ ],
];
- // Clear queries after sending
- $this->queries = [];
- $this->result = (new Resource($this->httpClient))->sendRequest($requestData);
+
+ $this->setResult((new Resource($this->httpClient, $this->endpointSuffixes))->sendRequest($requestData, $this->requestMethod));
return $this->getResult()->getContent();
}
@@ -88,13 +112,15 @@ public function run(): string
* @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
+ 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();
+ ->run($clientType);
return new self($httpClient);
}
@@ -106,11 +132,34 @@ public static function build(string $apiKey, ?string $baseUrl = null, ?int $time
* @param string|null $role
* @return self The current instance for method chaining.
*/
- public function query(string $content, ?string $role = null): self
+ 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.
@@ -142,7 +191,19 @@ public function setTemperature(float $temperature): self
return $this;
}
- protected function buildQuery(string $content, ?string $role = null): array
+ 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,
@@ -150,6 +211,17 @@ protected function buildQuery(string $content, ?string $role = null): array
];
}
+ /**
+ * 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
diff --git a/src/Enums/Configs/TemperatureValues.php b/src/Enums/Configs/TemperatureValues.php
index 12c21b1..c09d315 100644
--- a/src/Enums/Configs/TemperatureValues.php
+++ b/src/Enums/Configs/TemperatureValues.php
@@ -12,4 +12,6 @@ enum TemperatureValues: string
case TRANSLATION = "1.4";
case CREATIVE_WRITING = "1.5";
case POETRY = "1.6";
+ case MAX_TOKENS = "4096";
+ case RESPONSE_FORMAT_TYPE = "text";
}
diff --git a/src/Enums/Queries/QueryRoles.php b/src/Enums/Queries/QueryRoles.php
index 4659084..a23925c 100644
--- a/src/Enums/Queries/QueryRoles.php
+++ b/src/Enums/Queries/QueryRoles.php
@@ -6,4 +6,6 @@ enum QueryRoles: string
{
case USER = 'user';
case SYSTEM = 'system';
+ case ASSISTANT = 'assistant';
+ case TOOL = 'tool';
}
diff --git a/src/Enums/Requests/ClientTypes.php b/src/Enums/Requests/ClientTypes.php
new file mode 100644
index 0000000..fbc01d9
--- /dev/null
+++ b/src/Enums/Requests/ClientTypes.php
@@ -0,0 +1,10 @@
+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/Resources/Resource.php b/src/Resources/Resource.php
index a3075cf..65ed2fe 100644
--- a/src/Resources/Resource.php
+++ b/src/Resources/Resource.php
@@ -18,59 +18,57 @@
use GuzzleHttp\Exception\BadResponseException;
use GuzzleHttp\Exception\GuzzleException;
use Psr\Http\Client\ClientInterface;
+use Psr\Http\Message\RequestFactoryInterface;
+use Psr\Http\Message\StreamFactoryInterface;
+use Nyholm\Psr7\Factory\Psr17Factory;
class Resource implements ResourceContract
{
use HasQueryParams;
- /**
- * HTTP client for making requests.
- *
- * @var ClientInterface
- */
protected ClientInterface $client;
-
- /**
- * Initialize the Resource with a Guzzle HTTP client.
- *
- * @param ClientInterface $client The HTTP client instance for making requests.
- */
- public function __construct(ClientInterface $client)
- {
+ protected ?string $endpointSuffixes;
+ protected RequestFactoryInterface $requestFactory;
+ protected StreamFactoryInterface $streamFactory;
+
+ public function __construct(
+ ClientInterface $client,
+ ?string $endpointSuffixes = null,
+ ?RequestFactoryInterface $requestFactory = null,
+ ?StreamFactoryInterface $streamFactory = null
+ ) {
$this->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 as a result contract.
- *
- * @param array $requestData The data to send in the request.
- * @return ResultContract The response result.
- *
- */
- public function sendRequest(array $requestData): ResultContract
+ public function sendRequest(array $requestData, ?string $requestMethod = 'POST'): ResultContract
{
try {
- $response = $this->client->post($this->getEndpointSuffix(), [
- 'json' => $this->resolveHeaders($requestData),
- ]);
+ $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) {
-
- $response = $badResponse->getResponse();
- return (new BadResult())->setResponse($response);
-
+ 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().'"}');
-
}
}
@@ -119,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/Resources/HasChat.php b/src/Traits/Resources/HasChat.php
index 2365ade..02cabdd 100644
--- a/src/Traits/Resources/HasChat.php
+++ b/src/Traits/Resources/HasChat.php
@@ -19,6 +19,7 @@ public function chat(): string
'stream' => $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 1901756..9a684eb 100644
--- a/src/Traits/Resources/HasCoder.php
+++ b/src/Traits/Resources/HasCoder.php
@@ -19,6 +19,7 @@ public function code(): string
'stream' => $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 a8a7141..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/HandelResultDeepseekTest.php b/tests/HandelResultDeepseekTest.php
deleted file mode 100644
index 18a9007..0000000
--- a/tests/HandelResultDeepseekTest.php
+++ /dev/null
@@ -1,54 +0,0 @@
-apiKey = "valid-api-key";
- $this->expiredApiKey = "expired-api-key";
- }
- public function test_ok_response()
- {
- $deepseek = DeepSeekClient::build($this->apiKey)
- ->query('Hello Deepseek, how are you today?')
- ->setTemperature(1.5);
- $response = $deepseek->run();
- $result = $deepseek->getResult();
-
- $this->assertNotEmpty($response);
- $this->assertEquals(HTTPState::OK->value, $result->getStatusCode());
- }
- public function test_can_not_access_with_api_expired_payment()
- {
- $deepseek = DeepSeekClient::build($this->expiredApiKey)
- ->query('Hello Deepseek, how are you today?')
- ->setTemperature(1.5);
- $response = $deepseek->run();
- $result = $deepseek->getResult();
-
- $this->assertNotEmpty($response);
- if(!$result->isSuccess())
- {
- $this->assertEquals(HTTPState::PAYMENT_REQUIRED->value, $result->getStatusCode());
- }
- }
- public function test_access_with_wrong_api_key()
- {
- $deepseek = DeepSeekClient::build($this->apiKey."wrong-api-key")
- ->query('Hello Deepseek, how are you today?')
- ->setTemperature(1.5);
- $response = $deepseek->run();
- $result = $deepseek->getResult();
-
- $this->assertNotEmpty($response);
- $this->assertEquals(HTTPState::UNAUTHORIZED->value, $result->getStatusCode());
- }
-}
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();
+});
|