Skip to content

Commit

Permalink
feat: DSN configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
adrienbrault committed Jun 1, 2024
1 parent 502ad53 commit 942f6d7
Show file tree
Hide file tree
Showing 9 changed files with 280 additions and 11 deletions.
47 changes: 47 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,53 @@ $instructrice->get(
);
```

#### DSN

You may configure the LLM using a DSN:
- the scheme is the provider: `openai`, `openai-http`, `anthropic`, `google`
- the password is the api key
- the host, port and path are the api endpoints without the scheme
- the query string:
- `model` is the model name
- `context` is the context window
- `strategy` is the strategy to use:
- `json` for json mode with the schema in the prompt only
- `json_with_schema` for json mode with probably the completion perfectly constrained to the schema
- `tool_any`
- `tool_auto`
- `tool_function`

Examples:
```php
use AdrienBrault\Instructrice\InstructriceFactory;

$instructrice = InstructriceFactory::create(
defaultLlm: 'openai://:api_key@api.openai.com/v1/chat/completions?model=gpt-3.5-turbo&strategy=tool_auto&context=16000'
);

$instructrice->get(
...,
llm: 'openai-http://localhost:11434?model=adrienbrault/nous-hermes2theta-llama3-8b&strategy=json&context=8000'
);

$instructrice->get(
...,
llm: 'openai://:api_key@api.fireworks.ai/inference/v1/chat/completions?model=accounts/fireworks/models/llama-v3-70b-instruct&context=8000&strategy=json_with_schema'
);

$instructrice->get(
...,
llm: 'google://:api_key@generativelanguage.googleapis.com/v1beta/models?model=gemini-1.5-flash&context=1000000'
);

