diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..b9682fa --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,7 @@ +# Changelog + +All notable changes to `deepseek-php-client` will be documented in this file + +## 1.0.0 - 201X-XX-XX + +- initial release diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..b4ae1c4 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,55 @@ +# Contributing + +Contributions are **welcome** and will be fully **credited**. + +Please read and understand the contribution guide before creating an issue or pull request. + +## Etiquette + +This project is open source, and as such, the maintainers give their free time to build and maintain the source code +held within. They make the code freely available in the hope that it will be of use to other developers. It would be +extremely unfair for them to suffer abuse or anger for their hard work. + +Please be considerate towards maintainers when raising issues or presenting pull requests. Let's show the +world that developers are civilized and selfless people. + +It's the duty of the maintainer to ensure that all submissions to the project are of sufficient +quality to benefit the project. Many developers have different skillsets, strengths, and weaknesses. Respect the maintainer's decision, and do not be upset or abusive if your submission is not used. + +## Viability + +When requesting or submitting new features, first consider whether it might be useful to others. Open +source projects are used by many developers, who may have entirely different needs to your own. Think about +whether or not your feature is likely to be used by other users of the project. + +## Procedure + +Before filing an issue: + +- Attempt to replicate the problem, to ensure that it wasn't a coincidental incident. +- Check to make sure your feature suggestion isn't already present within the project. +- Check the pull requests tab to ensure that the bug doesn't have a fix in progress. +- Check the pull requests tab to ensure that the feature isn't already in progress. + +Before submitting a pull request: + +- Check the codebase to ensure that your feature doesn't already exist. +- Check the pull requests to ensure that another person hasn't already submitted the feature or fix. + +## Requirements + +If the project maintainer has any additional requirements, you will find them listed here. + +- **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - The easiest way to apply the conventions is to install [PHP Code Sniffer](https://pear.php.net/package/PHP_CodeSniffer). + +- **Add tests!** - Your patch won't be accepted if it doesn't have tests. + +- **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date. + +- **Consider our release cycle** - We try to follow [SemVer v2.0.0](https://semver.org/). Randomly breaking public APIs is not an option. + +- **One pull request per feature** - If you want to do more than one thing, send multiple pull requests. + +- **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](https://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting. + +**Happy coding**! diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..a62defb --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) deepseek-php + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..c3142bb --- /dev/null +++ b/README.md @@ -0,0 +1,118 @@ +

+ + Gpdf + +

