Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/deepseek-automation-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest]
php: ['8.1', '8.2', '8.3', '8.4']
php: ['8.2', '8.3', '8.4']

steps:
- name: Checkout code
Expand Down
3 changes: 0 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,18 @@ 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/
AGENTS.md
/.phpunit.cache
/.php-cs-fixer.cache
/.php-cs-fixer.php
/composer.lock
/phpunit.xml
/vendor/
*.swp
*.swo
Expand Down
128 changes: 125 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,129 @@
# Changelog

All notable changes to `deepseek-php-client` will be documented in this file
All notable changes to `deepseek-php-client` are documented in this file.

## 1.0.0 - 201X-XX-XX
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

- initial release
---

## Backward Compatibility Commitment

This package is used in 100k+ production installs. We take backward compatibility seriously and follow semantic versioning strictly within the `v2.x` line.

**Our commitments for the entire `v2.x` line:**

- No public method, class, trait, enum case, or constant will be **removed** or **renamed**.
- No public method's **return type** will change.
- No public method's existing **parameters** will change type or be removed (new optional parameters with defaults may be added).
- No published interface ([`ClientContract`](src/Contracts/ClientContract.php), `ResourceContract`, `ResultContract`, `ApiFactoryContract`) will gain new required methods. New methods on the client implementation will be exposed via separate, additive interfaces.
- Default behavior of existing methods will not silently change in ways that affect cost, output, or correctness (e.g. raising default `max_tokens`).

**What's coming in `v2.1.x`:** Every new DeepSeek API feature (V4 models, thinking mode, FIM completion, Anthropic format, user balance, `stop` / `top_p` / `tool_choice` / `logprobs` / `user_id`, chat prefix completion, etc.) will be delivered as **additive** new methods, optional parameters, and enum cases. Bug fixes (`/v3` base URL, ignored params in `chat()` / `code()`, keep-alive line stripping) are also in scope.

**What's reserved for `v3.0.0`:** Removing deprecated `Models::CODER` / `Models::R1` / `Models::R1Zero`, removing the `Coder` class and `HasCoder` trait, raising default `max_tokens`, retyping `run()` to return a structured DTO, expanding `ClientContract` to match the implementation. All of these will be announced via `@deprecated` notices throughout `v2.x` and shipped with a complete migration guide in [MIGRATION.md](MIGRATION.md).

See [TODO.md](TODO.md) for the full feature gap analysis with per-item BC classification.

---

## [Unreleased] - v2.2.0 (planned)

> Additive only. Zero breaking changes from v2.x. See [TODO.md](TODO.md) for the source list.

### Generation parameters and DX
- Thinking mode setters: `setThinking(array $config)` and `setReasoningEffort(string $effort)`.
- New sampling / generation parameter setters: `setStop()`, `setTopP()`, `setToolChoice()`, `setLogprobs()`, `setTopLogprobs()`, `setUserId()`.
- Optional `?string $name` parameter on `query()` and `buildQuery()` for the OpenAI message `name` field.
- `setSystemMessage(string $content)` convenience method.
- Tool `strict` mode helper for function definitions.
- Response introspection accessors on `SuccessResult`: `getMessage()`, `getUsage()`, `getReasoningContent()`, `getToolCalls()`, `getFinishReason()`, `getCacheHitTokens()` — `run()` continues to return `string`.
- `getQueries()`, `getConfig()`, `reset()` introspection helpers.
- `DefaultConfigs::MAX_TOKENS` and `DefaultConfigs::RESPONSE_FORMAT_TYPE` cases (`TemperatureValues::MAX_TOKENS` / `RESPONSE_FORMAT_TYPE` deprecated).
- Additive `ExtendedClientContract` interface (preserves the existing `ClientContract`).

## [Unreleased] - v2.3.0 (planned)

