Skip to content

Commit 880424b

Browse files
committed
Deepseek R1 reasoning content access
1 parent fb3b630 commit 880424b

File tree

9 files changed

+169
-9
lines changed

9 files changed

+169
-9
lines changed

config/llm.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,16 @@
9494
'contextLength' => 128_000,
9595
'maxOutputLength' => 8192,
9696
],
97+
'deepseek-r' => [
98+
'providerType' => LLMProviderType::DeepSeek->value,
99+
'apiUrl' => 'https://api.deepseek.com',
100+
'apiKey' => Env::get('DEEPSEEK_API_KEY', ''),
101+
'endpoint' => '/chat/completions',
102+
'defaultModel' => 'deepseek-reasoner',
103+
'defaultMaxTokens' => 1024,
104+
'contextLength' => 128_000,
105+
'maxOutputLength' => 8192,
106+
],
97107
'fireworks' => [
98108
'providerType' => LLMProviderType::Fireworks->value,
99109
'apiUrl' => 'https://api.fireworks.ai/inference/v1',
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
---
2+
title: 'Reasoning Content Access'
3+
docname: 'reasoning_content'
4+
path: ''
5+
---
6+
7+
## Overview
8+
9+
## Example
10+
11+
```php
12+
<?php
13+
$loader = require 'vendor/autoload.php';
14+
$loader->add('Cognesy\\Instructor\\', __DIR__ . '../../src/');
15+
16+
use Cognesy\Instructor\Features\LLM\Inference;
17+
use Cognesy\Instructor\Utils\Debug\Debug;
18+
use Cognesy\Instructor\Utils\Str;
19+
20+
//Debug::enable();
21+
22+
// EXAMPLE 1: regular API, allows to customize inference options
23+
$response = (new Inference)
24+
->withConnection('deepseek-r') // optional, default is set in /config/llm.php
25+
->create(
26+
messages: [['role' => 'user', 'content' => 'What is the capital of France. Answer with just a name.']],
27+
options: ['max_tokens' => 64]
28+
)
29+
->response();
30+
echo "\nCASE #1: Sync response\n";
31+
echo "USER: What is capital of France\n";
32+
echo "ASSISTANT: {$response->content()}\n";
33+
echo "REASONING: {$response->reasoningContent()}\n";
34+
assert($response->content() !== '');
35+
assert(Str::contains($response->content(), 'Paris'));
36+
assert($response->reasoningContent() !== '');
37+
38+
// EXAMPLE 2: streaming response
39+
$stream = (new Inference)
40+
->withConnection('deepseek-r') // optional, default is set in /config/llm.php
41+
->create(
42+
messages: [['role' => 'user', 'content' => 'What is capital of Brasil. Answer with just a name.']],
43+
options: ['max_tokens' => 128, 'stream' => true]
44+
)
45+
->stream();
46+
47+
echo "\nCASE #2: Streamed response\n";
48+
echo "USER: What is capital of Brasil\n";
49+
echo "ASSISTANT: ";
50+
foreach ($stream->responses() as $partial) {
51+
echo $partial->contentDelta;
52+
}
53+
echo "\n";
54+
echo "REASONING: {$stream->final()->reasoningContent()}\n";
55+
assert($stream->final()->reasoningContent() !== '');
56+
assert($stream->final()->content() !== '');
57+
assert(Str::contains($stream->final()->content(), 'Brasília'));
58+
?>
59+
```

notes/NOTES.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
- Unify template conventions - <||> vs. {{}}, update docs
77
- Full control over generated prompt (access to Script object processing)
88
- ToolUse - apply context variables to message sequence (via ScriptParameters??)
9+
- Predicted outputs API
10+
- Batch completion API
11+
- Citations API
12+
- Reasoning traces support in response objects
913

1014
## Low priority
1115

src/Features/LLM/Data/LLMResponse.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ public function __construct(
1313
private string $content = '',
1414
private string $finishReason = '',
1515
private ?ToolCalls $toolCalls = null,
16+
private string $reasoningContent = '',
17+
//private array $citations = [],
1618
private ?Usage $usage = null,
1719
private array $responseData = [],
1820
) {
@@ -53,6 +55,19 @@ public function withContent(string $content) : self {
5355
return $this;
5456
}
5557

58+
public function withReasoningContent(string $reasoningContent) : self {
59+
$this->reasoningContent = $reasoningContent;
60+
return $this;
61+
}
62+
63+
public function reasoningContent() : string {
64+
return $this->reasoningContent;
65+
}
66+
67+
public function hasReasoningContent() : bool {
68+
return $this->reasoningContent !== '';
69+
}
70+
5671
public function json(): Json {
5772
return match(true) {
5873
// TODO: what about tool calls?
@@ -103,16 +118,19 @@ private function makeFromPartialResponses(array $partialResponses = []) : self {
103118
}
104119

105120
$content = '';
121+
$reasoningContent = '';
106122
foreach($partialResponses as $partialResponse) {
107123
if ($partialResponse === null) {
108124
continue;
109125
}
110126
$content .= $partialResponse->contentDelta;
127+
$reasoningContent .= $partialResponse->reasoningContentDelta;
111128
$this->responseData[] = $partialResponse->responseData;
112129
$this->usage()->accumulate($partialResponse->usage);
113130
$this->finishReason = $partialResponse->finishReason;
114131
}
115132
$this->content = $content;
133+
$this->reasoningContent = $reasoningContent;
116134

117135
$tools = $this->makeTools($partialResponses);
118136
if (!empty($tools)) {

src/Features/LLM/Data/PartialLLMResponse.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@ class PartialLLMResponse
88
{
99
private mixed $value = null; // data extracted from response or tool calls
1010
private string $content = '';
11+
private string $reasoningContent = '';
1112

1213
public function __construct(
1314
public string $contentDelta = '',
15+
public string $reasoningContentDelta = '',
1416
public string $toolId = '',
1517
public string $toolName = '',
1618
public string $toolArgs = '',
@@ -47,6 +49,19 @@ public function content() : string {
4749
return $this->content;
4850
}
4951

52+
public function reasoningContent() : string {
53+
return $this->reasoningContent;
54+
}
55+
56+
public function withReasoningContent(string $reasoningContent) : self {
57+
$this->reasoningContent = $reasoningContent;
58+
return $this;
59+
}
60+
61+
public function hasReasoningContent() : bool {
62+
return $this->reasoningContent !== '';
63+
}
64+
5065
public function json(): string {
5166
if (!$this->hasContent()) {
5267
return '';
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
namespace Cognesy\Instructor\Features\LLM\Drivers\Deepseek;
4+
5+
use Cognesy\Instructor\Features\LLM\Data\LLMResponse;
6+
use Cognesy\Instructor\Features\LLM\Data\PartialLLMResponse;
7+
use Cognesy\Instructor\Features\LLM\Drivers\OpenAI\OpenAIResponseAdapter;
8+
9+
class DeepseekResponseAdapter extends OpenAIResponseAdapter
10+
{
11+
public function fromResponse(array $data): ?LLMResponse {
12+
return new LLMResponse(
13+
content: $this->makeContent($data),
14+
finishReason: $data['choices'][0]['finish_reason'] ?? '',
15+
toolCalls: $this->makeToolCalls($data),
16+
reasoningContent: $data['choices'][0]['message']['reasoning_content'] ?? '',
17+
usage: $this->usageFormat->fromData($data),
18+
responseData: $data,
19+
);
20+
}
21+
22+
public function fromStreamResponse(array $data): ?PartialLLMResponse {
23+
if ($data === null || empty($data)) {
24+
return null;
25+
}
26+
return new PartialLLMResponse(
27+
contentDelta: $this->makeContentDelta($data),
28+
reasoningContentDelta: $data['choices'][0]['delta']['reasoning_content'] ?? '',
29+
toolId: $this->makeToolId($data),
30+
toolName: $this->makeToolNameDelta($data),
31+
toolArgs: $this->makeToolArgsDelta($data),
32+
finishReason: $data['choices'][0]['finish_reason'] ?? '',
33+
usage: $this->usageFormat->fromData($data),
34+
responseData: $data,
35+
);
36+
}
37+
}

src/Features/LLM/Drivers/InferenceDriverFactory.php

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
use Cognesy\Instructor\Features\LLM\Drivers\CohereV2\CohereV2RequestAdapter;
2323
use Cognesy\Instructor\Features\LLM\Drivers\CohereV2\CohereV2ResponseAdapter;
2424
use Cognesy\Instructor\Features\LLM\Drivers\CohereV2\CohereV2UsageFormat;
25+
use Cognesy\Instructor\Features\LLM\Drivers\Deepseek\DeepseekResponseAdapter;
2526
use Cognesy\Instructor\Features\LLM\Drivers\Gemini\GeminiBodyFormat;
2627
use Cognesy\Instructor\Features\LLM\Drivers\Gemini\GeminiMessageFormat;
2728
use Cognesy\Instructor\Features\LLM\Drivers\Gemini\GeminiRequestAdapter;
@@ -67,6 +68,7 @@ public function make(LLMConfig $config, CanHandleHttp $httpClient, EventDispatch
6768
LLMProviderType::Cerebras => $this->cerebras($config, $httpClient, $events),
6869
LLMProviderType::CohereV1 => $this->cohereV1($config, $httpClient, $events),
6970
LLMProviderType::CohereV2 => $this->cohereV2($config, $httpClient, $events),
71+
LLMProviderType::DeepSeek => $this->deepseek($config, $httpClient, $events),
7072
LLMProviderType::Gemini => $this->gemini($config, $httpClient, $events),
7173
LLMProviderType::GeminiOAI => $this->geminiOAI($config, $httpClient, $events),
7274
LLMProviderType::Groq => $this->groq($config, $httpClient, $events),
@@ -78,7 +80,6 @@ public function make(LLMConfig $config, CanHandleHttp $httpClient, EventDispatch
7880
LLMProviderType::XAi => $this->xAi($config, $httpClient, $events),
7981
// OpenAI compatible driver for generic OAI providers
8082
LLMProviderType::A21,
81-
LLMProviderType::DeepSeek,
8283
LLMProviderType::Fireworks,
8384
LLMProviderType::Ollama,
8485
LLMProviderType::OpenAICompatible,
@@ -153,6 +154,19 @@ public function cohereV2(LLMConfig $config, CanHandleHttp $httpClient, EventDisp
153154
);
154155
}
155156

157+
public function deepseek(LLMConfig $config, CanHandleHttp $httpClient, EventDispatcher $events): CanHandleInference {
158+
return new ModularLLMDriver(
159+
$config,
160+
new OpenAIRequestAdapter(
161+
$config,
162+
new OpenAICompatibleBodyFormat($config, new OpenAIMessageFormat())
163+
),
164+
new DeepseekResponseAdapter(new OpenAIUsageFormat()),
165+
$httpClient,
166+
$events
167+
);
168+
}
169+
156170
public function gemini(LLMConfig $config, CanHandleHttp $httpClient, EventDispatcher $events): CanHandleInference {
157171
return new ModularLLMDriver(
158172
$config,

src/Features/LLM/Drivers/OpenAI/OpenAIResponseAdapter.php

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,14 @@ public function fromStreamData(string $data): string|bool {
5151
};
5252
}
5353

54-
private function makeToolCalls(array $data) : ToolCalls {
54+
protected function makeToolCalls(array $data) : ToolCalls {
5555
return ToolCalls::fromArray(array_map(
5656
callback: fn(array $call) => $this->makeToolCall($call),
5757
array: $data['choices'][0]['message']['tool_calls'] ?? []
5858
));
5959
}
6060

61-
private function makeToolCall(array $data) : ?ToolCall {
61+
protected function makeToolCall(array $data) : ?ToolCall {
6262
if (empty($data)) {
6363
return null;
6464
}
@@ -71,7 +71,7 @@ private function makeToolCall(array $data) : ?ToolCall {
7171
return ToolCall::fromArray($data['function'])?->withId($data['id']);
7272
}
7373

74-
private function makeContent(array $data): string {
74+
protected function makeContent(array $data): string {
7575
$contentMsg = $data['choices'][0]['message']['content'] ?? '';
7676
$contentFnArgs = $data['choices'][0]['message']['tool_calls'][0]['function']['arguments'] ?? '';
7777
return match(true) {
@@ -81,7 +81,7 @@ private function makeContent(array $data): string {
8181
};
8282
}
8383

84-
private function makeContentDelta(array $data): string {
84+
protected function makeContentDelta(array $data): string {
8585
$deltaContent = $data['choices'][0]['delta']['content'] ?? '';
8686
$deltaFnArgs = $data['choices'][0]['delta']['tool_calls'][0]['function']['arguments'] ?? '';
8787
return match(true) {
@@ -91,15 +91,15 @@ private function makeContentDelta(array $data): string {
9191
};
9292
}
9393

94-
private function makeToolId(array $data) : string {
94+
protected function makeToolId(array $data) : string {
9595
return $data['choices'][0]['delta']['tool_calls'][0]['id'] ?? '';
9696
}
9797

98-
private function makeToolNameDelta(array $data) : string {
98+
protected function makeToolNameDelta(array $data) : string {
9999
return $data['choices'][0]['delta']['tool_calls'][0]['function']['name'] ?? '';
100100
}
101101

102-
private function makeToolArgsDelta(array $data) : string {
102+
protected function makeToolArgsDelta(array $data) : string {
103103
return $data['choices'][0]['delta']['tool_calls'][0]['function']['arguments'] ?? '';
104104
}
105-
}
105+
}

src/Features/LLM/InferenceStream.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ protected function getAllPartialLLMResponses(Generator $stream) : array {
128128
*/
129129
private function makePartialLLMResponses(Generator $stream) : Generator {
130130
$content = '';
131+
$reasoningContent = '';
131132
$finishReason = '';
132133
$this->llmResponses = [];
133134
$this->lastPartialLLMResponse = null;
@@ -148,8 +149,10 @@ private function makePartialLLMResponses(Generator $stream) : Generator {
148149
$finishReason = $partialResponse->finishReason;
149150
}
150151
$content .= $partialResponse->contentDelta;
152+
$reasoningContent .= $partialResponse->reasoningContentDelta;
151153
$enrichedResponse = $partialResponse
152154
->withContent($content)
155+
->withReasoningContent($reasoningContent)
153156
->withFinishReason($finishReason);
154157
$this->events->dispatch(new PartialLLMResponseReceived($enrichedResponse));
155158

0 commit comments

Comments
 (0)