$instructrice->get(
...,
llm: 'anthropic://:api_key@api.anthropic.com?model=claude-3-haiku-20240307&context=200000'
);
```

#### LLMInterface

You may also implement [LLMInterface](src/LLM/LLMInterface.php).

## Acknowledgements
Expand Down
8 changes: 4 additions & 4 deletions src/Instructrice.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
class Instructrice
{
public function __construct(
private readonly ProviderModel|LLMConfig $defaultLlm,
private readonly ProviderModel|LLMConfig|string $defaultLlm,
private readonly LLMFactory $llmFactory,
private readonly LoggerInterface $logger,
private readonly SchemaFactory $schemaFactory,
Expand All @@ -50,7 +50,7 @@ public function get(
?string $prompt = null,
array $options = [],
?callable $onChunk = null,
LLMInterface|LLMConfig|ProviderModel|null $llm = null,
LLMInterface|LLMConfig|ProviderModel|string|null $llm = null,
) {
$denormalize = fn (mixed $data) => $data;
$schema = $type;
Expand Down Expand Up @@ -89,7 +89,7 @@ public function list(
?string $prompt = null,
array $options = [],
?callable $onChunk = null,
LLMInterface|LLMConfig|ProviderModel|null $llm = null,
LLMInterface|LLMConfig|ProviderModel|string|null $llm = null,
): array {
$wrappedWithProperty = 'list';
$schema = [
Expand Down Expand Up @@ -145,7 +145,7 @@ private function getAndDenormalize(
string $prompt,
bool $truncateAutomatically = false,
?callable $onChunk = null,
LLMInterface|LLMConfig|ProviderModel|null $llm = null,
LLMInterface|LLMConfig|ProviderModel|string|null $llm = null,
): mixed {
if (($schema['type'] ?? null) !== 'object') {
$wrappedWithProperty = 'inner';
Expand Down
4 changes: 2 additions & 2 deletions src/LLM/Cost.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
class Cost
{
public function __construct(
public readonly float $millionPromptTokensPrice,
public readonly float $millionCompletionTokensPrice,
public readonly float $millionPromptTokensPrice = 0,
public readonly float $millionCompletionTokensPrice = 0,
) {
}

Expand Down
122 changes: 122 additions & 0 deletions src/LLM/DSNParser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
<?php

declare(strict_types=1);

namespace AdrienBrault\Instructrice\LLM;

use AdrienBrault\Instructrice\LLM\Client\AnthropicLLM;
use AdrienBrault\Instructrice\LLM\Client\GoogleLLM;
use AdrienBrault\Instructrice\LLM\Client\OpenAiLLM;
use InvalidArgumentException;

use function Psl\Type\int;
use function Psl\Type\literal_scalar;
use function Psl\Type\optional;
use function Psl\Type\shape;
use function Psl\Type\string;
use function Psl\Type\union;

class DSNParser
{
public function parse(string $dsn): LLMConfig
{
$parsedUrl = parse_url($dsn);

if (! \is_array($parsedUrl)) {
throw new InvalidArgumentException('The DSN could not be parsed');
}

$parsedUrl = shape([
'scheme' => string(),
'pass' => optional(string()),
'host' => string(),
'port' => optional(int()),
'path' => optional(string()),
'query' => string(),
], true)->coerce($parsedUrl);

$apiKey = $parsedUrl['pass'] ?? null;
$host = $parsedUrl['host'];
$port = $parsedUrl['port'] ?? null;
$path = $parsedUrl['path'] ?? null;
$query = $parsedUrl['query'];

$hostWithPort = $host . ($port === null ? '' : ':' . $port);

$client = union(
literal_scalar('openai'),
literal_scalar('openai-http'),
literal_scalar('anthropic'),
literal_scalar('google')
)->coerce($parsedUrl['scheme']);

parse_str($query, $parsedQuery);
$model = $parsedQuery['model'];
$strategyName = $parsedQuery['strategy'] ?? null;
$context = (int) ($parsedQuery['context'] ?? null);

if (! \is_string($model)) {
throw new InvalidArgumentException('The DSN "model" query string must be a string');
}

if ($context <= 0) {
throw new InvalidArgumentException('The DSN "context" query string must be a positive integer');
}

$scheme = 'https';

$strategy = null;
if ($strategyName === 'json') {
$strategy = OpenAiJsonStrategy::JSON;
} elseif ($strategyName === 'json_with_schema') {
$strategy = OpenAiJsonStrategy::JSON_WITH_SCHEMA;
} elseif ($strategyName === 'tool_any') {
$strategy = OpenAiToolStrategy::ANY;
} elseif ($strategyName === 'tool_auto') {
$strategy = OpenAiToolStrategy::AUTO;
} elseif ($strategyName === 'tool_function') {
$strategy = OpenAiToolStrategy::FUNCTION;
}

if ($client === 'anthropic') {
$headers = [
'x-api-key' => $apiKey,
];
$llmClass = AnthropicLLM::class;
$path ??= '/v1/messages';
} elseif ($client === 'google') {
$headers = [
'x-api-key' => $apiKey,
];
$llmClass = GoogleLLM::class;
$path ??= '/v1beta/models';
} elseif ($client === 'openai' || $client === 'openai-http') {
$path ??= '/v1/chat/completions';
$headers = $apiKey === null ? [] : [
'Authorization' => 'Bearer ' . $apiKey,
];

$llmClass = OpenAiLLM::class;

if ($client === 'openai-http') {
$scheme = 'http';
}
} else {
throw new InvalidArgumentException(sprintf('Unknown client "%s", use one of %s', $client, implode(', ', ['openai', 'anthropic', 'google'])));
}

$uri = $scheme . '://' . $hostWithPort . $path;

return new LLMConfig(
$uri,
$model,
$context,
$model,
$hostWithPort,
new Cost(),
$strategy,
headers: $headers,
llmClass: $llmClass
);
}
}
2 changes: 2 additions & 0 deletions src/LLM/LLMConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class LLMConfig
* @param callable(mixed, string): string $systemPrompt
* @param array<string, mixed> $headers
* @param list<string> $stopTokens
* @param class-string<LLMInterface> $llmClass
*/
public function __construct(
public readonly string $uri,
Expand All @@ -29,6 +30,7 @@ public function __construct(
public readonly ?int $maxTokens = null,
public readonly ?string $docUrl = null,
array|false|null $stopTokens = null,
public readonly ?string $llmClass = null,
) {
if ($stopTokens !== false) {
$this->stopTokens = $stopTokens ?? ["```\n\n", '<|im_end|>', "\n\n\n", "\t\n\t\n"];
Expand Down
15 changes: 12 additions & 3 deletions src/LLM/LLMFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,18 +42,23 @@ public function __construct(
private readonly array $apiKeys = [],
private readonly Gpt3Tokenizer $tokenizer = new Gpt3Tokenizer(new Gpt3TokenizerConfig()),
private readonly ParserInterface $parser = new JsonParser(),
private readonly DSNParser $dsnParser = new DSNParser(),
) {
}

