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 composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
],
"require": {
"php": "^8.2",
"papi-ai/papi-core": "^0.14",
"papi-ai/papi-core": "^0.15",
"ext-curl": "*"
},
"require-dev": {
Expand Down
18 changes: 15 additions & 3 deletions src/CohereProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use Generator;
use PapiAI\Core\Contracts\EmbeddingProviderInterface;
use PapiAI\Core\Contracts\ProviderInterface;
use PapiAI\Core\Contracts\ToolSelectableInterface;
use PapiAI\Core\EmbeddingResponse;
use PapiAI\Core\Exception\AuthenticationException;
use PapiAI\Core\Exception\ProviderException;
Expand All @@ -35,15 +36,26 @@
* Supports chat completions, streaming, tool calling, and embeddings.
* Authentication via Bearer token. All HTTP via ext-curl.
*
* @see https://docs.cohere.com/reference/chat
* @see https://docs.cohere.com/reference/chat *
* The neutral `effort` option is accepted and ignored here. Cohere exposes no reasoning-effort parameter on the v2 chat API. Ignoring it
* degrades nothing the caller was promised, which is why it is silent where an unhonourable
* `toolChoice` throws.
*/
class CohereProvider implements ProviderInterface, EmbeddingProviderInterface
class CohereProvider implements ProviderInterface, EmbeddingProviderInterface, ToolSelectableInterface
{
private const CHAT_API_URL = 'https://api.cohere.com/v2/chat';
private const EMBED_API_URL = 'https://api.cohere.com/v1/embed';

public const MODEL_COMMAND_A_PLUS = 'command-a-plus-05-2026';
public const MODEL_COMMAND_A = 'command-a-03-2025';
public const MODEL_COMMAND_A_REASONING = 'command-a-reasoning-08-2025';
public const MODEL_COMMAND_R7B = 'command-r7b-12-2024';

/** @deprecated Deprecated 15 September 2025, and predates command-r7b so it rejects tool_choice. */
public const MODEL_COMMAND_R_PLUS = 'command-r-plus';
/** @deprecated Deprecated 15 September 2025, and predates command-r7b so it rejects tool_choice. */
public const MODEL_COMMAND_R = 'command-r';
/** @deprecated Deprecated 15 September 2025. */
public const MODEL_COMMAND = 'command';

public const MODEL_EMBED_ENGLISH = 'embed-english-v3.0';
Expand All @@ -55,7 +67,7 @@ class CohereProvider implements ProviderInterface, EmbeddingProviderInterface
*/
public function __construct(
private readonly string $apiKey,
private readonly string $defaultModel = self::MODEL_COMMAND_R_PLUS,
private readonly string $defaultModel = self::MODEL_COMMAND_A_PLUS,
) {
}

Expand Down
2 changes: 1 addition & 1 deletion tests/Unit/CohereProviderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ public function callHandleError(int $httpCode, ?array $data): void

$this->provider->chat([Message::user('Hello')]);

expect($this->provider->lastPayload['model'])->toBe('command-r-plus');
expect($this->provider->lastPayload['model'])->toBe('command-a-plus-05-2026');
});

it('overrides model and options from parameters', function () {
Expand Down
10 changes: 10 additions & 0 deletions tests/Unit/CohereToolChoiceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
declare(strict_types=1);

use PapiAI\Cohere\CohereProvider;
use PapiAI\Core\Contracts\NamedToolSelectableInterface;
use PapiAI\Core\Contracts\ToolSelectableInterface;
use PapiAI\Core\Exception\ProviderException;
use PapiAI\Core\Message;

Expand Down Expand Up @@ -94,3 +96,11 @@ protected function request(array $payload): array
->toThrow(InvalidArgumentException::class);
});
});

describe('CohereProvider tool-selection capability', function () {
it('declares what it can force, so callers can ask instead of catching', function () {
// Cohere can force "required" or "none", but its API cannot name a tool.
expect(is_subclass_of(CohereProvider::class, ToolSelectableInterface::class))->toBeTrue();
expect(is_subclass_of(CohereProvider::class, NamedToolSelectableInterface::class))->toBeFalse();
});
});
Loading