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
30 changes: 30 additions & 0 deletions config/cortex.php
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,36 @@
],
],

/*
|--------------------------------------------------------------------------
| Prompt Factories
|--------------------------------------------------------------------------
|
| Here you may define the prompt factories.
|
| Supported drivers: "langfuse", "mcp"
|
*/
'prompt_factory' => [
'default' => env('CORTEX_DEFAULT_PROMPT_FACTORY', 'langfuse'),

'langfuse' => [
'username' => env('LANGFUSE_USERNAME', ''),
'password' => env('LANGFUSE_PASSWORD', ''),
'base_uri' => env('LANGFUSE_BASE_URI', 'https://cloud.langfuse.com'),

'cache' => [
'enabled' => env('CORTEX_PROMPT_CACHE_ENABLED', false),
'store' => env('CORTEX_PROMPT_CACHE_STORE', null),
'ttl' => env('CORTEX_PROMPT_CACHE_TTL', 3600),
],
],

// 'mcp' => [
// 'base_uri' => env('MCP_BASE_URI', 'http://localhost:3000'),
// ],
],

/*
|--------------------------------------------------------------------------
| Embedding Providers
Expand Down
37 changes: 36 additions & 1 deletion scratchpad.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
declare(strict_types=1);

use Cortex\Cortex;
use Cortex\Prompts\Prompt;
use Cortex\JsonSchema\SchemaFactory;
use Cortex\LLM\Data\Messages\UserMessage;
use Cortex\LLM\Data\Messages\SystemMessage;
Expand All @@ -18,11 +19,45 @@

// Or more simply
$result = Cortex::prompt('What is the capital of {country}?')
->llm('anthropic', 'claude-3-5-sonnet-20240620')
->metadata(
provider: 'anthropic',
model: 'claude-3-5-sonnet-20240620',
structuredOutput: SchemaFactory::object()->properties(
SchemaFactory::string('capital'),
),
)
->llm()
->invoke([
'country' => 'France',
]);

// Get a text prompt builder
$prompt = Cortex::prompt('What is the capital of {country}?');

// Get a chat prompt builder
$prompt = Cortex::prompt([
new SystemMessage('You are an expert at geography.'),
new UserMessage('What is the capital of {country}?'),
]);

// Get a prompt from the given factory
$prompt = Cortex::prompt()->factory('langfuse')->make('test-prompt');

// Get a chat prompt builder
$prompt = Cortex::prompt()->builder('chat')->messages([
new SystemMessage('You are an expert at geography.'),
new UserMessage('What is the capital of {country}?'),
]);

// Get a text prompt builder
$prompt = Cortex::prompt()->builder('text');

$prompt = Prompt::factory()->make('test-prompt');
$prompt = Prompt::builder()->messages([
new SystemMessage('You are an expert at geography.'),
new UserMessage('What is the capital of {country}?'),
]);

// Uses default llm driver
$result = Cortex::llm()->invoke([
new SystemMessage('You are a helpful assistant'),
Expand Down
33 changes: 21 additions & 12 deletions src/Cortex.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,35 @@
namespace Cortex;

use Cortex\Facades\LLM;
use Cortex\Prompts\Prompt;
use Cortex\Facades\Embeddings;
use Cortex\Tasks\Enums\TaskType;
use Cortex\Tasks\Builders\TextTaskBuilder;
use Cortex\Prompts\Contracts\PromptBuilder;
use Cortex\LLM\Contracts\LLM as LLMContract;
use Cortex\Prompts\Builders\ChatPromptBuilder;
use Cortex\LLM\Data\Messages\MessageCollection;
use Cortex\Tasks\Builders\StructuredTaskBuilder;
use Cortex\Embeddings\Contracts\Embeddings as EmbeddingsContract;

class Cortex
{
/**
* Create a chat prompt builder.
* Create a prompt builder or factory.
*
* @param \Cortex\LLM\Data\Messages\MessageCollection|array<int, \Cortex\LLM\Contracts\Message>|string $messages
* @param \Cortex\LLM\Data\Messages\MessageCollection|array<int, \Cortex\LLM\Contracts\Message|\Cortex\LLM\Data\Messages\MessagePlaceholder>|string|null $messages
*
* @return ($messages is null ? \Cortex\Prompts\Prompt : ($messages is string ? \Cortex\Prompts\Builders\TextPromptBuilder : \Cortex\Prompts\Builders\ChatPromptBuilder))
*/
public static function prompt(
MessageCollection|array|string|null $messages,
): ChatPromptBuilder {
$builder = new ChatPromptBuilder();

if ($messages !== null) {
$builder->messages($messages);
MessageCollection|array|string|null $messages = null,
): Prompt|PromptBuilder {
if (func_num_args() === 0) {
return new Prompt();
}

return $builder;
return is_string($messages)
? Prompt::builder('text')->text($messages)
: Prompt::builder('chat')->messages($messages);
}

/**
Expand All @@ -47,9 +50,15 @@ public static function llm(?string $provider = null, ?string $model = null): LLM
return $llm;
}

public static function embeddings(?string $driver = null): EmbeddingsContract
public static function embeddings(?string $driver = null, ?string $model = null): EmbeddingsContract
{
return Embeddings::driver($driver);
$embeddings = Embeddings::driver($driver);

if ($model !== null) {
$embeddings->withModel($model);
}

return $embeddings;
}

/**
Expand Down
6 changes: 6 additions & 0 deletions src/CortexServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
use Cortex\ModelInfo\ModelInfoFactory;
use Spatie\LaravelPackageTools\Package;
use Cortex\Embeddings\EmbeddingsManager;
use Cortex\Prompts\PromptFactoryManager;
use Cortex\Embeddings\Contracts\Embeddings;
use Cortex\Prompts\Contracts\PromptFactory;
use Illuminate\Contracts\Container\Container;
use Spatie\LaravelPackageTools\PackageServiceProvider;

Expand All @@ -30,6 +32,10 @@ public function packageRegistered(): void
$this->app->alias('cortex.embeddings', EmbeddingsManager::class);
$this->app->bind(Embeddings::class, fn(Container $app) => $app->make('cortex.embeddings')->driver());

$this->app->singleton('cortex.prompt_factory', fn(Container $app): PromptFactoryManager => new PromptFactoryManager($app));
$this->app->alias('cortex.prompt_factory', PromptFactoryManager::class);
$this->app->bind(PromptFactory::class, fn(Container $app) => $app->make('cortex.prompt_factory')->driver());

$this->app->singleton('cortex.model_info_factory', function (Container $app): ModelInfoFactory {
$providers = [];
foreach ($app->make('config')->get('cortex.model_info_providers', []) as $provider => $config) {
Expand Down
22 changes: 22 additions & 0 deletions src/Facades/PromptFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

declare(strict_types=1);

namespace Cortex\Facades;

use Illuminate\Support\Facades\Facade;

/**
* @method static \Cortex\Prompts\Contracts\PromptFactory driver(string $driver = null)
* @method static \Cortex\Prompts\Contracts\PromptFactory make(string $name, array<string, mixed> $options = [])
* @method static string getDefaultDriver()
*
* @see \Cortex\Prompts\PromptFactoryManager
*/
class PromptFactory extends Facade
{
protected static function getFacadeAccessor(): string
{
return 'cortex.prompt_factory';
}
}
2 changes: 2 additions & 0 deletions src/LLM/AbstractLLM.php
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,8 @@ public function shouldApplyFormatInstructions(bool $applyFormatInstructions = tr

/**
* Apply the given format instructions to the messages.
*
* @return \Cortex\LLM\Data\Messages\MessageCollection<int, \Cortex\LLM\Contracts\Message>
*/
protected static function applyFormatInstructions(
MessageCollection $messages,
Expand Down
4 changes: 2 additions & 2 deletions src/LLM/Data/ChatStreamResult.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,13 @@ public static function fake(?string $string = null, ?ToolCallCollection $toolCal
index: $index,
createdAt: new DateTimeImmutable(),
finishReason: $isFinal ? FinishReason::Stop : null,
contentSoFar: $contentSoFar,
isFinal: $isFinal,
usage: new Usage(
promptTokens: 0,
completionTokens: $index,
totalTokens: $index,
),
contentSoFar: $contentSoFar,
isFinal: $isFinal,
);

// $this->dispatchEvent(
Expand Down
7 changes: 5 additions & 2 deletions src/LLM/Drivers/AnthropicChat.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ public function invoke(
throw new LLMException('You must provide at least one message to the LLM.');
}

// Apply format instructions if applicable.
if ($this->shouldApplyFormatInstructions && $formatInstructions = $this->outputParser?->formatInstructions()) {
$messages = static::applyFormatInstructions($messages, $formatInstructions);
}

[$systemMessages, $messages] = $messages->partition(
fn(Message $message): bool => $message instanceof SystemMessage,
);
Expand All @@ -83,8 +88,6 @@ public function invoke(
$params['system'] = $systemMessage->text();
}

// TODO: Apply format instructions if applicable.

$this->dispatchEvent(new ChatModelStart($messages, $params));

try {
Expand Down
20 changes: 15 additions & 5 deletions src/LLM/Drivers/OpenAIChat.php
Original file line number Diff line number Diff line change
Expand Up @@ -443,11 +443,21 @@ protected function buildParams(array $additionalParameters): array
...$additionalParameters,
];

if ($this->modelProvider === ModelProvider::OpenAI && isset($allParams['max_tokens'])) {
// `max_tokens` is deprecated in favour of `max_completion_tokens`
// https://platform.openai.com/docs/api-reference/chat/create#chat-create-max_tokens
$allParams['max_completion_tokens'] = $allParams['max_tokens'];
unset($allParams['max_tokens']);
if ($this->modelProvider === ModelProvider::OpenAI) {
if (array_key_exists('max_tokens', $allParams)) {
// `max_tokens` is deprecated in favour of `max_completion_tokens`
// https://platform.openai.com/docs/api-reference/chat/create#chat-create-max_tokens
$allParams['max_completion_tokens'] = $allParams['max_tokens'];
unset($allParams['max_tokens']);
}

if (array_key_exists('temperature', $allParams) && $allParams['temperature'] === null) {
unset($allParams['temperature']);
}

if (array_key_exists('top_p', $allParams) && $allParams['top_p'] === null) {
unset($allParams['top_p']);
}
}

return $allParams;
Expand Down
4 changes: 2 additions & 2 deletions src/LLM/LLMManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ protected function createDriver($driver): LLM // @pest-ignore-type
/**
* Create an OpenAI LLM driver instance.
*
* @param array<string, mixed> $config
* @param array{default_model?: string, model_provider?: string, default_parameters: array<string, mixed>, options: array{api_key?: string, organization?: string, base_uri?: string, headers?: array<string, string>, query_params?: array<string, string>}} $config
*/
public function createOpenAIDriver(array $config, string $name): OpenAIChat|CacheDecorator
{
Expand Down Expand Up @@ -142,7 +142,7 @@ public function createAnthropicDriver(array $config, string $name): AnthropicCha
}

/**
* @param array<string, mixed> $config
* @param array{model_provider?: string} $config
*
* @throws \InvalidArgumentException
*/
Expand Down
4 changes: 3 additions & 1 deletion src/OutputParsers/AbstractOutputParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,11 @@ public function formatInstructions(): ?string
/**
* Set the format instructions to use in the prompt.
*/
public function setFormatInstructions(string $formatInstructions): void
public function withFormatInstructions(string $formatInstructions): self
{
$this->formatInstructions = $formatInstructions;

return $this;
}

public function handlePipeable(mixed $payload, Closure $next): mixed
Expand Down
6 changes: 3 additions & 3 deletions src/OutputParsers/EnumOutputParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,16 @@
class EnumOutputParser extends AbstractOutputParser
{
/**
* @param class-string<BackedEnum> $enum
* @param class-string<\BackedEnum> $enum
*/
public function __construct(
protected string $enum,
) {}

public function parse(ChatGeneration|ChatGenerationChunk|string $output): BackedEnum
{
if (! enum_exists($this->enum)) {
throw OutputParserException::failed(sprintf('Enum %s does not exist', $this->enum));
if (! enum_exists($this->enum) || ! is_subclass_of($this->enum, BackedEnum::class)) {
throw OutputParserException::failed(sprintf('Invalid enum: %s', $this->enum));
}

$enumName = class_basename($this->enum);
Expand Down
2 changes: 1 addition & 1 deletion src/OutputParsers/StructuredOutputParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public function parse(ChatGeneration|ChatGenerationChunk|string $output): array
$parser = match (true) {
is_string($output) => new JsonOutputParser(),
// If the message has tool calls and no text, assume we are using the schema tool
$output->message->hasToolCalls() && in_array($output->message->text(), [null, '', '0'], true) => new JsonOutputToolsParser(singleToolCall: true),
$output->message->hasToolCalls() && in_array($output->message->text(), [null, ''], true) => new JsonOutputToolsParser(singleToolCall: true),
default => new JsonOutputParser(),
};

Expand Down
4 changes: 2 additions & 2 deletions src/Prompts/Builders/ChatPromptBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class ChatPromptBuilder implements PromptBuilder
use BuildsPrompts;

/**
* @var \Cortex\LLM\Data\Messages\MessageCollection|array<int, \Cortex\LLM\Contracts\Message>|string
* @var \Cortex\LLM\Data\Messages\MessageCollection|array<int, \Cortex\LLM\Contracts\Message|\Cortex\LLM\Data\Messages\MessagePlaceholder>|string
*/
protected MessageCollection|array|string $messages = [];

Expand All @@ -32,7 +32,7 @@ public function build(): PromptTemplate&Pipeable
}

/**
* @param \Cortex\LLM\Data\Messages\MessageCollection|array<int, \Cortex\LLM\Contracts\Message>|string $messages
* @param \Cortex\LLM\Data\Messages\MessageCollection|non-empty-array<int, \Cortex\LLM\Contracts\Message|\Cortex\LLM\Data\Messages\MessagePlaceholder>|string $messages
*/
public function messages(MessageCollection|array|string $messages): self
{
Expand Down
12 changes: 10 additions & 2 deletions src/Prompts/Builders/Concerns/BuildsPrompts.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,22 @@ public function metadata(
StructuredOutputConfig|ObjectSchema|string|null $structuredOutput = null,
array $additional = [],
): self {
$this->metadata = new PromptMetadata(
return $this->setMetadata(new PromptMetadata(
$provider,
$model,
$parameters,
$tools,
$structuredOutput,
$additional,
);
));
}

/**
* Set the metadata for the prompt.
*/
public function setMetadata(PromptMetadata $metadata): self
{
$this->metadata = $metadata;

return $this;
}
Expand Down
Loading
Loading