- Real streaming via new `runStreamed(callable $onChunk): void` method (existing `->withStream()->run()` string-returning behavior preserved).
- `stream_options.include_usage` exposure.
- Chat Prefix Completion (Beta): `queryAssistantPrefix(string $content)` and `/beta` base URL opt-in.
- `getUserBalance()` method, `EndpointSuffixes::USER_BALANCE` enum case, and `UserBalanceResult` DTO.
- FIM Completion (Beta): new resource class for `POST /completions` (Fill-In-the-Middle).
- Rate-limit handling: new `RateLimitResult` class with parsed `Retry-After` header for HTTP 429 responses.

> Note: Anthropic API format support is intentionally not on the v2.x roadmap. It may be reintroduced in a later release if there is sufficient community demand.

---

## [2.1.0] - 2026-05-22

> Foundation + Bug Fixes. Zero breaking changes from `v2.0.x`. All deprecated symbols remain fully functional throughout the `v2.x` line.

### Added
- New `Models::V4_PRO` (`deepseek-v4-pro`) and `Models::V4_FLASH` (`deepseek-v4-flash`) enum cases. Both models support DeepSeek's 1M-token context window and dual thinking / non-thinking modes (per the [V4 Preview announcement](https://api-docs.deepseek.com/news/news260424)).