public function create(LLMConfig|ProviderModel $config): LLMInterface
public function create(LLMConfig|ProviderModel|string $config): LLMInterface
{
if (\is_string($config)) {
$config = $this->dsnParser->parse($config);
}

if ($config instanceof ProviderModel) {
$apiKey = $this->apiKeys[$config::class] ?? null;
$apiKey ??= self::getProviderModelApiKey($config, true) ?? 'sk-xxx';
$config = $config->createConfig($apiKey);
}

if (str_contains($config->uri, 'api.anthropic.com')) {
if ($config->llmClass === AnthropicLLM::class) {
return new AnthropicLLM(
$this->client,
$this->logger,
Expand All @@ -62,7 +67,7 @@ public function create(LLMConfig|ProviderModel $config): LLMInterface
);
}

if (str_contains($config->uri, 'googleapis.com')) {
if ($config->llmClass === GoogleLLM::class) {
return new GoogleLLM(
$config,
$this->client,
Expand All @@ -72,6 +77,10 @@ public function create(LLMConfig|ProviderModel $config): LLMInterface
);
}

if ($config->llmClass !== OpenAiLLM::class && $config->llmClass !== null) {
throw new InvalidArgumentException(sprintf('Unknown LLM class %s', $config->llmClass));
}

return new OpenAiLLM(
$config,
$this->client,
Expand Down
4 changes: 3 additions & 1 deletion src/LLM/Provider/Anthropic.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace AdrienBrault\Instructrice\LLM\Provider;

use AdrienBrault\Instructrice\LLM\Client\AnthropicLLM;
use AdrienBrault\Instructrice\LLM\Cost;
use AdrienBrault\Instructrice\LLM\LLMConfig;

Expand Down Expand Up @@ -63,7 +64,8 @@ public function createConfig(string $apiKey): LLMConfig
headers: [
'x-api-key' => $apiKey,
],
docUrl: 'https://docs.anthropic.com/claude/docs/models-overview'
docUrl: 'https://docs.anthropic.com/claude/docs/models-overview',
llmClass: AnthropicLLM::class,
);
}
}
4 changes: 3 additions & 1 deletion src/LLM/Provider/Google.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace AdrienBrault\Instructrice\LLM\Provider;

use AdrienBrault\Instructrice\LLM\Client\GoogleLLM;
use AdrienBrault\Instructrice\LLM\Cost;
use AdrienBrault\Instructrice\LLM\LLMConfig;

Expand Down Expand Up @@ -36,7 +37,8 @@ public function createConfig(string $apiKey): LLMConfig
headers: [
'x-api-key' => $apiKey,
],
docUrl: 'https://ai.google.dev/gemini-api/docs/models/gemini'
docUrl: 'https://ai.google.dev/gemini-api/docs/models/gemini',
llmClass: GoogleLLM::class,
);
}
}
Loading

0 comments on commit 942f6d7

Please sign in to comment.