+ +# Deepseek PHP Client + +## 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) + +--- +## Overview +**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 + +### 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. + +--- + +## Installation + +You can install the package via Composer: + +```bash +composer require deepseek-php/deepseek-php-client +``` + +**Ensure your project meets the following requirements:** +- PHP 8.1 or later + +--- + +## **3. Quick Start Guide** + +### **Basic Usage** + +```php +use DeepseekPhp\DeepseekClient; + +$apiKey = 'your-api-key'; + +$response = DeepseekClient::build($apiKey) + ->query('Hello Deepseek, how are you today?') + ->run(); + +echo 'API Response:'.$response; +``` + +**Note**: in easy mode it will take defaults for all configs [Check Default Values](https://github.com/deepseek-php/deepseek-php-client/blob/main/src/Enums/Configs/DefaultConfigs.php) + +### **Advanced Usage** + +```php +use DeepseekPhp\DeepseekClient; +use DeepseekPhp\Enums\Queries\QueryRoles; +use DeepseekPhp\Enums\Models; + +$apiKey = 'your-api-key'; + +$response = DeepseekClient::build($apiKey, 'https://api.deepseek.com/v2', 500) + ->query('System setup query', 'system') + ->query('User input message', 'user') + ->withModel(Models::CODER->value) + ->run(); + +echo 'API Response:'.$response; +``` + +--- + +## **7. Testing** + +tests will come soon . + +## Changelog + +See [CHANGELOG](CHANGELOG.md) for recent changes. + +## Contributors ✨ + +Thanks to these wonderful people for contributing to this project! 💖 + + + + + + +
+ + Omar Al Alwi +
+ Omar AlAlwi +
+
+ 🏆 Creator +
+ +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 + +The MIT License (MIT). See [LICENSE](LICENSE.md) for more information. diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..d8e5795 --- /dev/null +++ b/composer.json @@ -0,0 +1,115 @@ +{ + "name": "deepseek-php/deepseek-php-client", + "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", + "sdk", + "codex", + "GPT-3", + "DALL-E", + "api", + "client", + "deepseek-sdk", + "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" + ], + "homepage": "https://github.com/deepseek-php/deepseek-php-client", + "license": "MIT", + "type": "library", + "authors": [ + { + "name": "deepseek-php", + "email": "omaralwi2010@gmail.com", + "role": "owner" + }, + { + "name": "Omar Alalwi", + "email": "omaralwi2010@gmail.com", + "role": "creator" + } + ], + "version": "1.0.0", + "require": { + "php": "^8.1.0", + "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-factory-implementation": "*", + "psr/http-message": "^1.1.0|^2.0.0" + }, + "require-dev": { + "guzzlehttp/guzzle": "^7.9.2", + "guzzlehttp/psr7": "^2.7.0", + "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", + "phpstan/phpstan": "^1.12.7", + "roave/security-advisories": "dev-latest", + "symfony/var-dumper": "^6.4.11|^7.1.5" + }, + "autoload": { + "psr-4": { + "DeepseekPhp\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "DeepseekPhp\\Tests\\": "tests/" + } + }, + "minimum-stability": "dev", + "prefer-stable": true, + "scripts": { + "lint": "pint -v", + "test:lint": "pint --test -v", + "test:types": "phpstan analyse --ansi", + "test:type-coverage": "pest --type-coverage --min=100", + "test:unit": "pest --colors=always", + "test": [ + "@test:lint", + "@test:types", + "@test:type-coverage", + "@test:unit" + ] + }, + "config": { + "sort-packages": true, + "preferred-install": "dist", + "allow-plugins": { + "pestphp/pest-plugin": true, + "php-http/discovery": true + } + } +} diff --git a/public/images/deepseek_screenshot.png b/public/images/deepseek_screenshot.png new file mode 100644 index 0000000..8c28996 Binary files /dev/null and b/public/images/deepseek_screenshot.png differ diff --git a/src/Contracts/DeepseekApiClientContract.php b/src/Contracts/DeepseekApiClientContract.php new file mode 100644 index 0000000..0106b90 --- /dev/null +++ b/src/Contracts/DeepseekApiClientContract.php @@ -0,0 +1,14 @@ +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 new file mode 100644 index 0000000..871cf8d --- /dev/null +++ b/src/Enums/Configs/DefaultConfigs.php @@ -0,0 +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 + { + $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", + ], + ]; + + return new Client($clientConfig); + } +} diff --git a/src/Resources/Chat.php b/src/Resources/Chat.php new file mode 100644 index 0000000..fe0ebda --- /dev/null +++ b/src/Resources/Chat.php @@ -0,0 +1,10 @@ +value; + } +} diff --git a/src/Resources/Resource.php b/src/Resources/Resource.php new file mode 100644 index 0000000..4e86a50 --- /dev/null +++ b/src/Resources/Resource.php @@ -0,0 +1,150 @@ +client = $client; + } + + /** + * 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 + { + 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()); + } + } + + /** + * Merge request data with default headers. + * + * This method merges the given query data with custom headers that are + * prepared for the request. + * + * @param array $requestData The data to send in the request. + * @return array The merged request data with default headers. + */ + protected function resolveHeaders(array $requestData): array + { + return array_merge($requestData, $this->prepareCustomHeaderParams($requestData)); + } + + /** + * Prepare the custom headers for the request. + * + * This method loops through the query parameters and applies the appropriate + * type conversion before returning the final headers. + * + * @param array $query The data to send in the request. + * @return array The custom headers for the request. + */ + public function prepareCustomHeaderParams(array $query): array + { + $headers = []; + $params = $this->getAllowedQueryParamsList(); + + // Loop through the parameters and apply the conversion logic dynamically + foreach ($params as $key => $type) { + $headers[$key] = $this->getQueryParam($query, $key, $this->getDefaultForKey($key), $type); + } + + return $headers; + } + + /** + * Get the endpoint suffix for the resource. + * + * This method returns the endpoint suffix that is used in the API URL. + * + * @return string The endpoint suffix. + */ + public function getEndpointSuffix(): string + { + return EndpointSuffixes::CHAT->value; + } + + /** + * Get the model associated with the resource. + * + * This method returns the default model value associated with the resource. + * + * @return string The default model value. + */ + public function getDefaultModel(): string + { + return Models::CHAT->value; + } + + /** + * Check if stream is enabled or not. + * + * This method checks whether the streaming option is enabled based on the + * default configuration. + * + * @return bool True if streaming is enabled, false otherwise. + */ + public function getDefaultStream(): bool + { + return DefaultConfigs::STREAM->value === 'true'; + } + + /** + * Get the list of query parameters and their corresponding types for conversion. + * + * This method returns an array of query keys and their associated data types + * for use in preparing the custom headers. + * + * @return array An associative array of query keys and their data types. + */ + protected function getAllowedQueryParamsList(): array + { + return [ + QueryFlags::MODEL->value => DataTypes::STRING->value, + QueryFlags::STREAM->value => DataTypes::BOOL->value, + ]; + } +} diff --git a/src/Traits/Queries/HasQueryParams.php b/src/Traits/Queries/HasQueryParams.php new file mode 100644 index 0000000..453c7bb --- /dev/null +++ b/src/Traits/Queries/HasQueryParams.php @@ -0,0 +1,75 @@ +convertValue($value, $type); + } + + return $default; + } + + /** + * Convert the value to the specified type. + * + * @param mixed $value + * @param string $type + * @return mixed + */ + private function convertValue($value, string $type): mixed + { + switch ($type) { + case DataTypes::STRING->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; + } + } + + /** + * Get default value for specific query keys. + * + * @param string $key + * @return 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; + } + } +} diff --git a/src/Traits/Resources/HasChat.php b/src/Traits/Resources/HasChat.php new file mode 100644 index 0000000..29ae330 --- /dev/null +++ b/src/Traits/Resources/HasChat.php @@ -0,0 +1,24 @@ + $this->queries, + 'model' => $this->model, + 'stream' => $this->stream, + ]; + $this->queries = []; + return (new Chat($this->httpClient))->sendRequest($requestData); + } +} diff --git a/src/Traits/Resources/HasCoder.php b/src/Traits/Resources/HasCoder.php new file mode 100644 index 0000000..600b92b --- /dev/null +++ b/src/Traits/Resources/HasCoder.php @@ -0,0 +1,24 @@ + $this->queries, + 'model' => $this->model, + 'stream' => $this->stream, + ]; + $this->queries = []; + return (new Coder($this->httpClient))->sendRequest($requestData); + } +} diff --git a/tests/DeepseekTest.php b/tests/DeepseekTest.php new file mode 100644 index 0000000..e97639e --- /dev/null +++ b/tests/DeepseekTest.php @@ -0,0 +1,9 @@ +