### Fixed
- **Default `baseUrl` corrected** from `https://api.deepseek.com/v3` to `https://api.deepseek.com`. The `/v3` path was never a valid DeepSeek API endpoint. Users who passed an explicit `baseUrl` to `DeepSeekClient::build()` are unaffected.
- **`chat()` and `code()` shortcuts** now honor `temperature`, `maxTokens`, `tools`, and `responseFormat` set on the client. Previously these settings were silently dropped from the request body when using the shortcut methods; the request body now matches what `run()` sends.
- **Keep-Alive padding stripped from responses.** The DeepSeek API may send empty lines (non-streaming) or `: keep-alive` SSE comments (streaming) while waiting for inference to start. These are now removed before the response content is exposed to user code. See the [DeepSeek Rate Limit docs](https://api-docs.deepseek.com/quick_start/rate_limit#request-keep-alive-mechanism) for details.

### Deprecated
The following symbols are deprecated and will be removed in `v3.0.0`. They remain fully functional throughout the `v2.x` line — only IDE `@deprecated` notices are emitted (no `trigger_error()`).

- `Models::CHAT` — the `deepseek-chat` alias retires from the DeepSeek API on 2026-07-24. Use `Models::V4_FLASH` (with `setThinking(['type' => 'disabled'])` in v2.2.0+ for non-thinking mode).
- `Models::CODER` — `deepseek-coder` no longer exists in the DeepSeek API. Use `Models::V4_PRO` or `Models::V4_FLASH`.
- `Models::R1` — the `DeepSeek-R1` alias retires from the DeepSeek API on 2026-07-24. Use `Models::V4_FLASH` (with `setThinking(['type' => 'enabled'])` in v2.2.0+ for thinking mode).
- `Models::R1Zero` — `DeepSeek-R1-Zero` was never a valid DeepSeek API model id.

### Documentation
- README refreshed: default temperature corrected (1.3, not 0.8); `baseUrl` example updated; advanced-configuration example now uses `Models::V4_PRO`; `getModelsList()` example output updated to include V4 models; new "Supported Models" callout under Features.
- `docs/FUNCTION-CALLING.md` updated: JSON examples use `deepseek-v4-pro`; new "Thinking-mode caveat" section explaining that `reasoning_content` must be echoed back on tool turns to avoid HTTP 400 from the API.

### Internal
- New test file: `tests/Feature/V210ChangesTest.php` covering V4 enum cases, default base URL, Keep-Alive stripping (both non-streaming and streaming), and `chat()` / `code()` request body completeness.

---

## [2.0.6] - 2025

Current published baseline prior to v2.1.0. No breaking changes from `2.0.0`.

Patch-level fixes and documentation updates rolled up since `2.0.0`. This release marks the point from which the [Backward Compatibility Commitment](#backward-compatibility-commitment) above applies.

---

## [2.0.0] - 2025-02-01

### Changed (Breaking)
- **Namespace renamed** from `DeepseekPhp` to `DeepSeek`. All imports in user code must be updated. See [MIGRATION.md](MIGRATION.md) for details.

Replace:
```php
use DeepseekPhp\SomeClass;
```
With:
```php
use DeepSeek\SomeClass;
```

---

## [Roadmap] - v3.0.0 (no ETA)

> Breaking changes only. Will ship with a complete migration guide in [MIGRATION.md](MIGRATION.md). Users will get at least one full `v2.1.x` release with `@deprecated` notices before any of these land.

### Removed
- Deprecated `Models` enum cases: `CHAT`, `CODER`, `R1`, `R1Zero` (per the 2026-07-24 DeepSeek API retirement of the `deepseek-chat` and `deepseek-reasoner` aliases).
- `Resources\Coder` class and `Traits\Resources\HasCoder` trait (including `code()`).
- `Enums\Configs\TemperatureValues::MAX_TOKENS` and `RESPONSE_FORMAT_TYPE` cases.

### Changed (Breaking)
- `run()` will return a structured `ChatCompletionResult` DTO instead of a raw JSON `string`. The current string-returning behavior moves to a new method (`runRaw()` or equivalent).
- Default `MAX_TOKENS` raised from 4096 to a V4-appropriate value (V4 models support 384K output tokens — current default is ~1% of capacity).
- `ClientContract` expanded to declare all methods the implementation provides: `resetQueries()`, `setTemperature()`, `setMaxTokens()`, `setResponseFormat()`, `setResult()`, `getResult()`, and the 4-arg `build()` signature including `?string $clientType`.
- Default `baseUrl` no longer accepts the legacy `/v3` suffix (already removed in `v2.1.x` as a fix; v3.0.0 removes the back-compat shim).

---

## [1.0.0] - 201X-XX-XX

- Initial release.
32 changes: 25 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,18 @@
- **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.).
- **Latest DeepSeek V4 Models**: First-class support for `deepseek-v4-pro` and `deepseek-v4-flash` with 1M-token context windows and thinking / non-thinking modes.
- **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.

> **Supported Models**
>
> - `Models::V4_PRO` — flagship 1.6T/49B-active model, max 384K output tokens.
> - `Models::V4_FLASH` — fast, economical 284B/13B-active model, max 384K output tokens.
>
> Legacy `Models::CHAT`, `Models::CODER`, `Models::R1`, and `Models::R1Zero` are deprecated and will be removed in v3.0.0. The `deepseek-chat` and `deepseek-reasoner` aliases retire from the DeepSeek API on **2026-07-24**.

---

## 📦 Installation
Expand Down Expand Up @@ -83,19 +90,21 @@ echo $response;
```

📌 Defaults used:
- Model: `deepseek-chat`
- Temperature: 0.8
- Model: API default (no `model` field sent unless you call `withModel()`)
- Temperature: 1.3 (`TemperatureValues::GENERAL_CONVERSATION`)
- Max tokens: 4096
- Response format: `text`

### 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');
$client = DeepSeekClient::build(apiKey:'your-api-key', baseUrl:'https://api.deepseek.com', timeout:30, clientType:'guzzle');

$response = $client
->withModel(Models::CODER->value)
->withModel(Models::V4_PRO->value)
->withStream()
->setTemperature(1.2)
->setMaxTokens(8192)
Expand Down Expand Up @@ -149,7 +158,7 @@ ex with symfony:
// 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 = DeepSeekClient::build(apiKey:'your-api-key', baseUrl:'https://api.deepseek.com', timeout:30, clientType:'symfony');

$client->query('Explain quantum computing in simple terms')
->run();
Expand All @@ -164,7 +173,16 @@ $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"}]}
echo $response;
// {
// "object": "list",
// "data": [
// {"id": "deepseek-v4-pro", "object": "model", "owned_by": "deepseek"},
// {"id": "deepseek-v4-flash", "object": "model", "owned_by": "deepseek"},
// {"id": "deepseek-chat", "object": "model", "owned_by": "deepseek"}, // deprecated, retires 2026-07-24
// {"id": "deepseek-reasoner", "object": "model", "owned_by": "deepseek"} // deprecated, retires 2026-07-24
// ]
// }
```


Expand Down
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@
"role": "creator"
}
],
"version": "2.0.6",
"version": "2.1.0",
"require": {
"php": "^8.1.0",
"php": "^8.2.0",
"nyholm/psr7": "^1.8",
"php-http/discovery": "^1.20.0",
"php-http/multipart-stream-builder": "^1.4.2",
Expand Down
13 changes: 10 additions & 3 deletions docs/FUNCTION-CALLING.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ Output response like.
"id": "chat_12345",
"object": "chat.completion",
"created": 1677654321,
"model": "deepseek-chat",
"model": "deepseek-v4-pro",
"choices": [
{
"index": 0,
Expand Down Expand Up @@ -136,7 +136,7 @@ Request like
"content": "{\"temperature\":22,\"condition\":\"Sunny\"}"
}
],
"model": "deepseek-chat",
"model": "deepseek-v4-pro",
"stream": false,
"temperature": 1.3,
"tools": [
Expand Down Expand Up @@ -176,7 +176,7 @@ Output response like :-
"id": "chat_67890",
"object": "chat.completion",
"created": 1677654322,
"model": "deepseek-chat",
"model": "deepseek-v4-pro",
"choices": [
{
"index": 0,
Expand All @@ -190,3 +190,10 @@ Output response like :-
}
```

---

### Thinking-mode caveat

When using V4 models with thinking mode enabled (or the legacy `DeepSeek-R1`), assistant responses include a `reasoning_content` field at the same level as `content`. **This field MUST be echoed back on the next tool turn**; otherwise the DeepSeek API returns HTTP 400.

See the [DeepSeek reasoning model docs](https://api-docs.deepseek.com/guides/reasoning_model) for details. Helpers for reading `reasoning_content` off the response and passing it back into the next request will land in `v2.2.0` together with `setThinking()` / `setReasoningEffort()`.
4 changes: 4 additions & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
parameters:
level: 5
paths:
- src
19 changes: 19 additions & 0 deletions phpunit.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
colors="true">
<testsuites>
<testsuite name="Unit">
<directory suffix="Test.php">./tests/Unit</directory>
</testsuite>
<testsuite name="Feature">
<directory suffix="Test.php">./tests/Feature</directory>
</testsuite>
</testsuites>
<source>
<include>
<directory suffix=".php">./src</directory>
</include>
</source>
</phpunit>
8 changes: 7 additions & 1 deletion src/Contracts/ClientContract.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,16 @@
interface ClientContract
{
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 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;
}
13 changes: 3 additions & 10 deletions src/Contracts/Factories/ApiFactoryContract.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,39 +10,32 @@ interface ApiFactoryContract
{
/**
* Create a new instance of the factory.
*
* @return ApiFactory
*/
public static function build(): ApiFactory;

/**
* Set the base URL for the API.
*
* @param string|null $baseUrl The base URL to set (optional).
* @return ApiFactory
* @param string|null $baseUrl The base URL to set (optional).
*/
public function setBaseUri(?string $baseUrl = null): ApiFactory;

/**
* Set the API key for authentication.
*
* @param string $apiKey The API key to set.
* @return ApiFactory
* @param string $apiKey The API key to set.
*/
public function setKey(string $apiKey): ApiFactory;

/**
* Set the timeout for the API request.
*
* @param int|null $timeout The timeout value in seconds (optional).
* @return ApiFactory
* @param int|null $timeout The timeout value in seconds (optional).
*/
public function setTimeout(?int $timeout = null): ApiFactory;

/**
* Build and return http Client instance.
*
* @return ClientInterface
*/
public function run(?string $clientType = null): ClientInterface;
}
3 changes: 0 additions & 3 deletions src/Contracts/Models/ResultContract.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,16 @@ interface ResultContract
{
/**
* result status code
* @return int
*/
public function getStatusCode(): int;

/**
* result content date as a string
* @return string
*/
public function getContent(): string;

/**
* if response status code is ok (200)
* @return bool
*/
public function isSuccess(): bool;
}
Loading
Loading