├── compose.yaml ├── ollama.http ├── src ├── Platform │ ├── Message │ │ ├── Content │ │ │ ├── ContentInterface.php │ │ │ ├── Image.php │ │ │ ├── Audio.php │ │ │ ├── Document.php │ │ │ ├── Text.php │ │ │ ├── ImageUrl.php │ │ │ └── DocumentUrl.php │ │ ├── Role.php │ │ ├── MessageInterface.php │ │ ├── SystemMessage.php │ │ ├── MessageBagInterface.php │ │ ├── ToolCallMessage.php │ │ ├── AssistantMessage.php │ │ ├── Message.php │ │ └── UserMessage.php │ ├── Exception │ │ ├── ExceptionInterface.php │ │ ├── ContentFilterException.php │ │ ├── RuntimeException.php │ │ ├── InvalidArgumentException.php │ │ └── UnexpectedResponseTypeException.php │ ├── Bridge │ │ ├── OpenAI │ │ │ ├── Whisper │ │ │ │ ├── Task.php │ │ │ │ ├── ResponseConverter.php │ │ │ │ ├── AudioNormalizer.php │ │ │ │ └── ModelClient.php │ │ │ ├── DallE │ │ │ │ ├── UrlImage.php │ │ │ │ ├── Base64Image.php │ │ │ │ └── ImageResponse.php │ │ │ ├── Embeddings.php │ │ │ ├── Whisper.php │ │ │ ├── DallE.php │ │ │ ├── Embeddings │ │ │ │ ├── ResponseConverter.php │ │ │ │ └── ModelClient.php │ │ │ ├── TokenOutputProcessor.php │ │ │ └── GPT │ │ │ │ └── ModelClient.php │ │ ├── HuggingFace │ │ │ ├── Output │ │ │ │ ├── Classification.php │ │ │ │ ├── ImageSegment.php │ │ │ │ ├── MaskFill.php │ │ │ │ ├── Token.php │ │ │ │ ├── DetectedObject.php │ │ │ │ ├── SentenceSimilarityResult.php │ │ │ │ ├── ClassificationResult.php │ │ │ │ ├── ImageSegmentationResult.php │ │ │ │ ├── QuestionAnsweringResult.php │ │ │ │ ├── FillMaskResult.php │ │ │ │ ├── ZeroShotClassificationResult.php │ │ │ │ ├── TableQuestionAnsweringResult.php │ │ │ │ ├── TokenClassificationResult.php │ │ │ │ └── ObjectDetectionResult.php │ │ │ ├── Provider.php │ │ │ ├── ApiClient.php │ │ │ ├── Contract │ │ │ │ ├── FileNormalizer.php │ │ │ │ └── MessageBagNormalizer.php │ │ │ ├── PlatformFactory.php │ │ │ └── Task.php │ │ ├── Mistral │ │ │ ├── Contract │ │ │ │ └── ToolNormalizer.php │ │ │ ├── Embeddings.php │ │ │ ├── Embeddings │ │ │ │ ├── ResponseConverter.php │ │ │ │ └── ModelClient.php │ │ │ ├── PlatformFactory.php │ │ │ └── Llm │ │ │ │ └── ModelClient.php │ │ ├── TransformersPHP │ │ │ ├── RawPipelineResponse.php │ │ │ ├── PlatformFactory.php │ │ │ └── PipelineExecution.php │ │ ├── Bedrock │ │ │ ├── BedrockModelClient.php │ │ │ ├── RawBedrockResponse.php │ │ │ ├── Nova │ │ │ │ ├── Nova.php │ │ │ │ └── Contract │ │ │ │ │ ├── ToolNormalizer.php │ │ │ │ │ └── MessageBagNormalizer.php │ │ │ └── PlatformFactory.php │ │ ├── Google │ │ │ ├── Contract │ │ │ │ ├── GoogleContract.php │ │ │ │ ├── UserMessageNormalizer.php │ │ │ │ └── AssistantMessageNormalizer.php │ │ │ ├── Embeddings.php │ │ │ ├── PlatformFactory.php │ │ │ ├── Gemini.php │ │ │ └── Embeddings │ │ │ │ └── TaskType.php │ │ ├── Voyage │ │ │ ├── Voyage.php │ │ │ └── PlatformFactory.php │ │ ├── Azure │ │ │ ├── Meta │ │ │ │ └── PlatformFactory.php │ │ │ └── OpenAI │ │ │ │ └── PlatformFactory.php │ │ ├── Anthropic │ │ │ ├── Contract │ │ │ │ ├── AnthropicContract.php │ │ │ │ ├── DocumentUrlNormalizer.php │ │ │ │ ├── DocumentNormalizer.php │ │ │ │ ├── ImageUrlNormalizer.php │ │ │ │ ├── ToolNormalizer.php │ │ │ │ ├── ImageNormalizer.php │ │ │ │ └── ToolCallMessageNormalizer.php │ │ │ ├── PlatformFactory.php │ │ │ ├── Claude.php │ │ │ └── ModelClient.php │ │ ├── Ollama │ │ │ └── PlatformFactory.php │ │ ├── Replicate │ │ │ ├── LlamaModelClient.php │ │ │ ├── PlatformFactory.php │ │ │ ├── LlamaResponseConverter.php │ │ │ └── Contract │ │ │ │ └── LlamaMessageBagNormalizer.php │ │ ├── Meta │ │ │ ├── Contract │ │ │ │ └── MessageBagNormalizer.php │ │ │ └── Llama.php │ │ ├── OpenRouter │ │ │ └── PlatformFactory.php │ │ └── Albert │ │ │ ├── EmbeddingsModelClient.php │ │ │ ├── GPTModelClient.php │ │ │ └── PlatformFactory.php │ ├── Vector │ │ ├── VectorInterface.php │ │ ├── NullVector.php │ │ └── Vector.php │ ├── Tool │ │ ├── ExecutionReference.php │ │ └── Tool.php │ ├── Response │ │ ├── Metadata │ │ │ └── MetadataAwareTrait.php │ │ ├── BaseResponse.php │ │ ├── TextResponse.php │ │ ├── StreamResponse.php │ │ ├── Exception │ │ │ └── RawResponseAlreadySetException.php │ │ ├── RawResponseInterface.php │ │ ├── ObjectResponse.php │ │ ├── VectorResponse.php │ │ ├── RawHttpResponse.php │ │ ├── RawResponseAwareTrait.php │ │ ├── ChoiceResponse.php │ │ ├── ToolCallResponse.php │ │ ├── ResponseInterface.php │ │ ├── Choice.php │ │ ├── BinaryResponse.php │ │ └── ToolCall.php │ ├── PlatformInterface.php │ ├── ModelClientInterface.php │ ├── ResponseConverterInterface.php │ ├── Capability.php │ ├── Contract │ │ ├── Normalizer │ │ │ ├── Message │ │ │ │ ├── Content │ │ │ │ │ ├── TextNormalizer.php │ │ │ │ │ ├── ImageNormalizer.php │ │ │ │ │ ├── ImageUrlNormalizer.php │ │ │ │ │ └── AudioNormalizer.php │ │ │ │ ├── SystemMessageNormalizer.php │ │ │ │ ├── ToolCallMessageNormalizer.php │ │ │ │ ├── UserMessageNormalizer.php │ │ │ │ ├── AssistantMessageNormalizer.php │ │ │ │ └── MessageBagNormalizer.php │ │ │ ├── ModelContractNormalizer.php │ │ │ ├── Response │ │ │ │ └── ToolCallNormalizer.php │ │ │ └── ToolNormalizer.php │ │ └── JsonSchema │ │ │ └── DescriptionParser.php │ └── Model.php ├── Chain │ ├── Exception │ │ ├── ExceptionInterface.php │ │ ├── LogicException.php │ │ ├── RuntimeException.php │ │ ├── InvalidArgumentException.php │ │ └── MissingModelSupportException.php │ ├── ChainAwareInterface.php │ ├── InputProcessorInterface.php │ ├── Memory │ │ ├── Memory.php │ │ ├── MemoryProviderInterface.php │ │ └── StaticMemoryProvider.php │ ├── OutputProcessorInterface.php │ ├── ChainAwareTrait.php │ ├── Toolbox │ │ ├── Exception │ │ │ ├── ExceptionInterface.php │ │ │ ├── ToolConfigurationException.php │ │ │ ├── ToolExecutionException.php │ │ │ ├── ToolException.php │ │ │ └── ToolNotFoundException.php │ │ ├── ToolCallResult.php │ │ ├── Attribute │ │ │ └── AsTool.php │ │ ├── ToolFactoryInterface.php │ │ ├── Event │ │ │ ├── ToolCallArgumentsResolved.php │ │ │ └── ToolCallsExecuted.php │ │ ├── ToolboxInterface.php │ │ ├── Tool │ │ │ ├── Clock.php │ │ │ ├── Chain.php │ │ │ ├── Crawler.php │ │ │ ├── SerpApi.php │ │ │ ├── YouTubeTranscriber.php │ │ │ ├── SimilaritySearch.php │ │ │ └── Tavily.php │ │ ├── StreamResponse.php │ │ ├── ToolResultConverter.php │ │ ├── ToolFactory │ │ │ ├── ReflectionToolFactory.php │ │ │ ├── MemoryToolFactory.php │ │ │ ├── AbstractToolFactory.php │ │ │ └── ChainFactory.php │ │ ├── FaultTolerantToolbox.php │ │ └── ToolCallArgumentResolver.php │ ├── Chat │ │ ├── MessageStoreInterface.php │ │ └── MessageStore │ │ │ ├── InMemoryStore.php │ │ │ ├── SessionStore.php │ │ │ └── CacheStore.php │ ├── ChatInterface.php │ ├── ChainInterface.php │ ├── StructuredOutput │ │ ├── ResponseFormatFactoryInterface.php │ │ └── ResponseFormatFactory.php │ ├── Output.php │ ├── Input.php │ ├── InputProcessor │ │ └── ModelOverrideInputProcessor.php │ └── Chat.php └── Store │ ├── Exception │ ├── ExceptionInterface.php │ ├── RuntimeException.php │ └── InvalidArgumentException.php │ ├── Document │ ├── Metadata.php │ ├── TextDocument.php │ ├── VectorDocument.php │ ├── LoaderInterface.php │ ├── TransformerInterface.php │ ├── Transformer │ │ ├── ChainTransformer.php │ │ └── ChunkDelayTransformer.php │ ├── Loader │ │ └── TextFileLoader.php │ └── Vectorizer.php │ ├── StoreInterface.php │ ├── InitializableStoreInterface.php │ ├── VectorStoreInterface.php │ └── Indexer.php └── LICENSE /compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | mariadb: 3 | image: mariadb:11.7 4 | environment: 5 | MARIADB_ALLOW_EMPTY_ROOT_PASSWORD: 1 6 | MARIADB_DATABASE: my_database 7 | ports: 8 | - "3309:3306" 9 | -------------------------------------------------------------------------------- /ollama.http: -------------------------------------------------------------------------------- 1 | ### Llama 3.2 on Ollama 2 | POST http://localhost:11434/api/chat 3 | 4 | { 5 | "model": "llama3.2", 6 | "messages": [ 7 | { 8 | "role": "user", 9 | "content": "why is the sky blue?" 10 | } 11 | ], 12 | "stream": false 13 | } 14 | -------------------------------------------------------------------------------- /src/Platform/Message/Content/ContentInterface.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | interface ContentInterface 11 | { 12 | } 13 | -------------------------------------------------------------------------------- /src/Platform/Message/Content/Image.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | final readonly class Image extends File 11 | { 12 | } 13 | -------------------------------------------------------------------------------- /src/Chain/Exception/ExceptionInterface.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | interface ExceptionInterface extends \Throwable 11 | { 12 | } 13 | -------------------------------------------------------------------------------- /src/Store/Exception/ExceptionInterface.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | interface ExceptionInterface extends \Throwable 11 | { 12 | } 13 | -------------------------------------------------------------------------------- /src/Platform/Exception/ExceptionInterface.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | interface ExceptionInterface extends \Throwable 11 | { 12 | } 13 | -------------------------------------------------------------------------------- /src/Platform/Message/Content/Audio.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | final readonly class Audio extends File 11 | { 12 | } 13 | -------------------------------------------------------------------------------- /src/Platform/Message/Content/Document.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | final readonly class Document extends File 11 | { 12 | } 13 | -------------------------------------------------------------------------------- /src/Chain/Exception/LogicException.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class LogicException extends \LogicException implements ExceptionInterface 11 | { 12 | } 13 | -------------------------------------------------------------------------------- /src/Platform/Exception/ContentFilterException.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class ContentFilterException extends InvalidArgumentException 11 | { 12 | } 13 | -------------------------------------------------------------------------------- /src/Chain/Exception/RuntimeException.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class RuntimeException extends \RuntimeException implements ExceptionInterface 11 | { 12 | } 13 | -------------------------------------------------------------------------------- /src/Store/Exception/RuntimeException.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class RuntimeException extends \RuntimeException implements ExceptionInterface 11 | { 12 | } 13 | -------------------------------------------------------------------------------- /src/Chain/ChainAwareInterface.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | interface ChainAwareInterface 11 | { 12 | public function setChain(ChainInterface $chain): void; 13 | } 14 | -------------------------------------------------------------------------------- /src/Platform/Exception/RuntimeException.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class RuntimeException extends \RuntimeException implements ExceptionInterface 11 | { 12 | } 13 | -------------------------------------------------------------------------------- /src/Chain/InputProcessorInterface.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | interface InputProcessorInterface 11 | { 12 | public function processInput(Input $input): void; 13 | } 14 | -------------------------------------------------------------------------------- /src/Chain/Memory/Memory.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | final readonly class Memory 11 | { 12 | public function __construct(public string $content) 13 | { 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Chain/OutputProcessorInterface.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | interface OutputProcessorInterface 11 | { 12 | public function processOutput(Output $output): void; 13 | } 14 | -------------------------------------------------------------------------------- /src/Store/Document/Metadata.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * @author Christopher Hertel 11 | */ 12 | final class Metadata extends \ArrayObject 13 | { 14 | } 15 | -------------------------------------------------------------------------------- /src/Chain/Exception/InvalidArgumentException.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface 11 | { 12 | } 13 | -------------------------------------------------------------------------------- /src/Store/Exception/InvalidArgumentException.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface 11 | { 12 | } 13 | -------------------------------------------------------------------------------- /src/Platform/Exception/InvalidArgumentException.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface 11 | { 12 | } 13 | -------------------------------------------------------------------------------- /src/Store/StoreInterface.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | interface StoreInterface 13 | { 14 | public function add(VectorDocument ...$documents): void; 15 | } 16 | -------------------------------------------------------------------------------- /src/Platform/Bridge/OpenAI/Whisper/Task.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | interface Task 11 | { 12 | public const TRANSCRIPTION = 'transcription'; 13 | public const TRANSLATION = 'translation'; 14 | } 15 | -------------------------------------------------------------------------------- /src/Platform/Message/Content/Text.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | final readonly class Text implements ContentInterface 11 | { 12 | public function __construct( 13 | public string $text, 14 | ) { 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Platform/Message/Content/ImageUrl.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | final readonly class ImageUrl implements ContentInterface 11 | { 12 | public function __construct( 13 | public string $url, 14 | ) { 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Chain/ChainAwareTrait.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | trait ChainAwareTrait 11 | { 12 | private ChainInterface $chain; 13 | 14 | public function setChain(ChainInterface $chain): void 15 | { 16 | $this->chain = $chain; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Platform/Message/Content/DocumentUrl.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | final readonly class DocumentUrl implements ContentInterface 11 | { 12 | public function __construct( 13 | public string $url, 14 | ) { 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Chain/Toolbox/Exception/ExceptionInterface.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | interface ExceptionInterface extends BaseExceptionInterface 13 | { 14 | } 15 | -------------------------------------------------------------------------------- /src/Platform/Vector/VectorInterface.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | interface VectorInterface 11 | { 12 | /** 13 | * @return list 14 | */ 15 | public function getData(): array; 16 | 17 | public function getDimensions(): int; 18 | } 19 | -------------------------------------------------------------------------------- /src/Platform/Tool/ExecutionReference.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | final class ExecutionReference 11 | { 12 | public function __construct( 13 | public string $class, 14 | public string $method = '__invoke', 15 | ) { 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Store/InitializableStoreInterface.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | interface InitializableStoreInterface extends StoreInterface 11 | { 12 | /** 13 | * @param array $options 14 | */ 15 | public function initialize(array $options = []): void; 16 | } 17 | -------------------------------------------------------------------------------- /src/Chain/Memory/MemoryProviderInterface.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | interface MemoryProviderInterface 13 | { 14 | /** 15 | * @return list 16 | */ 17 | public function loadMemory(Input $input): array; 18 | } 19 | -------------------------------------------------------------------------------- /src/Chain/Chat/MessageStoreInterface.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | final readonly class Classification 11 | { 12 | public function __construct( 13 | public string $label, 14 | public float $score, 15 | ) { 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Platform/Response/Metadata/MetadataAwareTrait.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | trait MetadataAwareTrait 11 | { 12 | private ?Metadata $metadata = null; 13 | 14 | public function getMetadata(): Metadata 15 | { 16 | return $this->metadata ??= new Metadata(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Platform/Message/Role.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | enum Role: string 13 | { 14 | use Comparable; 15 | 16 | case System = 'system'; 17 | case Assistant = 'assistant'; 18 | case User = 'user'; 19 | case ToolCall = 'tool'; 20 | } 21 | -------------------------------------------------------------------------------- /src/Chain/Toolbox/ToolCallResult.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | final readonly class ToolCallResult 13 | { 14 | public function __construct( 15 | public ToolCall $toolCall, 16 | public mixed $result, 17 | ) { 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Platform/Bridge/HuggingFace/Output/ImageSegment.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | final readonly class ImageSegment 11 | { 12 | public function __construct( 13 | public string $label, 14 | public ?float $score, 15 | public string $mask, 16 | ) { 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Platform/Message/MessageInterface.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | interface MessageInterface 14 | { 15 | public function getRole(): Role; 16 | 17 | public function getId(): AbstractUid&TimeBasedUidInterface; 18 | } 19 | -------------------------------------------------------------------------------- /src/Platform/Bridge/OpenAI/DallE/UrlImage.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | final readonly class UrlImage 13 | { 14 | public function __construct( 15 | public string $url, 16 | ) { 17 | Assert::stringNotEmpty($url, 'The image url must be given.'); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Chain/ChatInterface.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | abstract class BaseResponse implements ResponseInterface 15 | { 16 | use MetadataAwareTrait; 17 | use RawResponseAwareTrait; 18 | } 19 | -------------------------------------------------------------------------------- /src/Platform/Bridge/HuggingFace/Output/MaskFill.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | final readonly class MaskFill 11 | { 12 | public function __construct( 13 | public int $token, 14 | public string $tokenStr, 15 | public string $sequence, 16 | public float $score, 17 | ) { 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Platform/Response/TextResponse.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | final class TextResponse extends BaseResponse 11 | { 12 | public function __construct( 13 | private readonly string $content, 14 | ) { 15 | } 16 | 17 | public function getContent(): string 18 | { 19 | return $this->content; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Platform/Bridge/HuggingFace/Output/Token.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | final readonly class Token 11 | { 12 | public function __construct( 13 | public string $entityGroup, 14 | public float $score, 15 | public string $word, 16 | public int $start, 17 | public int $end, 18 | ) { 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Platform/Response/StreamResponse.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | final class StreamResponse extends BaseResponse 11 | { 12 | public function __construct( 13 | private readonly \Generator $generator, 14 | ) { 15 | } 16 | 17 | public function getContent(): \Generator 18 | { 19 | yield from $this->generator; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Chain/Toolbox/Attribute/AsTool.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)] 11 | final readonly class AsTool 12 | { 13 | public function __construct( 14 | public string $name, 15 | public string $description, 16 | public string $method = '__invoke', 17 | ) { 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Platform/Bridge/OpenAI/DallE/Base64Image.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | final readonly class Base64Image 13 | { 14 | public function __construct( 15 | public string $encodedImage, 16 | ) { 17 | Assert::stringNotEmpty($encodedImage, 'The base64 encoded image generated must be given.'); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Platform/Response/Exception/RawResponseAlreadySetException.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | final class RawResponseAlreadySetException extends RuntimeException 13 | { 14 | public function __construct() 15 | { 16 | parent::__construct('The raw response was already set.'); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Chain/ChainInterface.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | interface ChainInterface 14 | { 15 | /** 16 | * @param array $options 17 | */ 18 | public function call(MessageBagInterface $messages, array $options = []): ResponseInterface; 19 | } 20 | -------------------------------------------------------------------------------- /src/Platform/Exception/UnexpectedResponseTypeException.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | interface PlatformInterface 13 | { 14 | /** 15 | * @param array|string|object $input 16 | * @param array $options 17 | */ 18 | public function request(Model $model, array|string|object $input, array $options = []): ResponsePromise; 19 | } 20 | -------------------------------------------------------------------------------- /src/Platform/Bridge/HuggingFace/Output/DetectedObject.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | final readonly class DetectedObject 11 | { 12 | public function __construct( 13 | public string $label, 14 | public float $score, 15 | public float $xmin, 16 | public float $ymin, 17 | public float $xmax, 18 | public float $ymax, 19 | ) { 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Platform/Response/RawResponseInterface.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | interface RawResponseInterface 13 | { 14 | /** 15 | * Returns an array representation of the raw response data. 16 | * 17 | * @return array 18 | */ 19 | public function getRawData(): array; 20 | 21 | public function getRawObject(): object; 22 | } 23 | -------------------------------------------------------------------------------- /src/Store/Document/TextDocument.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | final readonly class TextDocument 14 | { 15 | public function __construct( 16 | public Uuid $id, 17 | public string $content, 18 | public Metadata $metadata = new Metadata(), 19 | ) { 20 | Assert::stringNotEmpty(trim($this->content)); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Chain/Toolbox/ToolFactoryInterface.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | interface ToolFactoryInterface 14 | { 15 | /** 16 | * @return iterable 17 | * 18 | * @throws ToolException if the metadata for the given reference is not found 19 | */ 20 | public function getTool(string $reference): iterable; 21 | } 22 | -------------------------------------------------------------------------------- /src/Store/Document/VectorDocument.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | final readonly class VectorDocument 14 | { 15 | public function __construct( 16 | public Uuid $id, 17 | public VectorInterface $vector, 18 | public Metadata $metadata = new Metadata(), 19 | public ?float $score = null, 20 | ) { 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Store/VectorStoreInterface.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | interface VectorStoreInterface extends StoreInterface 14 | { 15 | /** 16 | * @param array $options 17 | * 18 | * @return VectorDocument[] 19 | */ 20 | public function query(Vector $vector, array $options = [], ?float $minScore = null): array; 21 | } 22 | -------------------------------------------------------------------------------- /src/Platform/ModelClientInterface.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | interface ModelClientInterface 13 | { 14 | public function supports(Model $model): bool; 15 | 16 | /** 17 | * @param array $payload 18 | * @param array $options 19 | */ 20 | public function request(Model $model, array|string $payload, array $options = []): ResponseInterface; 21 | } 22 | -------------------------------------------------------------------------------- /src/Platform/ResponseConverterInterface.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | interface ResponseConverterInterface 14 | { 15 | public function supports(Model $model): bool; 16 | 17 | /** 18 | * @param array $options 19 | */ 20 | public function convert(HttpResponse $response, array $options = []): LlmResponse; 21 | } 22 | -------------------------------------------------------------------------------- /src/Platform/Vector/NullVector.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | final class NullVector implements VectorInterface 13 | { 14 | public function getData(): array 15 | { 16 | throw new RuntimeException('getData() method cannot be called on a NullVector.'); 17 | } 18 | 19 | public function getDimensions(): int 20 | { 21 | throw new RuntimeException('getDimensions() method cannot be called on a NullVector.'); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Chain/Toolbox/Event/ToolCallArgumentsResolved.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | final readonly class ToolCallArgumentsResolved 15 | { 16 | /** 17 | * @param array $arguments 18 | */ 19 | public function __construct( 20 | public object $tool, 21 | public Tool $metadata, 22 | public array $arguments, 23 | ) { 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Platform/Bridge/Mistral/Contract/ToolNormalizer.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class ToolNormalizer extends BaseToolNormalizer 11 | { 12 | public function normalize(mixed $data, ?string $format = null, array $context = []): array 13 | { 14 | $array = parent::normalize($data, $format, $context); 15 | 16 | $array['function']['parameters'] ??= ['type' => 'object']; 17 | 18 | return $array; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Platform/Tool/Tool.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | final readonly class Tool 15 | { 16 | /** 17 | * @param JsonSchema|null $parameters 18 | */ 19 | public function __construct( 20 | public ExecutionReference $reference, 21 | public string $name, 22 | public string $description, 23 | public ?array $parameters = null, 24 | ) { 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Chain/StructuredOutput/ResponseFormatFactoryInterface.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | interface ResponseFormatFactoryInterface 11 | { 12 | /** 13 | * @param class-string $responseClass 14 | * 15 | * @return array{ 16 | * type: 'json_schema', 17 | * json_schema: array{ 18 | * name: string, 19 | * schema: array, 20 | * strict: true, 21 | * } 22 | * } 23 | */ 24 | public function create(string $responseClass): array; 25 | } 26 | -------------------------------------------------------------------------------- /src/Platform/Bridge/HuggingFace/Output/SentenceSimilarityResult.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | final readonly class SentenceSimilarityResult 11 | { 12 | /** 13 | * @param array $similarities 14 | */ 15 | public function __construct( 16 | public array $similarities, 17 | ) { 18 | } 19 | 20 | /** 21 | * @param array $data 22 | */ 23 | public static function fromArray(array $data): self 24 | { 25 | return new self($data); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Platform/Response/ObjectResponse.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | final class ObjectResponse extends BaseResponse 11 | { 12 | /** 13 | * @param object|array $structuredOutput 14 | */ 15 | public function __construct( 16 | private readonly object|array $structuredOutput, 17 | ) { 18 | } 19 | 20 | /** 21 | * @return object|array 22 | */ 23 | public function getContent(): object|array 24 | { 25 | return $this->structuredOutput; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Chain/Toolbox/Exception/ToolConfigurationException.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | final class ToolConfigurationException extends InvalidArgumentException implements ExceptionInterface 13 | { 14 | public static function invalidMethod(string $toolClass, string $methodName, \ReflectionException $previous): self 15 | { 16 | return new self(\sprintf('Method "%s" not found in tool "%s".', $methodName, $toolClass), previous: $previous); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Store/Document/LoaderInterface.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | interface LoaderInterface 11 | { 12 | /** 13 | * @param string $source Identifier for the loader to load the documents from, e.g. file path, folder, or URL. 14 | * @param array $options loader specific set of options to control the loading process 15 | * 16 | * @return iterable iterable of TextDocuments loaded from the source 17 | */ 18 | public function __invoke(string $source, array $options = []): iterable; 19 | } 20 | -------------------------------------------------------------------------------- /src/Platform/Response/VectorResponse.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | final class VectorResponse extends BaseResponse 13 | { 14 | /** 15 | * @var Vector[] 16 | */ 17 | private readonly array $vectors; 18 | 19 | public function __construct(Vector ...$vector) 20 | { 21 | $this->vectors = $vector; 22 | } 23 | 24 | /** 25 | * @return Vector[] 26 | */ 27 | public function getContent(): array 28 | { 29 | return $this->vectors; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Chain/Output.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | final class Output 15 | { 16 | /** 17 | * @param array $options 18 | */ 19 | public function __construct( 20 | public readonly Model $model, 21 | public ResponseInterface $response, 22 | public readonly MessageBagInterface $messages, 23 | public readonly array $options, 24 | ) { 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Platform/Response/RawHttpResponse.php: -------------------------------------------------------------------------------- 1 | response->toArray(false); 22 | } 23 | 24 | public function getRawObject(): ResponseInterface 25 | { 26 | return $this->response; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Platform/Bridge/Mistral/Embeddings.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | final class Embeddings extends Model 14 | { 15 | public const MISTRAL_EMBED = 'mistral-embed'; 16 | 17 | /** 18 | * @param array $options 19 | */ 20 | public function __construct( 21 | string $name = self::MISTRAL_EMBED, 22 | array $options = [], 23 | ) { 24 | parent::__construct($name, [Capability::INPUT_MULTIPLE], $options); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Platform/Bridge/OpenAI/Embeddings.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class Embeddings extends Model 13 | { 14 | public const TEXT_ADA_002 = 'text-embedding-ada-002'; 15 | public const TEXT_3_LARGE = 'text-embedding-3-large'; 16 | public const TEXT_3_SMALL = 'text-embedding-3-small'; 17 | 18 | /** 19 | * @param array $options 20 | */ 21 | public function __construct(string $name = self::TEXT_3_SMALL, array $options = []) 22 | { 23 | parent::__construct($name, [], $options); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Platform/Bridge/HuggingFace/Provider.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | interface Provider 11 | { 12 | public const CEREBRAS = 'cerebras'; 13 | public const COHERE = 'cohere'; 14 | public const FAL_AI = 'fal-ai'; 15 | public const FIREWORKS = 'fireworks-ai'; 16 | public const HYPERBOLIC = 'hyperbolic'; 17 | public const HF_INFERENCE = 'hf-inference'; 18 | public const NEBIUS = 'nebius'; 19 | public const NOVITA = 'novita'; 20 | public const REPLICATE = 'replicate'; 21 | public const SAMBA_NOVA = 'sambanova'; 22 | public const TOGETHER = 'together'; 23 | } 24 | -------------------------------------------------------------------------------- /src/Platform/Bridge/OpenAI/Whisper.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class Whisper extends Model 14 | { 15 | public const WHISPER_1 = 'whisper-1'; 16 | 17 | /** 18 | * @param array $options 19 | */ 20 | public function __construct(string $name = self::WHISPER_1, array $options = []) 21 | { 22 | $capabilities = [ 23 | Capability::INPUT_AUDIO, 24 | Capability::OUTPUT_TEXT, 25 | ]; 26 | 27 | parent::__construct($name, $capabilities, $options); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Platform/Bridge/TransformersPHP/RawPipelineResponse.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | final readonly class RawPipelineResponse implements RawResponseInterface 13 | { 14 | public function __construct( 15 | private PipelineExecution $pipelineExecution, 16 | ) { 17 | } 18 | 19 | public function getRawData(): array 20 | { 21 | return $this->pipelineExecution->getResult(); 22 | } 23 | 24 | public function getRawObject(): PipelineExecution 25 | { 26 | return $this->pipelineExecution; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Platform/Bridge/Bedrock/BedrockModelClient.php: -------------------------------------------------------------------------------- 1 | |string $payload 20 | * @param array $options 21 | */ 22 | public function request(Model $model, array|string $payload, array $options = []): InvokeModelResponse; 23 | 24 | public function convert(InvokeModelResponse $bedrockResponse): ResponseInterface; 25 | } 26 | -------------------------------------------------------------------------------- /src/Chain/Toolbox/Exception/ToolExecutionException.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | final class ToolExecutionException extends \RuntimeException implements ExceptionInterface 13 | { 14 | public ?ToolCall $toolCall = null; 15 | 16 | public static function executionFailed(ToolCall $toolCall, \Throwable $previous): self 17 | { 18 | $exception = new self(\sprintf('Execution of tool "%s" failed with error: %s', $toolCall->name, $previous->getMessage()), previous: $previous); 19 | $exception->toolCall = $toolCall; 20 | 21 | return $exception; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Chain/Toolbox/Event/ToolCallsExecuted.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | final class ToolCallsExecuted 14 | { 15 | /** 16 | * @var ToolCallResult[] 17 | */ 18 | public readonly array $toolCallResults; 19 | public ResponseInterface $response; 20 | 21 | public function __construct(ToolCallResult ...$toolCallResults) 22 | { 23 | $this->toolCallResults = $toolCallResults; 24 | } 25 | 26 | public function hasResponse(): bool 27 | { 28 | return isset($this->response); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Platform/Bridge/HuggingFace/Output/ClassificationResult.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | final class ClassificationResult 11 | { 12 | /** 13 | * @param Classification[] $classifications 14 | */ 15 | public function __construct( 16 | public array $classifications, 17 | ) { 18 | } 19 | 20 | /** 21 | * @param array $data 22 | */ 23 | public static function fromArray(array $data): self 24 | { 25 | return new self( 26 | array_map(fn (array $item) => new Classification($item['label'], $item['score']), $data) 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Platform/Bridge/TransformersPHP/PlatformFactory.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | final readonly class PlatformFactory 14 | { 15 | public static function create(): Platform 16 | { 17 | if (!class_exists(Transformers::class)) { 18 | throw new RuntimeException('For using the TransformersPHP with FFI to run models in PHP, the codewithkyrian/transformers package is required. Try running "composer require codewithkyrian/transformers".'); 19 | } 20 | 21 | return new Platform(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Platform/Response/RawResponseAwareTrait.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | trait RawResponseAwareTrait 13 | { 14 | protected ?RawResponseInterface $rawResponse = null; 15 | 16 | public function setRawResponse(RawResponseInterface $rawResponse): void 17 | { 18 | if (isset($this->rawResponse)) { 19 | throw new RawResponseAlreadySetException(); 20 | } 21 | 22 | $this->rawResponse = $rawResponse; 23 | } 24 | 25 | public function getRawResponse(): ?RawResponseInterface 26 | { 27 | return $this->rawResponse; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Chain/Toolbox/ToolboxInterface.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | interface ToolboxInterface 16 | { 17 | /** 18 | * @return Tool[] 19 | */ 20 | public function getTools(): array; 21 | 22 | /** 23 | * @throws ToolExecutionException if the tool execution fails 24 | * @throws ToolNotFoundException if the tool is not found 25 | */ 26 | public function execute(ToolCall $toolCall): mixed; 27 | } 28 | -------------------------------------------------------------------------------- /src/Platform/Message/SystemMessage.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | final readonly class SystemMessage implements MessageInterface 15 | { 16 | public AbstractUid&TimeBasedUidInterface $id; 17 | 18 | public function __construct(public string $content) 19 | { 20 | $this->id = Uuid::v7(); 21 | } 22 | 23 | public function getRole(): Role 24 | { 25 | return Role::System; 26 | } 27 | 28 | public function getId(): AbstractUid&TimeBasedUidInterface 29 | { 30 | return $this->id; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Chain/Chat/MessageStore/InMemoryStore.php: -------------------------------------------------------------------------------- 1 | messages = $messages; 18 | } 19 | 20 | public function load(): MessageBagInterface 21 | { 22 | return $this->messages ?? new MessageBag(); 23 | } 24 | 25 | public function clear(): void 26 | { 27 | $this->messages = new MessageBag(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Platform/Bridge/Google/Contract/GoogleContract.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | final readonly class GoogleContract extends Contract 14 | { 15 | public static function create(NormalizerInterface ...$normalizer): Contract 16 | { 17 | return parent::create( 18 | new AssistantMessageNormalizer(), 19 | new MessageBagNormalizer(), 20 | new ToolNormalizer(), 21 | new ToolCallMessageNormalizer(), 22 | new UserMessageNormalizer(), 23 | ...$normalizer, 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Platform/Bridge/HuggingFace/Output/ImageSegmentationResult.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | final class ImageSegmentationResult 11 | { 12 | /** 13 | * @param ImageSegment[] $segments 14 | */ 15 | public function __construct( 16 | public array $segments, 17 | ) { 18 | } 19 | 20 | /** 21 | * @param array $data 22 | */ 23 | public static function fromArray(array $data): self 24 | { 25 | return new self( 26 | array_map(fn (array $item) => new ImageSegment($item['label'], $item['score'], $item['mask']), $data) 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Platform/Bridge/OpenAI/DallE.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class DallE extends Model 14 | { 15 | public const DALL_E_2 = 'dall-e-2'; 16 | public const DALL_E_3 = 'dall-e-3'; 17 | 18 | /** @param array $options The default options for the model usage */ 19 | public function __construct(string $name = self::DALL_E_2, array $options = []) 20 | { 21 | $capabilities = [ 22 | Capability::INPUT_TEXT, 23 | Capability::OUTPUT_IMAGE, 24 | ]; 25 | 26 | parent::__construct($name, $capabilities, $options); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Platform/Bridge/OpenAI/DallE/ImageResponse.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class ImageResponse extends BaseResponse 13 | { 14 | /** @var list */ 15 | private readonly array $images; 16 | 17 | public function __construct( 18 | public ?string $revisedPrompt = null, // Only string on Dall-E 3 usage 19 | Base64Image|UrlImage ...$images, 20 | ) { 21 | $this->images = array_values($images); 22 | } 23 | 24 | /** 25 | * @return list 26 | */ 27 | public function getContent(): array 28 | { 29 | return $this->images; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Store/Document/TransformerInterface.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | interface TransformerInterface 15 | { 16 | /** 17 | * @param iterable $documents 18 | * @param array $options 19 | * 20 | * @return iterable 21 | */ 22 | public function __invoke(iterable $documents, array $options = []): iterable; 23 | } 24 | -------------------------------------------------------------------------------- /src/Chain/Toolbox/Exception/ToolException.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | final class ToolException extends InvalidArgumentException implements ExceptionInterface 14 | { 15 | public static function invalidReference(mixed $reference): self 16 | { 17 | return new self(\sprintf('The reference "%s" is not a valid tool.', $reference)); 18 | } 19 | 20 | public static function missingAttribute(string $className): self 21 | { 22 | return new self(\sprintf('The class "%s" is not a tool, please add %s attribute.', $className, AsTool::class)); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Platform/Bridge/Bedrock/RawBedrockResponse.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | final readonly class RawBedrockResponse implements RawResponseInterface 14 | { 15 | public function __construct( 16 | private InvokeModelResponse $invokeModelResponse, 17 | ) { 18 | } 19 | 20 | public function getRawData(): array 21 | { 22 | return json_decode($this->invokeModelResponse->getBody(), true, 512, \JSON_THROW_ON_ERROR); 23 | } 24 | 25 | public function getRawObject(): InvokeModelResponse 26 | { 27 | return $this->invokeModelResponse; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Platform/Bridge/HuggingFace/Output/QuestionAnsweringResult.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | final readonly class QuestionAnsweringResult 11 | { 12 | public function __construct( 13 | public string $answer, 14 | public int $startIndex, 15 | public int $endIndex, 16 | public float $score, 17 | ) { 18 | } 19 | 20 | /** 21 | * @param array{answer: string, start: int, end: int, score: float} $data 22 | */ 23 | public static function fromArray(array $data): self 24 | { 25 | return new self( 26 | $data['answer'], 27 | $data['start'], 28 | $data['end'], 29 | $data['score'], 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Platform/Response/ChoiceResponse.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | final class ChoiceResponse extends BaseResponse 13 | { 14 | /** 15 | * @var Choice[] 16 | */ 17 | private readonly array $choices; 18 | 19 | public function __construct(Choice ...$choices) 20 | { 21 | if (0 === \count($choices)) { 22 | throw new InvalidArgumentException('Response must have at least one choice.'); 23 | } 24 | 25 | $this->choices = $choices; 26 | } 27 | 28 | /** 29 | * @return Choice[] 30 | */ 31 | public function getContent(): array 32 | { 33 | return $this->choices; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Platform/Message/MessageBagInterface.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | interface MessageBagInterface extends \Countable 11 | { 12 | public function add(MessageInterface $message): void; 13 | 14 | /** 15 | * @return list 16 | */ 17 | public function getMessages(): array; 18 | 19 | public function getSystemMessage(): ?SystemMessage; 20 | 21 | public function with(MessageInterface $message): self; 22 | 23 | public function merge(self $messageBag): self; 24 | 25 | public function withoutSystemMessage(): self; 26 | 27 | public function prepend(MessageInterface $message): self; 28 | 29 | public function containsAudio(): bool; 30 | 31 | public function containsImage(): bool; 32 | } 33 | -------------------------------------------------------------------------------- /src/Platform/Bridge/Voyage/Voyage.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class Voyage extends Model 14 | { 15 | public const V3 = 'voyage-3'; 16 | public const V3_LITE = 'voyage-3-lite'; 17 | public const FINANCE_2 = 'voyage-finance-2'; 18 | public const MULTILINGUAL_2 = 'voyage-multilingual-2'; 19 | public const LAW_2 = 'voyage-law-2'; 20 | public const CODE_2 = 'voyage-code-2'; 21 | 22 | /** 23 | * @param array $options 24 | */ 25 | public function __construct(string $name = self::V3, array $options = []) 26 | { 27 | parent::__construct($name, [Capability::INPUT_MULTIPLE], $options); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Platform/Response/ToolCallResponse.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | final class ToolCallResponse extends BaseResponse 13 | { 14 | /** 15 | * @var ToolCall[] 16 | */ 17 | private readonly array $toolCalls; 18 | 19 | public function __construct(ToolCall ...$toolCalls) 20 | { 21 | if (0 === \count($toolCalls)) { 22 | throw new InvalidArgumentException('Response must have at least one tool call.'); 23 | } 24 | 25 | $this->toolCalls = $toolCalls; 26 | } 27 | 28 | /** 29 | * @return ToolCall[] 30 | */ 31 | public function getContent(): array 32 | { 33 | return $this->toolCalls; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Chain/Toolbox/Tool/Clock.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | #[AsTool('clock', description: 'Provides the current date and time.')] 15 | final readonly class Clock 16 | { 17 | public function __construct( 18 | private ClockInterface $clock = new SymfonyClock(), 19 | ) { 20 | } 21 | 22 | public function __invoke(): string 23 | { 24 | return \sprintf( 25 | 'Current date is %s (YYYY-MM-DD) and the time is %s (HH:MM:SS).', 26 | $this->clock->now()->format('Y-m-d'), 27 | $this->clock->now()->format('H:i:s'), 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Platform/Bridge/Azure/Meta/PlatformFactory.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | final readonly class PlatformFactory 16 | { 17 | public static function create( 18 | string $baseUrl, 19 | #[\SensitiveParameter] 20 | string $apiKey, 21 | ?HttpClientInterface $httpClient = null, 22 | ?Contract $contract = null, 23 | ): Platform { 24 | $modelClient = new LlamaHandler($httpClient ?? HttpClient::create(), $baseUrl, $apiKey); 25 | 26 | return new Platform([$modelClient], [$modelClient], $contract); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Platform/Capability.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class Capability 11 | { 12 | // INPUT 13 | public const INPUT_AUDIO = 'input-audio'; 14 | public const INPUT_IMAGE = 'input-image'; 15 | public const INPUT_MESSAGES = 'input-messages'; 16 | public const INPUT_MULTIPLE = 'input-multiple'; 17 | public const INPUT_PDF = 'input-pdf'; 18 | public const INPUT_TEXT = 'input-text'; 19 | 20 | // OUTPUT 21 | public const OUTPUT_AUDIO = 'output-audio'; 22 | public const OUTPUT_IMAGE = 'output-image'; 23 | public const OUTPUT_STREAMING = 'output-streaming'; 24 | public const OUTPUT_STRUCTURED = 'output-structured'; 25 | public const OUTPUT_TEXT = 'output-text'; 26 | 27 | // FUNCTIONALITY 28 | public const TOOL_CALLING = 'tool-calling'; 29 | } 30 | -------------------------------------------------------------------------------- /src/Platform/Message/ToolCallMessage.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | final readonly class ToolCallMessage implements MessageInterface 16 | { 17 | public AbstractUid&TimeBasedUidInterface $id; 18 | 19 | public function __construct( 20 | public ToolCall $toolCall, 21 | public string $content, 22 | ) { 23 | $this->id = Uuid::v7(); 24 | } 25 | 26 | public function getRole(): Role 27 | { 28 | return Role::ToolCall; 29 | } 30 | 31 | public function getId(): AbstractUid&TimeBasedUidInterface 32 | { 33 | return $this->id; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Chain/Memory/StaticMemoryProvider.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | final readonly class StaticMemoryProvider implements MemoryProviderInterface 13 | { 14 | /** 15 | * @var array 16 | */ 17 | private array $memory; 18 | 19 | public function __construct(string ...$memory) 20 | { 21 | $this->memory = $memory; 22 | } 23 | 24 | public function loadMemory(Input $input): array 25 | { 26 | if (0 === \count($this->memory)) { 27 | return []; 28 | } 29 | 30 | $content = '## Static Memory'.\PHP_EOL; 31 | 32 | foreach ($this->memory as $memory) { 33 | $content .= \PHP_EOL.'- '.$memory; 34 | } 35 | 36 | return [new Memory($content)]; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Chain/Toolbox/Tool/Chain.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | final readonly class Chain 16 | { 17 | public function __construct( 18 | private ChainInterface $chain, 19 | ) { 20 | } 21 | 22 | /** 23 | * @param string $message the message to pass to the chain 24 | */ 25 | public function __invoke(string $message): string 26 | { 27 | $response = $this->chain->call(new MessageBag(Message::ofUser($message))); 28 | 29 | \assert($response instanceof TextResponse); 30 | 31 | return $response->getContent(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Chain/Input.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | final class Input 14 | { 15 | /** 16 | * @param array $options 17 | */ 18 | public function __construct( 19 | public Model $model, 20 | public MessageBagInterface $messages, 21 | private array $options, 22 | ) { 23 | } 24 | 25 | /** 26 | * @return array 27 | */ 28 | public function getOptions(): array 29 | { 30 | return $this->options; 31 | } 32 | 33 | /** 34 | * @param array $options 35 | */ 36 | public function setOptions(array $options): void 37 | { 38 | $this->options = $options; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Platform/Response/ResponseInterface.php: -------------------------------------------------------------------------------- 1 | 12 | * @author Denis Zunke 13 | */ 14 | interface ResponseInterface 15 | { 16 | /** 17 | * @return string|iterable|object|null 18 | */ 19 | public function getContent(): string|iterable|object|null; 20 | 21 | public function getMetadata(): Metadata; 22 | 23 | public function getRawResponse(): ?RawResponseInterface; 24 | 25 | /** 26 | * @throws RawResponseAlreadySetException if the response is tried to be set more than once 27 | */ 28 | public function setRawResponse(RawResponseInterface $rawResponse): void; 29 | } 30 | -------------------------------------------------------------------------------- /src/Platform/Bridge/HuggingFace/Output/FillMaskResult.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | final class FillMaskResult 11 | { 12 | /** 13 | * @param MaskFill[] $fills 14 | */ 15 | public function __construct( 16 | public array $fills, 17 | ) { 18 | } 19 | 20 | /** 21 | * @param array $data 22 | */ 23 | public static function fromArray(array $data): self 24 | { 25 | return new self(array_map( 26 | fn (array $item) => new MaskFill( 27 | $item['token'], 28 | $item['token_str'], 29 | $item['sequence'], 30 | $item['score'], 31 | ), 32 | $data, 33 | )); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Platform/Bridge/HuggingFace/Output/ZeroShotClassificationResult.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | final class ZeroShotClassificationResult 11 | { 12 | /** 13 | * @param array $labels 14 | * @param array $scores 15 | */ 16 | public function __construct( 17 | public array $labels, 18 | public array $scores, 19 | public ?string $sequence = null, 20 | ) { 21 | } 22 | 23 | /** 24 | * @param array{labels: array, scores: array, sequence?: string} $data 25 | */ 26 | public static function fromArray(array $data): self 27 | { 28 | return new self( 29 | $data['labels'], 30 | $data['scores'], 31 | $data['sequence'] ?? null, 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Platform/Bridge/Anthropic/Contract/AnthropicContract.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | final readonly class AnthropicContract extends Contract 14 | { 15 | public static function create(NormalizerInterface ...$normalizer): Contract 16 | { 17 | return parent::create( 18 | new AssistantMessageNormalizer(), 19 | new DocumentNormalizer(), 20 | new DocumentUrlNormalizer(), 21 | new ImageNormalizer(), 22 | new ImageUrlNormalizer(), 23 | new MessageBagNormalizer(), 24 | new ToolCallMessageNormalizer(), 25 | new ToolNormalizer(), 26 | ...$normalizer, 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Platform/Bridge/Ollama/PlatformFactory.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | final class PlatformFactory 16 | { 17 | public static function create( 18 | string $hostUrl = 'http://localhost:11434', 19 | ?HttpClientInterface $httpClient = null, 20 | ?Contract $contract = null, 21 | ): Platform { 22 | $httpClient = $httpClient instanceof EventSourceHttpClient ? $httpClient : new EventSourceHttpClient($httpClient); 23 | $handler = new LlamaModelHandler($httpClient, $hostUrl); 24 | 25 | return new Platform([$handler], [$handler], $contract); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Platform/Bridge/Voyage/PlatformFactory.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | final class PlatformFactory 16 | { 17 | public static function create( 18 | #[\SensitiveParameter] 19 | string $apiKey, 20 | ?HttpClientInterface $httpClient = null, 21 | ?Contract $contract = null, 22 | ): Platform { 23 | $httpClient = $httpClient instanceof EventSourceHttpClient ? $httpClient : new EventSourceHttpClient($httpClient); 24 | $handler = new ModelHandler($httpClient, $apiKey); 25 | 26 | return new Platform([$handler], [$handler], $contract); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Platform/Response/Choice.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | final readonly class Choice 11 | { 12 | /** 13 | * @param ToolCall[] $toolCalls 14 | */ 15 | public function __construct( 16 | private ?string $content = null, 17 | private array $toolCalls = [], 18 | ) { 19 | } 20 | 21 | public function getContent(): ?string 22 | { 23 | return $this->content; 24 | } 25 | 26 | public function hasContent(): bool 27 | { 28 | return null !== $this->content; 29 | } 30 | 31 | /** 32 | * @return ToolCall[] 33 | */ 34 | public function getToolCalls(): array 35 | { 36 | return $this->toolCalls; 37 | } 38 | 39 | public function hasToolCall(): bool 40 | { 41 | return 0 !== \count($this->toolCalls); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Platform/Response/BinaryResponse.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | final class BinaryResponse extends BaseResponse 13 | { 14 | public function __construct( 15 | public string $data, 16 | public ?string $mimeType = null, 17 | ) { 18 | } 19 | 20 | public function getContent(): string 21 | { 22 | return $this->data; 23 | } 24 | 25 | public function toBase64(): string 26 | { 27 | return base64_encode($this->data); 28 | } 29 | 30 | public function toDataUri(): string 31 | { 32 | if (null === $this->mimeType) { 33 | throw new RuntimeException('Mime type is not set.'); 34 | } 35 | 36 | return 'data:'.$this->mimeType.';base64,'.$this->toBase64(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Chain/StructuredOutput/ResponseFormatFactory.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | final readonly class ResponseFormatFactory implements ResponseFormatFactoryInterface 15 | { 16 | public function __construct( 17 | private Factory $schemaFactory = new Factory(), 18 | ) { 19 | } 20 | 21 | public function create(string $responseClass): array 22 | { 23 | return [ 24 | 'type' => 'json_schema', 25 | 'json_schema' => [ 26 | 'name' => u($responseClass)->afterLast('\\')->toString(), 27 | 'schema' => $this->schemaFactory->buildProperties($responseClass), 28 | 'strict' => true, 29 | ], 30 | ]; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Store/Document/Transformer/ChainTransformer.php: -------------------------------------------------------------------------------- 1 | $transformers 18 | */ 19 | public function __construct(iterable $transformers) 20 | { 21 | $this->transformers = $transformers instanceof \Traversable ? iterator_to_array($transformers) : $transformers; 22 | } 23 | 24 | public function __invoke(iterable $documents, array $options = []): iterable 25 | { 26 | foreach ($this->transformers as $transformer) { 27 | $documents = $transformer($documents, $options); 28 | } 29 | 30 | return $documents; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Chain/InputProcessor/ModelOverrideInputProcessor.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | final class ModelOverrideInputProcessor implements InputProcessorInterface 16 | { 17 | public function processInput(Input $input): void 18 | { 19 | $options = $input->getOptions(); 20 | 21 | if (!\array_key_exists('model', $options)) { 22 | return; 23 | } 24 | 25 | if (!$options['model'] instanceof Model) { 26 | throw new InvalidArgumentException(\sprintf('Option "model" must be an instance of %s.', Model::class)); 27 | } 28 | 29 | $input->model = $options['model']; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Chain/Toolbox/Exception/ToolNotFoundException.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | final class ToolNotFoundException extends \RuntimeException implements ExceptionInterface 14 | { 15 | public ?ToolCall $toolCall = null; 16 | 17 | public static function notFoundForToolCall(ToolCall $toolCall): self 18 | { 19 | $exception = new self(\sprintf('Tool not found for call: %s.', $toolCall->name)); 20 | $exception->toolCall = $toolCall; 21 | 22 | return $exception; 23 | } 24 | 25 | public static function notFoundForReference(ExecutionReference $reference): self 26 | { 27 | return new self(\sprintf('Tool not found for reference: %s::%s.', $reference->class, $reference->method)); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Platform/Bridge/HuggingFace/Output/TableQuestionAnsweringResult.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | final readonly class TableQuestionAnsweringResult 11 | { 12 | /** 13 | * @param array $cells 14 | * @param array $aggregator 15 | */ 16 | public function __construct( 17 | public string $answer, 18 | public array $cells = [], 19 | public array $aggregator = [], 20 | ) { 21 | } 22 | 23 | /** 24 | * @param array{answer: string, cells?: array, aggregator?: array} $data 25 | */ 26 | public static function fromArray(array $data): self 27 | { 28 | return new self( 29 | $data['answer'], 30 | $data['cells'] ?? [], 31 | $data['aggregator'] ?? [], 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Platform/Bridge/HuggingFace/Output/TokenClassificationResult.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | final class TokenClassificationResult 11 | { 12 | /** 13 | * @param Token[] $tokens 14 | */ 15 | public function __construct( 16 | public array $tokens, 17 | ) { 18 | } 19 | 20 | /** 21 | * @param array $data 22 | */ 23 | public static function fromArray(array $data): self 24 | { 25 | return new self(array_map( 26 | fn (array $item) => new Token( 27 | $item['entity_group'], 28 | $item['score'], 29 | $item['word'], 30 | $item['start'], 31 | $item['end'], 32 | ), 33 | $data, 34 | )); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Platform/Bridge/Google/Embeddings.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class Embeddings extends Model 15 | { 16 | /** Supported dimensions: 3072, 1536, or 768 */ 17 | public const GEMINI_EMBEDDING_EXP_03_07 = 'gemini-embedding-exp-03-07'; 18 | /** Fixed 768 dimensions */ 19 | public const TEXT_EMBEDDING_004 = 'text-embedding-004'; 20 | /** Fixed 768 dimensions */ 21 | public const EMBEDDING_001 = 'embedding-001'; 22 | 23 | /** 24 | * @param array{dimensions?: int, task_type?: TaskType|string} $options 25 | */ 26 | public function __construct(string $name = self::GEMINI_EMBEDDING_EXP_03_07, array $options = []) 27 | { 28 | parent::__construct($name, [Capability::INPUT_MULTIPLE], $options); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Platform/Bridge/OpenAI/Whisper/ResponseConverter.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | final class ResponseConverter implements BaseResponseConverter 18 | { 19 | public function supports(Model $model): bool 20 | { 21 | return $model instanceof Whisper; 22 | } 23 | 24 | public function convert(HttpResponse $response, array $options = []): LlmResponse 25 | { 26 | $data = $response->toArray(); 27 | 28 | return new TextResponse($data['text']); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Chain/Exception/MissingModelSupportException.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | final class MissingModelSupportException extends RuntimeException 11 | { 12 | private function __construct(string $model, string $support) 13 | { 14 | parent::__construct(\sprintf('Model "%s" does not support "%s".', $model, $support)); 15 | } 16 | 17 | public static function forToolCalling(string $model): self 18 | { 19 | return new self($model, 'tool calling'); 20 | } 21 | 22 | public static function forAudioInput(string $model): self 23 | { 24 | return new self($model, 'audio input'); 25 | } 26 | 27 | public static function forImageInput(string $model): self 28 | { 29 | return new self($model, 'image input'); 30 | } 31 | 32 | public static function forStructuredOutput(string $model): self 33 | { 34 | return new self($model, 'structured output'); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Platform/Bridge/Replicate/LlamaModelClient.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | final readonly class LlamaModelClient implements ModelClientInterface 17 | { 18 | public function __construct( 19 | private Client $client, 20 | ) { 21 | } 22 | 23 | public function supports(Model $model): bool 24 | { 25 | return $model instanceof Llama; 26 | } 27 | 28 | public function request(Model $model, array|string $payload, array $options = []): ResponseInterface 29 | { 30 | Assert::isInstanceOf($model, Llama::class); 31 | 32 | return $this->client->request(\sprintf('meta/meta-%s', $model->getName()), 'predictions', $payload); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Chain/Toolbox/StreamResponse.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | final class StreamResponse extends BaseResponse 15 | { 16 | public function __construct( 17 | private readonly \Generator $generator, 18 | private readonly \Closure $handleToolCallsCallback, 19 | ) { 20 | } 21 | 22 | public function getContent(): \Generator 23 | { 24 | $streamedResponse = ''; 25 | foreach ($this->generator as $value) { 26 | if ($value instanceof ToolCallResponse) { 27 | yield from ($this->handleToolCallsCallback)($value, Message::ofAssistant($streamedResponse))->getContent(); 28 | 29 | break; 30 | } 31 | 32 | $streamedResponse .= $value; 33 | yield $value; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Platform/Bridge/HuggingFace/Output/ObjectDetectionResult.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | final class ObjectDetectionResult 11 | { 12 | /** 13 | * @param DetectedObject[] $objects 14 | */ 15 | public function __construct( 16 | public array $objects, 17 | ) { 18 | } 19 | 20 | /** 21 | * @param array $data 22 | */ 23 | public static function fromArray(array $data): self 24 | { 25 | return new self(array_map( 26 | fn (array $item) => new DetectedObject( 27 | $item['label'], 28 | $item['score'], 29 | $item['box']['xmin'], 30 | $item['box']['ymin'], 31 | $item['box']['xmax'], 32 | $item['box']['ymax'], 33 | ), 34 | $data, 35 | )); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Platform/Bridge/Replicate/PlatformFactory.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | final class PlatformFactory 18 | { 19 | public static function create( 20 | #[\SensitiveParameter] 21 | string $apiKey, 22 | ?HttpClientInterface $httpClient = null, 23 | ?Contract $contract = null, 24 | ): Platform { 25 | return new Platform( 26 | [new LlamaModelClient(new Client($httpClient ?? HttpClient::create(), new Clock(), $apiKey))], 27 | [new LlamaResponseConverter()], 28 | $contract ?? Contract::create(new LlamaMessageBagNormalizer()), 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Platform/Bridge/TransformersPHP/PipelineExecution.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | final class PipelineExecution 13 | { 14 | /** 15 | * @var array|null 16 | */ 17 | private ?array $result = null; 18 | 19 | /** 20 | * @param array|string|object $input 21 | */ 22 | public function __construct( 23 | private readonly Pipeline $pipeline, 24 | private readonly object|array|string $input, 25 | ) { 26 | } 27 | 28 | public function getPipeline(): Pipeline 29 | { 30 | return $this->pipeline; 31 | } 32 | 33 | /** 34 | * @return array 35 | */ 36 | public function getResult(): array 37 | { 38 | if (null === $this->result) { 39 | $this->result = ($this->pipeline)($this->input); 40 | } 41 | 42 | return $this->result; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Platform/Contract/Normalizer/Message/Content/TextNormalizer.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | final class TextNormalizer implements NormalizerInterface 14 | { 15 | public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool 16 | { 17 | return $data instanceof Text; 18 | } 19 | 20 | public function getSupportedTypes(?string $format): array 21 | { 22 | return [ 23 | Text::class => true, 24 | ]; 25 | } 26 | 27 | /** 28 | * @param Text $data 29 | * 30 | * @return array{type: 'text', text: string} 31 | */ 32 | public function normalize(mixed $data, ?string $format = null, array $context = []): array 33 | { 34 | return ['type' => 'text', 'text' => $data->text]; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Platform/Response/ToolCall.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | final readonly class ToolCall implements \JsonSerializable 11 | { 12 | /** 13 | * @param array $arguments 14 | */ 15 | public function __construct( 16 | public string $id, 17 | public string $name, 18 | public array $arguments = [], 19 | ) { 20 | } 21 | 22 | /** 23 | * @return array{ 24 | * id: string, 25 | * type: 'function', 26 | * function: array{ 27 | * name: string, 28 | * arguments: string 29 | * } 30 | * } 31 | */ 32 | public function jsonSerialize(): array 33 | { 34 | return [ 35 | 'id' => $this->id, 36 | 'type' => 'function', 37 | 'function' => [ 38 | 'name' => $this->name, 39 | 'arguments' => json_encode($this->arguments), 40 | ], 41 | ]; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2024 Christopher Hertel 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /src/Platform/Bridge/HuggingFace/ApiClient.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | final class ApiClient 15 | { 16 | public function __construct( 17 | private ?HttpClientInterface $httpClient = null, 18 | ) { 19 | $this->httpClient = $httpClient ?? HttpClient::create(); 20 | } 21 | 22 | /** 23 | * @return Model[] 24 | */ 25 | public function models(?string $provider, ?string $task): array 26 | { 27 | $response = $this->httpClient->request('GET', 'https://huggingface.co/api/models', [ 28 | 'query' => [ 29 | 'inference_provider' => $provider, 30 | 'pipeline_tag' => $task, 31 | ], 32 | ]); 33 | 34 | return array_map(fn (array $model) => new Model($model['id']), $response->toArray()); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Store/Document/Loader/TextFileLoader.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | final readonly class TextFileLoader implements LoaderInterface 17 | { 18 | public function __invoke(string $source, array $options = []): iterable 19 | { 20 | if (!is_file($source)) { 21 | throw new RuntimeException(\sprintf('File "%s" does not exist.', $source)); 22 | } 23 | 24 | $content = file_get_contents($source); 25 | 26 | if (false === $content) { 27 | throw new RuntimeException(\sprintf('Unable to read file "%s"', $source)); 28 | } 29 | 30 | yield new TextDocument(Uuid::v4(), trim($content), new Metadata([ 31 | 'source' => $source, 32 | ])); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Platform/Bridge/HuggingFace/Contract/FileNormalizer.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class FileNormalizer extends ModelContractNormalizer 13 | { 14 | protected function supportedDataClass(): string 15 | { 16 | return File::class; 17 | } 18 | 19 | protected function supportsModel(Model $model): bool 20 | { 21 | return true; 22 | } 23 | 24 | /** 25 | * @param File $data 26 | * 27 | * @return array{ 28 | * headers: array<'Content-Type', string>, 29 | * body: string 30 | * } 31 | */ 32 | public function normalize(mixed $data, ?string $format = null, array $context = []): array 33 | { 34 | return [ 35 | 'headers' => ['Content-Type' => $data->getFormat()], 36 | 'body' => $data->asBinary(), 37 | ]; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Platform/Model.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class Model 11 | { 12 | /** 13 | * @param string[] $capabilities 14 | * @param array $options 15 | */ 16 | public function __construct( 17 | private readonly string $name, 18 | private readonly array $capabilities = [], 19 | private readonly array $options = [], 20 | ) { 21 | } 22 | 23 | public function getName(): string 24 | { 25 | return $this->name; 26 | } 27 | 28 | /** 29 | * @return string[] 30 | */ 31 | public function getCapabilities(): array 32 | { 33 | return $this->capabilities; 34 | } 35 | 36 | public function supports(string $capability): bool 37 | { 38 | return \in_array($capability, $this->capabilities, true); 39 | } 40 | 41 | /** 42 | * @return array 43 | */ 44 | public function getOptions(): array 45 | { 46 | return $this->options; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Platform/Bridge/Bedrock/Nova/Nova.php: -------------------------------------------------------------------------------- 1 | $options The default options for the model usage 22 | */ 23 | public function __construct( 24 | string $name = self::PRO, 25 | array $options = ['temperature' => 1.0, 'max_tokens' => 1000], 26 | ) { 27 | $capabilities = [ 28 | Capability::INPUT_MESSAGES, 29 | Capability::OUTPUT_TEXT, 30 | Capability::TOOL_CALLING, 31 | ]; 32 | 33 | if (self::MICRO !== $name) { 34 | $capabilities[] = Capability::INPUT_IMAGE; 35 | } 36 | 37 | parent::__construct($name, $capabilities, $options); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Platform/Message/AssistantMessage.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | final readonly class AssistantMessage implements MessageInterface 16 | { 17 | public AbstractUid&TimeBasedUidInterface $id; 18 | 19 | /** 20 | * @param ?ToolCall[] $toolCalls 21 | */ 22 | public function __construct( 23 | public ?string $content = null, 24 | public ?array $toolCalls = null, 25 | ) { 26 | $this->id = Uuid::v7(); 27 | } 28 | 29 | public function getRole(): Role 30 | { 31 | return Role::Assistant; 32 | } 33 | 34 | public function getId(): AbstractUid&TimeBasedUidInterface 35 | { 36 | return $this->id; 37 | } 38 | 39 | public function hasToolCalls(): bool 40 | { 41 | return null !== $this->toolCalls && 0 !== \count($this->toolCalls); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Platform/Bridge/Anthropic/PlatformFactory.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | final readonly class PlatformFactory 17 | { 18 | public static function create( 19 | #[\SensitiveParameter] 20 | string $apiKey, 21 | string $version = '2023-06-01', 22 | ?HttpClientInterface $httpClient = null, 23 | ?Contract $contract = null, 24 | ): Platform { 25 | $httpClient = $httpClient instanceof EventSourceHttpClient ? $httpClient : new EventSourceHttpClient($httpClient); 26 | 27 | return new Platform( 28 | [new ModelClient($httpClient, $apiKey, $version)], 29 | [new ResponseConverter()], 30 | $contract ?? AnthropicContract::create(), 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Platform/Contract/Normalizer/Message/Content/ImageNormalizer.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | final class ImageNormalizer implements NormalizerInterface 14 | { 15 | public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool 16 | { 17 | return $data instanceof Image; 18 | } 19 | 20 | public function getSupportedTypes(?string $format): array 21 | { 22 | return [ 23 | Image::class => true, 24 | ]; 25 | } 26 | 27 | /** 28 | * @param Image $data 29 | * 30 | * @return array{type: 'image_url', image_url: array{url: string}} 31 | */ 32 | public function normalize(mixed $data, ?string $format = null, array $context = []): array 33 | { 34 | return [ 35 | 'type' => 'image_url', 36 | 'image_url' => ['url' => $data->asDataUrl()], 37 | ]; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Platform/Contract/Normalizer/Message/SystemMessageNormalizer.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | final class SystemMessageNormalizer implements NormalizerInterface 14 | { 15 | public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool 16 | { 17 | return $data instanceof SystemMessage; 18 | } 19 | 20 | public function getSupportedTypes(?string $format): array 21 | { 22 | return [ 23 | SystemMessage::class => true, 24 | ]; 25 | } 26 | 27 | /** 28 | * @param SystemMessage $data 29 | * 30 | * @return array{role: 'system', content: string} 31 | */ 32 | public function normalize(mixed $data, ?string $format = null, array $context = []): array 33 | { 34 | return [ 35 | 'role' => $data->getRole()->value, 36 | 'content' => $data->content, 37 | ]; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Platform/Contract/Normalizer/Message/Content/ImageUrlNormalizer.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | final class ImageUrlNormalizer implements NormalizerInterface 14 | { 15 | public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool 16 | { 17 | return $data instanceof ImageUrl; 18 | } 19 | 20 | public function getSupportedTypes(?string $format): array 21 | { 22 | return [ 23 | ImageUrl::class => true, 24 | ]; 25 | } 26 | 27 | /** 28 | * @param ImageUrl $data 29 | * 30 | * @return array{type: 'image_url', image_url: array{url: string}} 31 | */ 32 | public function normalize(mixed $data, ?string $format = null, array $context = []): array 33 | { 34 | return [ 35 | 'type' => 'image_url', 36 | 'image_url' => ['url' => $data->url], 37 | ]; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Platform/Bridge/Replicate/LlamaResponseConverter.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | final readonly class LlamaResponseConverter implements ResponseConverterInterface 19 | { 20 | public function supports(Model $model): bool 21 | { 22 | return $model instanceof Llama; 23 | } 24 | 25 | public function convert(HttpResponse $response, array $options = []): LlmResponse 26 | { 27 | $data = $response->toArray(); 28 | 29 | if (!isset($data['output'])) { 30 | throw new RuntimeException('Response does not contain output'); 31 | } 32 | 33 | return new TextResponse(implode('', $data['output'])); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Chain/Toolbox/ToolResultConverter.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | final readonly class ToolResultConverter 18 | { 19 | public function __construct( 20 | private SerializerInterface $serializer = new Serializer([new JsonSerializableNormalizer(), new DateTimeNormalizer(), new ObjectNormalizer()], [new JsonEncoder()]), 21 | ) { 22 | } 23 | 24 | public function convert(mixed $result): ?string 25 | { 26 | if (null === $result || \is_string($result)) { 27 | return $result; 28 | } 29 | 30 | if ($result instanceof \Stringable) { 31 | return (string) $result; 32 | } 33 | 34 | return $this->serializer->serialize($result, 'json'); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Chain/Toolbox/Tool/Crawler.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | #[AsTool('crawler', 'A tool that crawls one page of a website and returns the visible text of it.')] 16 | final readonly class Crawler 17 | { 18 | public function __construct( 19 | private HttpClientInterface $httpClient, 20 | ) { 21 | if (!class_exists(DomCrawler::class)) { 22 | throw new RuntimeException('For using the Crawler tool, the symfony/dom-crawler package is required. Try running "composer require symfony/dom-crawler".'); 23 | } 24 | } 25 | 26 | /** 27 | * @param string $url the URL of the page to crawl 28 | */ 29 | public function __invoke(string $url): string 30 | { 31 | $response = $this->httpClient->request('GET', $url); 32 | 33 | return (new DomCrawler($response->getContent()))->filter('body')->text(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Chain/Toolbox/ToolFactory/ReflectionToolFactory.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | final class ReflectionToolFactory extends AbstractToolFactory 16 | { 17 | /** 18 | * @param class-string $reference 19 | */ 20 | public function getTool(string $reference): iterable 21 | { 22 | if (!class_exists($reference)) { 23 | throw ToolException::invalidReference($reference); 24 | } 25 | 26 | $reflectionClass = new \ReflectionClass($reference); 27 | $attributes = $reflectionClass->getAttributes(AsTool::class); 28 | 29 | if (0 === \count($attributes)) { 30 | throw ToolException::missingAttribute($reference); 31 | } 32 | 33 | foreach ($attributes as $attribute) { 34 | yield $this->convertAttribute($reference, $attribute->newInstance()); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Platform/Bridge/Google/PlatformFactory.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | final class MemoryToolFactory extends AbstractToolFactory 14 | { 15 | /** 16 | * @var array 17 | */ 18 | private array $tools = []; 19 | 20 | public function addTool(string|object $class, string $name, string $description, string $method = '__invoke'): self 21 | { 22 | $className = \is_object($class) ? $class::class : $class; 23 | $this->tools[$className][] = new AsTool($name, $description, $method); 24 | 25 | return $this; 26 | } 27 | 28 | /** 29 | * @param class-string $reference 30 | */ 31 | public function getTool(string $reference): iterable 32 | { 33 | if (!isset($this->tools[$reference])) { 34 | throw ToolException::invalidReference($reference); 35 | } 36 | 37 | foreach ($this->tools[$reference] as $tool) { 38 | yield $this->convertAttribute($reference, $tool); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Platform/Bridge/Bedrock/PlatformFactory.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | final class DocumentUrlNormalizer extends ModelContractNormalizer 16 | { 17 | protected function supportedDataClass(): string 18 | { 19 | return DocumentUrl::class; 20 | } 21 | 22 | protected function supportsModel(Model $model): bool 23 | { 24 | return $model instanceof Claude; 25 | } 26 | 27 | /** 28 | * @param DocumentUrl $data 29 | * 30 | * @return array{type: 'document', source: array{type: 'url', url: string}} 31 | */ 32 | public function normalize(mixed $data, ?string $format = null, array $context = []): array 33 | { 34 | return [ 35 | 'type' => 'document', 36 | 'source' => [ 37 | 'type' => 'url', 38 | 'url' => $data->url, 39 | ], 40 | ]; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Platform/Contract/Normalizer/ModelContractNormalizer.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | abstract class ModelContractNormalizer implements NormalizerInterface 13 | { 14 | /** 15 | * @return class-string 16 | */ 17 | abstract protected function supportedDataClass(): string; 18 | 19 | abstract protected function supportsModel(Model $model): bool; 20 | 21 | public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool 22 | { 23 | if (!is_a($data, $this->supportedDataClass(), true)) { 24 | return false; 25 | } 26 | 27 | if (isset($context[Contract::CONTEXT_MODEL]) && $context[Contract::CONTEXT_MODEL] instanceof Model) { 28 | return $this->supportsModel($context[Contract::CONTEXT_MODEL]); 29 | } 30 | 31 | return false; 32 | } 33 | 34 | public function getSupportedTypes(?string $format): array 35 | { 36 | return [ 37 | $this->supportedDataClass() => true, 38 | ]; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Platform/Bridge/Google/Gemini.php: -------------------------------------------------------------------------------- 1 | $options The default options for the model usage 23 | */ 24 | public function __construct(string $name = self::GEMINI_2_PRO, array $options = ['temperature' => 1.0]) 25 | { 26 | $capabilities = [ 27 | Capability::INPUT_MESSAGES, 28 | Capability::INPUT_IMAGE, 29 | Capability::INPUT_AUDIO, 30 | Capability::INPUT_PDF, 31 | Capability::OUTPUT_STREAMING, 32 | Capability::OUTPUT_STRUCTURED, 33 | Capability::TOOL_CALLING, 34 | ]; 35 | 36 | parent::__construct($name, $capabilities, $options); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Platform/Bridge/Anthropic/Contract/DocumentNormalizer.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class DocumentNormalizer extends ModelContractNormalizer 14 | { 15 | protected function supportedDataClass(): string 16 | { 17 | return Document::class; 18 | } 19 | 20 | protected function supportsModel(Model $model): bool 21 | { 22 | return $model instanceof Claude; 23 | } 24 | 25 | /** 26 | * @param Document $data 27 | * 28 | * @return array{type: 'document', source: array{type: 'base64', media_type: string, data: string}} 29 | */ 30 | public function normalize(mixed $data, ?string $format = null, array $context = []): array 31 | { 32 | return [ 33 | 'type' => 'document', 34 | 'source' => [ 35 | 'type' => 'base64', 36 | 'media_type' => $data->getFormat(), 37 | 'data' => $data->asBase64(), 38 | ], 39 | ]; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Platform/Bridge/Anthropic/Contract/ImageUrlNormalizer.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | final class ImageUrlNormalizer extends ModelContractNormalizer 17 | { 18 | protected function supportedDataClass(): string 19 | { 20 | return ImageUrl::class; 21 | } 22 | 23 | protected function supportsModel(Model $model): bool 24 | { 25 | return $model instanceof Claude; 26 | } 27 | 28 | /** 29 | * @param ImageUrl $data 30 | * 31 | * @return array{type: 'image', source: array{type: 'url', url: string}} 32 | */ 33 | public function normalize(mixed $data, ?string $format = null, array $context = []): array 34 | { 35 | return [ 36 | 'type' => 'image', 37 | 'source' => [ 38 | 'type' => 'url', 39 | 'url' => $data->url, 40 | ], 41 | ]; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Platform/Bridge/OpenAI/Whisper/AudioNormalizer.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | final class AudioNormalizer implements NormalizerInterface 16 | { 17 | public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool 18 | { 19 | return $data instanceof Audio && $context[Contract::CONTEXT_MODEL] instanceof Whisper; 20 | } 21 | 22 | public function getSupportedTypes(?string $format): array 23 | { 24 | return [ 25 | Audio::class => true, 26 | ]; 27 | } 28 | 29 | /** 30 | * @param Audio $data 31 | * 32 | * @return array{model: string, file: resource} 33 | */ 34 | public function normalize(mixed $data, ?string $format = null, array $context = []): array 35 | { 36 | return [ 37 | 'model' => $context[Contract::CONTEXT_MODEL]->getName(), 38 | 'file' => $data->asResource(), 39 | ]; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Chain/Chat.php: -------------------------------------------------------------------------------- 1 | store->clear(); 25 | $this->store->save($messages); 26 | } 27 | 28 | public function submit(UserMessage $message): AssistantMessage 29 | { 30 | $messages = $this->store->load(); 31 | 32 | $messages->add($message); 33 | $response = $this->chain->call($messages); 34 | 35 | \assert($response instanceof TextResponse); 36 | 37 | $assistantMessage = Message::ofAssistant($response->getContent()); 38 | $messages->add($assistantMessage); 39 | 40 | $this->store->save($messages); 41 | 42 | return $assistantMessage; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Platform/Bridge/HuggingFace/PlatformFactory.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | final readonly class PlatformFactory 18 | { 19 | public static function create( 20 | #[\SensitiveParameter] 21 | string $apiKey, 22 | string $provider = Provider::HF_INFERENCE, 23 | ?HttpClientInterface $httpClient = null, 24 | ?Contract $contract = null, 25 | ): Platform { 26 | $httpClient = $httpClient instanceof EventSourceHttpClient ? $httpClient : new EventSourceHttpClient($httpClient); 27 | 28 | return new Platform( 29 | [new ModelClient($httpClient, $provider, $apiKey)], 30 | [new ResponseConverter()], 31 | $contract ?? Contract::create( 32 | new FileNormalizer(), 33 | new MessageBagNormalizer(), 34 | ), 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Chain/Toolbox/ToolFactory/AbstractToolFactory.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | abstract class AbstractToolFactory implements ToolFactoryInterface 18 | { 19 | public function __construct( 20 | private readonly Factory $factory = new Factory(), 21 | ) { 22 | } 23 | 24 | protected function convertAttribute(string $className, AsTool $attribute): Tool 25 | { 26 | try { 27 | return new Tool( 28 | new ExecutionReference($className, $attribute->method), 29 | $attribute->name, 30 | $attribute->description, 31 | $this->factory->buildParameters($className, $attribute->method) 32 | ); 33 | } catch (\ReflectionException $e) { 34 | throw ToolConfigurationException::invalidMethod($className, $attribute->method, $e); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Platform/Bridge/Google/Embeddings/TaskType.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | #[AsTool(name: 'serpapi', description: 'search for information on the internet')] 14 | final readonly class SerpApi 15 | { 16 | public function __construct( 17 | private HttpClientInterface $httpClient, 18 | private string $apiKey, 19 | ) { 20 | } 21 | 22 | /** 23 | * @param string $query The search query to use 24 | */ 25 | public function __invoke(string $query): string 26 | { 27 | $response = $this->httpClient->request('GET', 'https://serpapi.com/search', [ 28 | 'query' => [ 29 | 'q' => $query, 30 | 'api_key' => $this->apiKey, 31 | ], 32 | ]); 33 | 34 | return \sprintf('Results for "%s" are "%s".', $query, $this->extractBestResponse($response->toArray())); 35 | } 36 | 37 | /** 38 | * @param array $results 39 | */ 40 | private function extractBestResponse(array $results): string 41 | { 42 | return implode('. ', array_map(fn ($story) => $story['title'], $results['organic_results'])); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Platform/Bridge/Meta/Contract/MessageBagNormalizer.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | final class MessageBagNormalizer extends ModelContractNormalizer 17 | { 18 | public function __construct( 19 | private readonly LlamaPromptConverter $promptConverter = new LlamaPromptConverter(), 20 | ) { 21 | } 22 | 23 | protected function supportedDataClass(): string 24 | { 25 | return MessageBagInterface::class; 26 | } 27 | 28 | protected function supportsModel(Model $model): bool 29 | { 30 | return $model instanceof Llama; 31 | } 32 | 33 | /** 34 | * @param MessageBagInterface $data 35 | * 36 | * @return array{prompt: string} 37 | */ 38 | public function normalize(mixed $data, ?string $format = null, array $context = []): array 39 | { 40 | return [ 41 | 'prompt' => $this->promptConverter->convertToPrompt($data), 42 | ]; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Platform/Bridge/OpenAI/Embeddings/ResponseConverter.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | final class ResponseConverter implements PlatformResponseConverter 19 | { 20 | public function supports(Model $model): bool 21 | { 22 | return $model instanceof Embeddings; 23 | } 24 | 25 | public function convert(ResponseInterface $response, array $options = []): VectorResponse 26 | { 27 | $data = $response->toArray(); 28 | 29 | if (!isset($data['data'])) { 30 | throw new RuntimeException('Response does not contain data'); 31 | } 32 | 33 | return new VectorResponse( 34 | ...array_map( 35 | static fn (array $item): Vector => new Vector($item['embedding']), 36 | $data['data'] 37 | ), 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Platform/Bridge/Anthropic/Claude.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class Claude extends Model 14 | { 15 | public const HAIKU_3 = 'claude-3-haiku-20240307'; 16 | public const HAIKU_35 = 'claude-3-5-haiku-20241022'; 17 | public const SONNET_3 = 'claude-3-sonnet-20240229'; 18 | public const SONNET_35 = 'claude-3-5-sonnet-20240620'; 19 | public const SONNET_35_V2 = 'claude-3-5-sonnet-20241022'; 20 | public const SONNET_37 = 'claude-3-7-sonnet-20250219'; 21 | public const OPUS_3 = 'claude-3-opus-20240229'; 22 | 23 | /** 24 | * @param array $options The default options for the model usage 25 | */ 26 | public function __construct( 27 | string $name = self::SONNET_37, 28 | array $options = ['temperature' => 1.0, 'max_tokens' => 1000], 29 | ) { 30 | $capabilities = [ 31 | Capability::INPUT_MESSAGES, 32 | Capability::INPUT_IMAGE, 33 | Capability::OUTPUT_TEXT, 34 | Capability::OUTPUT_STREAMING, 35 | Capability::TOOL_CALLING, 36 | ]; 37 | 38 | parent::__construct($name, $capabilities, $options); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Platform/Bridge/OpenRouter/PlatformFactory.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | class ToolNormalizer extends ModelContractNormalizer 17 | { 18 | protected function supportedDataClass(): string 19 | { 20 | return Tool::class; 21 | } 22 | 23 | protected function supportsModel(Model $model): bool 24 | { 25 | return $model instanceof Claude; 26 | } 27 | 28 | /** 29 | * @param Tool $data 30 | * 31 | * @return array{ 32 | * name: string, 33 | * description: string, 34 | * input_schema: JsonSchema|array{type: 'object'} 35 | * } 36 | */ 37 | public function normalize(mixed $data, ?string $format = null, array $context = []): array 38 | { 39 | return [ 40 | 'name' => $data->name, 41 | 'description' => $data->description, 42 | 'input_schema' => $data->parameters ?? ['type' => 'object'], 43 | ]; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Platform/Bridge/HuggingFace/Task.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | interface Task 11 | { 12 | public const AUDIO_CLASSIFICATION = 'audio-classification'; 13 | public const AUTOMATIC_SPEECH_RECOGNITION = 'automatic-speech-recognition'; 14 | public const CHAT_COMPLETION = 'chat-completion'; 15 | public const FEATURE_EXTRACTION = 'feature-extraction'; 16 | public const FILL_MASK = 'fill-mask'; 17 | public const IMAGE_CLASSIFICATION = 'image-classification'; 18 | public const IMAGE_SEGMENTATION = 'image-segmentation'; 19 | public const IMAGE_TO_TEXT = 'image-to-text'; 20 | public const OBJECT_DETECTION = 'object-detection'; 21 | public const QUESTION_ANSWERING = 'question-answering'; 22 | public const SENTENCE_SIMILARITY = 'sentence-similarity'; 23 | public const SUMMARIZATION = 'summarization'; 24 | public const TABLE_QUESTION_ANSWERING = 'table-question-answering'; 25 | public const TEXT_CLASSIFICATION = 'text-classification'; 26 | public const TEXT_GENERATION = 'text-generation'; 27 | public const TEXT_TO_IMAGE = 'text-to-image'; 28 | public const TOKEN_CLASSIFICATION = 'token-classification'; 29 | public const TRANSLATION = 'translation'; 30 | public const ZERO_SHOT_CLASSIFICATION = 'zero-shot-classification'; 31 | } 32 | -------------------------------------------------------------------------------- /src/Platform/Bridge/Anthropic/Contract/ImageNormalizer.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | final class ImageNormalizer extends ModelContractNormalizer 18 | { 19 | protected function supportedDataClass(): string 20 | { 21 | return Image::class; 22 | } 23 | 24 | protected function supportsModel(Model $model): bool 25 | { 26 | return $model instanceof Claude; 27 | } 28 | 29 | /** 30 | * @param Image $data 31 | * 32 | * @return array{type: 'image', source: array{type: 'base64', media_type: string, data: string}} 33 | */ 34 | public function normalize(mixed $data, ?string $format = null, array $context = []): array 35 | { 36 | return [ 37 | 'type' => 'image', 38 | 'source' => [ 39 | 'type' => 'base64', 40 | 'media_type' => u($data->getFormat())->replace('jpg', 'jpeg')->toString(), 41 | 'data' => $data->asBase64(), 42 | ], 43 | ]; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Platform/Vector/Vector.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | final class Vector implements VectorInterface 13 | { 14 | /** 15 | * @param list $data 16 | */ 17 | public function __construct( 18 | private readonly array $data, 19 | private ?int $dimensions = null, 20 | ) { 21 | if (null !== $dimensions && $dimensions !== \count($data)) { 22 | throw new InvalidArgumentException('Vector must have '.$dimensions.' dimensions'); 23 | } 24 | 25 | if (0 === \count($data)) { 26 | throw new InvalidArgumentException('Vector must have at least one dimension'); 27 | } 28 | 29 | if (\is_int($dimensions) && \count($data) !== $dimensions) { 30 | throw new InvalidArgumentException('Vector must have '.$dimensions.' dimensions'); 31 | } 32 | 33 | if (null === $this->dimensions) { 34 | $this->dimensions = \count($data); 35 | } 36 | } 37 | 38 | /** 39 | * @return list 40 | */ 41 | public function getData(): array 42 | { 43 | return $this->data; 44 | } 45 | 46 | public function getDimensions(): int 47 | { 48 | return $this->dimensions; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Store/Document/Transformer/ChunkDelayTransformer.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | final readonly class ChunkDelayTransformer implements TransformerInterface 17 | { 18 | public const OPTION_CHUNK_SIZE = 'chunk_size'; 19 | public const OPTION_DELAY = 'delay'; 20 | 21 | public function __construct( 22 | private ClockInterface $clock, 23 | ) { 24 | } 25 | 26 | /** 27 | * @param array{chunk_size?: int, delay?: int} $options 28 | */ 29 | public function __invoke(iterable $documents, array $options = []): iterable 30 | { 31 | $chunkSize = $options[self::OPTION_CHUNK_SIZE] ?? 50; 32 | $delay = $options[self::OPTION_DELAY] ?? 10; 33 | 34 | $counter = 0; 35 | foreach ($documents as $document) { 36 | yield $document; 37 | ++$counter; 38 | 39 | if ($chunkSize === $counter && 0 !== $delay) { 40 | $this->clock->sleep($delay); 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Chain/Chat/MessageStore/SessionStore.php: -------------------------------------------------------------------------------- 1 | session = $requestStack->getSession(); 25 | } 26 | 27 | public function save(MessageBagInterface $messages): void 28 | { 29 | $this->session->set($this->sessionKey, $messages); 30 | } 31 | 32 | public function load(): MessageBagInterface 33 | { 34 | return $this->session->get($this->sessionKey, new MessageBag()); 35 | } 36 | 37 | public function clear(): void 38 | { 39 | $this->session->remove($this->sessionKey); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Chain/Toolbox/ToolFactory/ChainFactory.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | final readonly class ChainFactory implements ToolFactoryInterface 14 | { 15 | /** 16 | * @var list 17 | */ 18 | private array $factories; 19 | 20 | /** 21 | * @param iterable $factories 22 | */ 23 | public function __construct(iterable $factories) 24 | { 25 | $this->factories = $factories instanceof \Traversable ? iterator_to_array($factories) : $factories; 26 | } 27 | 28 | public function getTool(string $reference): iterable 29 | { 30 | $invalid = 0; 31 | foreach ($this->factories as $factory) { 32 | try { 33 | yield from $factory->getTool($reference); 34 | } catch (ToolException) { 35 | ++$invalid; 36 | continue; 37 | } 38 | 39 | // If the factory does not throw an exception, we don't need to check the others 40 | return; 41 | } 42 | 43 | if ($invalid === \count($this->factories)) { 44 | throw ToolException::invalidReference($reference); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Chain/Toolbox/FaultTolerantToolbox.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | final readonly class FaultTolerantToolbox implements ToolboxInterface 18 | { 19 | public function __construct( 20 | private ToolboxInterface $innerToolbox, 21 | ) { 22 | } 23 | 24 | public function getTools(): array 25 | { 26 | return $this->innerToolbox->getTools(); 27 | } 28 | 29 | public function execute(ToolCall $toolCall): mixed 30 | { 31 | try { 32 | return $this->innerToolbox->execute($toolCall); 33 | } catch (ToolExecutionException $e) { 34 | return \sprintf('An error occurred while executing tool "%s".', $e->toolCall->name); 35 | } catch (ToolNotFoundException) { 36 | $names = array_map(fn (Tool $metadata) => $metadata->name, $this->getTools()); 37 | 38 | return \sprintf('Tool "%s" was not found, please use one of these: %s', $toolCall->name, implode(', ', $names)); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Platform/Contract/Normalizer/Response/ToolCallNormalizer.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | final class ToolCallNormalizer implements NormalizerInterface 14 | { 15 | public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool 16 | { 17 | return $data instanceof ToolCall; 18 | } 19 | 20 | public function getSupportedTypes(?string $format): array 21 | { 22 | return [ 23 | ToolCall::class => true, 24 | ]; 25 | } 26 | 27 | /** 28 | * @param ToolCall $data 29 | * 30 | * @return array{ 31 | * id: string, 32 | * type: 'function', 33 | * function: array{ 34 | * name: string, 35 | * arguments: string 36 | * } 37 | * } 38 | */ 39 | public function normalize(mixed $data, ?string $format = null, array $context = []): array 40 | { 41 | return [ 42 | 'id' => $data->id, 43 | 'type' => 'function', 44 | 'function' => [ 45 | 'name' => $data->name, 46 | 'arguments' => json_encode($data->arguments), 47 | ], 48 | ]; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Chain/Chat/MessageStore/CacheStore.php: -------------------------------------------------------------------------------- 1 | cache->getItem($this->cacheKey); 27 | 28 | $item->set($messages); 29 | $item->expiresAfter($this->ttl); 30 | 31 | $this->cache->save($item); 32 | } 33 | 34 | public function load(): MessageBag 35 | { 36 | $item = $this->cache->getItem($this->cacheKey); 37 | 38 | return $item->isHit() ? $item->get() : new MessageBag(); 39 | } 40 | 41 | public function clear(): void 42 | { 43 | $this->cache->deleteItem($this->cacheKey); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Chain/Toolbox/ToolCallArgumentResolver.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | final readonly class ToolCallArgumentResolver 16 | { 17 | public function __construct( 18 | private DenormalizerInterface $denormalizer = new Serializer([new DateTimeNormalizer(), new ObjectNormalizer()]), 19 | ) { 20 | } 21 | 22 | /** 23 | * @return array 24 | */ 25 | public function resolveArguments(Tool $metadata, ToolCall $toolCall): array 26 | { 27 | $method = new \ReflectionMethod($metadata->reference->class, $metadata->reference->method); 28 | 29 | /** @var array $parameters */ 30 | $parameters = array_column($method->getParameters(), null, 'name'); 31 | $arguments = []; 32 | 33 | foreach ($toolCall->arguments as $name => $value) { 34 | $parameterType = (string) $parameters[$name]->getType(); 35 | $arguments[$name] = 'array' === $parameterType ? $value : $this->denormalizer->denormalize($value, $parameterType); 36 | } 37 | 38 | return $arguments; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Platform/Bridge/HuggingFace/Contract/MessageBagNormalizer.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class MessageBagNormalizer extends ModelContractNormalizer implements NormalizerAwareInterface 15 | { 16 | use NormalizerAwareTrait; 17 | 18 | protected function supportedDataClass(): string 19 | { 20 | return MessageBagInterface::class; 21 | } 22 | 23 | protected function supportsModel(Model $model): bool 24 | { 25 | return true; 26 | } 27 | 28 | /** 29 | * @param MessageBagInterface $data 30 | * 31 | * @return array{ 32 | * headers: array<'Content-Type', 'application/json'>, 33 | * json: array{messages: array} 34 | * } 35 | */ 36 | public function normalize(mixed $data, ?string $format = null, array $context = []): array 37 | { 38 | return [ 39 | 'headers' => ['Content-Type' => 'application/json'], 40 | 'json' => [ 41 | 'messages' => $this->normalizer->normalize($data->getMessages(), $format, $context), 42 | ], 43 | ]; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Platform/Contract/Normalizer/Message/Content/AudioNormalizer.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | final class AudioNormalizer implements NormalizerInterface 14 | { 15 | public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool 16 | { 17 | return $data instanceof Audio; 18 | } 19 | 20 | public function getSupportedTypes(?string $format): array 21 | { 22 | return [ 23 | Audio::class => true, 24 | ]; 25 | } 26 | 27 | /** 28 | * @param Audio $data 29 | * 30 | * @return array{type: 'input_audio', input_audio: array{ 31 | * data: string, 32 | * format: 'mp3'|'wav'|string, 33 | * }} 34 | */ 35 | public function normalize(mixed $data, ?string $format = null, array $context = []): array 36 | { 37 | return [ 38 | 'type' => 'input_audio', 39 | 'input_audio' => [ 40 | 'data' => $data->asBase64(), 41 | 'format' => match ($data->getFormat()) { 42 | 'audio/mpeg' => 'mp3', 43 | 'audio/wav' => 'wav', 44 | default => $data->getFormat(), 45 | }, 46 | ], 47 | ]; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Platform/Contract/Normalizer/Message/ToolCallMessageNormalizer.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | final class ToolCallMessageNormalizer implements NormalizerInterface, NormalizerAwareInterface 16 | { 17 | use NormalizerAwareTrait; 18 | 19 | public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool 20 | { 21 | return $data instanceof ToolCallMessage; 22 | } 23 | 24 | public function getSupportedTypes(?string $format): array 25 | { 26 | return [ 27 | ToolCallMessage::class => true, 28 | ]; 29 | } 30 | 31 | /** 32 | * @return array{ 33 | * role: 'tool', 34 | * content: string, 35 | * tool_call_id: string, 36 | * } 37 | */ 38 | public function normalize(mixed $data, ?string $format = null, array $context = []): array 39 | { 40 | return [ 41 | 'role' => $data->getRole()->value, 42 | 'content' => $this->normalizer->normalize($data->content, $format, $context), 43 | 'tool_call_id' => $data->toolCall->id, 44 | ]; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Platform/Bridge/Albert/EmbeddingsModelClient.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | final readonly class EmbeddingsModelClient implements ModelClientInterface 18 | { 19 | public function __construct( 20 | private HttpClientInterface $httpClient, 21 | #[\SensitiveParameter] private string $apiKey, 22 | private string $baseUrl, 23 | ) { 24 | '' !== $apiKey || throw new InvalidArgumentException('The API key must not be empty.'); 25 | '' !== $baseUrl || throw new InvalidArgumentException('The base URL must not be empty.'); 26 | } 27 | 28 | public function supports(Model $model): bool 29 | { 30 | return $model instanceof Embeddings; 31 | } 32 | 33 | public function request(Model $model, array|string $payload, array $options = []): ResponseInterface 34 | { 35 | return $this->httpClient->request('POST', \sprintf('%s/embeddings', $this->baseUrl), [ 36 | 'auth_bearer' => $this->apiKey, 37 | 'json' => \is_array($payload) ? array_merge($payload, $options) : $payload, 38 | ]); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Platform/Bridge/OpenAI/Embeddings/ModelClient.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | final readonly class ModelClient implements PlatformResponseFactory 18 | { 19 | public function __construct( 20 | private HttpClientInterface $httpClient, 21 | #[\SensitiveParameter] 22 | private string $apiKey, 23 | ) { 24 | Assert::stringNotEmpty($apiKey, 'The API key must not be empty.'); 25 | Assert::startsWith($apiKey, 'sk-', 'The API key must start with "sk-".'); 26 | } 27 | 28 | public function supports(Model $model): bool 29 | { 30 | return $model instanceof Embeddings; 31 | } 32 | 33 | public function request(Model $model, array|string $payload, array $options = []): ResponseInterface 34 | { 35 | return $this->httpClient->request('POST', 'https://api.openai.com/v1/embeddings', [ 36 | 'auth_bearer' => $this->apiKey, 37 | 'json' => array_merge($options, [ 38 | 'model' => $model->getName(), 39 | 'input' => $payload, 40 | ]), 41 | ]); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Platform/Bridge/Mistral/Embeddings/ResponseConverter.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | final readonly class ResponseConverter implements ResponseConverterInterface 19 | { 20 | public function supports(Model $model): bool 21 | { 22 | return $model instanceof Embeddings; 23 | } 24 | 25 | public function convert(ResponseInterface $response, array $options = []): VectorResponse 26 | { 27 | $data = $response->toArray(false); 28 | 29 | if (200 !== $response->getStatusCode()) { 30 | throw new RuntimeException(\sprintf('Unexpected response code %d: %s', $response->getStatusCode(), $response->getContent(false))); 31 | } 32 | 33 | if (!isset($data['data'])) { 34 | throw new RuntimeException('Response does not contain data'); 35 | } 36 | 37 | return new VectorResponse( 38 | ...array_map( 39 | static fn (array $item): Vector => new Vector($item['embedding']), 40 | $data['data'] 41 | ), 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Platform/Bridge/Mistral/PlatformFactory.php: -------------------------------------------------------------------------------- 1 | 19 | */ 20 | final class PlatformFactory 21 | { 22 | public static function create( 23 | #[\SensitiveParameter] 24 | string $apiKey, 25 | ?HttpClientInterface $httpClient = null, 26 | ?Contract $contract = null, 27 | ): Platform { 28 | $httpClient = $httpClient instanceof EventSourceHttpClient ? $httpClient : new EventSourceHttpClient($httpClient); 29 | 30 | return new Platform( 31 | [new EmbeddingsModelClient($httpClient, $apiKey), new MistralModelClient($httpClient, $apiKey)], 32 | [new EmbeddingsResponseConverter(), new MistralResponseConverter()], 33 | $contract ?? Contract::create(new ToolNormalizer()), 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Platform/Bridge/Meta/Llama.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class Llama extends Model 14 | { 15 | public const V3_3_70B_INSTRUCT = 'llama-3.3-70B-Instruct'; 16 | public const V3_2_90B_VISION_INSTRUCT = 'llama-3.2-90b-vision-instruct'; 17 | public const V3_2_11B_VISION_INSTRUCT = 'llama-3.2-11b-vision-instruct'; 18 | public const V3_2_3B = 'llama-3.2-3b'; 19 | public const V3_2_3B_INSTRUCT = 'llama-3.2-3b-instruct'; 20 | public const V3_2_1B = 'llama-3.2-1b'; 21 | public const V3_2_1B_INSTRUCT = 'llama-3.2-1b-instruct'; 22 | public const V3_1_405B_INSTRUCT = 'llama-3.1-405b-instruct'; 23 | public const V3_1_70B = 'llama-3.1-70b'; 24 | public const V3_1_70B_INSTRUCT = 'llama-3-70b-instruct'; 25 | public const V3_1_8B = 'llama-3.1-8b'; 26 | public const V3_1_8B_INSTRUCT = 'llama-3.1-8b-instruct'; 27 | public const V3_70B = 'llama-3-70b'; 28 | public const V3_8B_INSTRUCT = 'llama-3-8b-instruct'; 29 | public const V3_8B = 'llama-3-8b'; 30 | 31 | /** 32 | * @param array $options 33 | */ 34 | public function __construct(string $name = self::V3_1_405B_INSTRUCT, array $options = []) 35 | { 36 | $capabilities = [ 37 | Capability::INPUT_MESSAGES, 38 | Capability::OUTPUT_TEXT, 39 | ]; 40 | 41 | parent::__construct($name, $capabilities, $options); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Platform/Bridge/Replicate/Contract/LlamaMessageBagNormalizer.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | final class LlamaMessageBagNormalizer extends ModelContractNormalizer 18 | { 19 | public function __construct( 20 | private readonly LlamaPromptConverter $promptConverter = new LlamaPromptConverter(), 21 | ) { 22 | } 23 | 24 | protected function supportedDataClass(): string 25 | { 26 | return MessageBagInterface::class; 27 | } 28 | 29 | protected function supportsModel(Model $model): bool 30 | { 31 | return $model instanceof Llama; 32 | } 33 | 34 | /** 35 | * @param MessageBagInterface $data 36 | * 37 | * @return array{system: string, prompt: string} 38 | */ 39 | public function normalize(mixed $data, ?string $format = null, array $context = []): array 40 | { 41 | return [ 42 | 'system' => $this->promptConverter->convertMessage($data->getSystemMessage() ?? new SystemMessage('')), 43 | 'prompt' => $this->promptConverter->convertToPrompt($data->withoutSystemMessage()), 44 | ]; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Platform/Bridge/Mistral/Llm/ModelClient.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | final readonly class ModelClient implements ModelClientInterface 18 | { 19 | private EventSourceHttpClient $httpClient; 20 | 21 | public function __construct( 22 | HttpClientInterface $httpClient, 23 | #[\SensitiveParameter] 24 | private string $apiKey, 25 | ) { 26 | $this->httpClient = $httpClient instanceof EventSourceHttpClient ? $httpClient : new EventSourceHttpClient($httpClient); 27 | } 28 | 29 | public function supports(Model $model): bool 30 | { 31 | return $model instanceof Mistral; 32 | } 33 | 34 | public function request(Model $model, array|string $payload, array $options = []): ResponseInterface 35 | { 36 | return $this->httpClient->request('POST', 'https://api.mistral.ai/v1/chat/completions', [ 37 | 'auth_bearer' => $this->apiKey, 38 | 'headers' => [ 39 | 'Content-Type' => 'application/json', 40 | 'Accept' => 'application/json', 41 | ], 42 | 'json' => array_merge($options, $payload), 43 | ]); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Chain/Toolbox/Tool/YouTubeTranscriber.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | #[AsTool('youtube_transcript', 'Fetches the transcript of a YouTube video')] 17 | final readonly class YouTubeTranscriber 18 | { 19 | public function __construct( 20 | private HttpClientInterface $client, 21 | ) { 22 | if (!class_exists(TranscriptListFetcher::class)) { 23 | throw new LogicException('For using the YouTube transcription tool, the mrmysql/youtube-transcript package is required. Try running "composer require mrmysql/youtube-transcript".'); 24 | } 25 | } 26 | 27 | /** 28 | * @param string $videoId The ID of the YouTube video 29 | */ 30 | public function __invoke(string $videoId): string 31 | { 32 | $psr18Client = new Psr18Client($this->client); 33 | $fetcher = new TranscriptListFetcher($psr18Client, $psr18Client, $psr18Client); 34 | 35 | $list = $fetcher->fetch($videoId); 36 | $transcript = $list->findTranscript($list->getAvailableLanguageCodes()); 37 | 38 | return array_reduce($transcript->fetch(), function (string $carry, array $item): string { 39 | return $carry.\PHP_EOL.$item['text']; 40 | }, ''); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Platform/Bridge/OpenAI/TokenOutputProcessor.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | final class TokenOutputProcessor implements OutputProcessorInterface 16 | { 17 | public function processOutput(Output $output): void 18 | { 19 | if ($output->response instanceof StreamResponse) { 20 | // Streams have to be handled manually as the tokens are part of the streamed chunks 21 | return; 22 | } 23 | 24 | $rawResponse = $output->response->getRawResponse()?->getRawObject(); 25 | if (!$rawResponse instanceof ResponseInterface) { 26 | return; 27 | } 28 | 29 | $metadata = $output->response->getMetadata(); 30 | 31 | $metadata->add( 32 | 'remaining_tokens', 33 | (int) $rawResponse->getHeaders(false)['x-ratelimit-remaining-tokens'][0], 34 | ); 35 | 36 | $content = $rawResponse->toArray(false); 37 | 38 | if (!\array_key_exists('usage', $content)) { 39 | return; 40 | } 41 | 42 | $metadata->add('prompt_tokens', $content['usage']['prompt_tokens'] ?? null); 43 | $metadata->add('completion_tokens', $content['usage']['completion_tokens'] ?? null); 44 | $metadata->add('total_tokens', $content['usage']['total_tokens'] ?? null); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Platform/Contract/Normalizer/ToolNormalizer.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class ToolNormalizer implements NormalizerInterface 15 | { 16 | public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool 17 | { 18 | return $data instanceof Tool; 19 | } 20 | 21 | public function getSupportedTypes(?string $format): array 22 | { 23 | return [ 24 | Tool::class => true, 25 | ]; 26 | } 27 | 28 | /** 29 | * @param Tool $data 30 | * 31 | * @return array{ 32 | * type: 'function', 33 | * function: array{ 34 | * name: string, 35 | * description: string, 36 | * parameters?: JsonSchema 37 | * } 38 | * } 39 | */ 40 | public function normalize(mixed $data, ?string $format = null, array $context = []): array 41 | { 42 | $function = [ 43 | 'name' => $data->name, 44 | 'description' => $data->description, 45 | ]; 46 | 47 | if (isset($data->parameters)) { 48 | $function['parameters'] = $data->parameters; 49 | } 50 | 51 | return [ 52 | 'type' => 'function', 53 | 'function' => $function, 54 | ]; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Chain/Toolbox/Tool/SimilaritySearch.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | #[AsTool('similarity_search', description: 'Searches for documents similar to a query or sentence.')] 17 | final class SimilaritySearch 18 | { 19 | /** 20 | * @var VectorDocument[] 21 | */ 22 | public array $usedDocuments = []; 23 | 24 | public function __construct( 25 | private readonly PlatformInterface $platform, 26 | private readonly Model $model, 27 | private readonly VectorStoreInterface $vectorStore, 28 | ) { 29 | } 30 | 31 | /** 32 | * @param string $searchTerm string used for similarity search 33 | */ 34 | public function __invoke(string $searchTerm): string 35 | { 36 | $vectors = $this->platform->request($this->model, $searchTerm)->asVectors(); 37 | $this->usedDocuments = $this->vectorStore->query($vectors[0]); 38 | 39 | if (0 === \count($this->usedDocuments)) { 40 | return 'No results found'; 41 | } 42 | 43 | $result = 'Found documents with following information:'.\PHP_EOL; 44 | foreach ($this->usedDocuments as $document) { 45 | $result .= json_encode($document->metadata); 46 | } 47 | 48 | return $result; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Platform/Bridge/Bedrock/Nova/Contract/ToolNormalizer.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | class ToolNormalizer extends ModelContractNormalizer 17 | { 18 | protected function supportedDataClass(): string 19 | { 20 | return Tool::class; 21 | } 22 | 23 | protected function supportsModel(Model $model): bool 24 | { 25 | return $model instanceof Nova; 26 | } 27 | 28 | /** 29 | * @param Tool $data 30 | * 31 | * @return array{ 32 | * toolSpec: array{ 33 | * name: string, 34 | * description: string, 35 | * inputSchema: array{ 36 | * json: JsonSchema|array{type: 'object'} 37 | * } 38 | * } 39 | * } 40 | */ 41 | public function normalize(mixed $data, ?string $format = null, array $context = []): array 42 | { 43 | return [ 44 | 'toolSpec' => [ 45 | 'name' => $data->name, 46 | 'description' => $data->description, 47 | 'inputSchema' => [ 48 | 'json' => $data->parameters ?? new \stdClass(), 49 | ], 50 | ], 51 | ]; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Platform/Bridge/OpenAI/Whisper/ModelClient.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | final readonly class ModelClient implements BaseModelClient 18 | { 19 | public function __construct( 20 | private HttpClientInterface $httpClient, 21 | #[\SensitiveParameter] 22 | private string $apiKey, 23 | ) { 24 | Assert::stringNotEmpty($apiKey, 'The API key must not be empty.'); 25 | } 26 | 27 | public function supports(Model $model): bool 28 | { 29 | return $model instanceof Whisper; 30 | } 31 | 32 | public function request(Model $model, array|string $payload, array $options = []): ResponseInterface 33 | { 34 | $task = $options['task'] ?? Task::TRANSCRIPTION; 35 | $endpoint = Task::TRANSCRIPTION === $task ? 'transcriptions' : 'translations'; 36 | unset($options['task']); 37 | 38 | return $this->httpClient->request('POST', \sprintf('https://api.openai.com/v1/audio/%s', $endpoint), [ 39 | 'auth_bearer' => $this->apiKey, 40 | 'headers' => ['Content-Type' => 'multipart/form-data'], 41 | 'body' => array_merge($options, $payload, ['model' => $model->getName()]), 42 | ]); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Platform/Bridge/Anthropic/ModelClient.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | final readonly class ModelClient implements ModelClientInterface 17 | { 18 | private EventSourceHttpClient $httpClient; 19 | 20 | public function __construct( 21 | HttpClientInterface $httpClient, 22 | #[\SensitiveParameter] private string $apiKey, 23 | private string $version = '2023-06-01', 24 | ) { 25 | $this->httpClient = $httpClient instanceof EventSourceHttpClient ? $httpClient : new EventSourceHttpClient($httpClient); 26 | } 27 | 28 | public function supports(Model $model): bool 29 | { 30 | return $model instanceof Claude; 31 | } 32 | 33 | public function request(Model $model, array|string $payload, array $options = []): ResponseInterface 34 | { 35 | if (isset($options['tools'])) { 36 | $options['tool_choice'] = ['type' => 'auto']; 37 | } 38 | 39 | return $this->httpClient->request('POST', 'https://api.anthropic.com/v1/messages', [ 40 | 'headers' => [ 41 | 'x-api-key' => $this->apiKey, 42 | 'anthropic-version' => $this->version, 43 | ], 44 | 'json' => array_merge($options, $payload), 45 | ]); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Platform/Bridge/OpenAI/GPT/ModelClient.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | final readonly class ModelClient implements PlatformResponseFactory 19 | { 20 | private EventSourceHttpClient $httpClient; 21 | 22 | public function __construct( 23 | HttpClientInterface $httpClient, 24 | #[\SensitiveParameter] 25 | private string $apiKey, 26 | ) { 27 | $this->httpClient = $httpClient instanceof EventSourceHttpClient ? $httpClient : new EventSourceHttpClient($httpClient); 28 | Assert::stringNotEmpty($apiKey, 'The API key must not be empty.'); 29 | Assert::startsWith($apiKey, 'sk-', 'The API key must start with "sk-".'); 30 | } 31 | 32 | public function supports(Model $model): bool 33 | { 34 | return $model instanceof GPT; 35 | } 36 | 37 | public function request(Model $model, array|string $payload, array $options = []): ResponseInterface 38 | { 39 | return $this->httpClient->request('POST', 'https://api.openai.com/v1/chat/completions', [ 40 | 'auth_bearer' => $this->apiKey, 41 | 'json' => array_merge($options, $payload), 42 | ]); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Platform/Bridge/Google/Contract/UserMessageNormalizer.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | final class UserMessageNormalizer extends ModelContractNormalizer 18 | { 19 | protected function supportedDataClass(): string 20 | { 21 | return UserMessage::class; 22 | } 23 | 24 | protected function supportsModel(Model $model): bool 25 | { 26 | return $model instanceof Gemini; 27 | } 28 | 29 | /** 30 | * @param UserMessage $data 31 | * 32 | * @return list 33 | */ 34 | public function normalize(mixed $data, ?string $format = null, array $context = []): array 35 | { 36 | $parts = []; 37 | foreach ($data->content as $content) { 38 | if ($content instanceof Text) { 39 | $parts[] = ['text' => $content->text]; 40 | } 41 | if ($content instanceof File) { 42 | $parts[] = ['inline_data' => [ 43 | 'mime_type' => $content->getFormat(), 44 | 'data' => $content->asBase64(), 45 | ]]; 46 | } 47 | } 48 | 49 | return $parts; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Platform/Bridge/Mistral/Embeddings/ModelClient.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | final readonly class ModelClient implements ModelClientInterface 18 | { 19 | private EventSourceHttpClient $httpClient; 20 | 21 | public function __construct( 22 | HttpClientInterface $httpClient, 23 | #[\SensitiveParameter] 24 | private string $apiKey, 25 | ) { 26 | $this->httpClient = $httpClient instanceof EventSourceHttpClient ? $httpClient : new EventSourceHttpClient($httpClient); 27 | } 28 | 29 | public function supports(Model $model): bool 30 | { 31 | return $model instanceof Embeddings; 32 | } 33 | 34 | public function request(Model $model, array|string $payload, array $options = []): ResponseInterface 35 | { 36 | return $this->httpClient->request('POST', 'https://api.mistral.ai/v1/embeddings', [ 37 | 'auth_bearer' => $this->apiKey, 38 | 'headers' => [ 39 | 'Content-Type' => 'application/json', 40 | ], 41 | 'json' => array_merge($options, [ 42 | 'model' => $model->getName(), 43 | 'input' => $payload, 44 | ]), 45 | ]); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Platform/Message/Message.php: -------------------------------------------------------------------------------- 1 | 13 | * @author Denis Zunke 14 | */ 15 | final readonly class Message 16 | { 17 | // Disabled by default, just a bridge to the specific messages 18 | private function __construct() 19 | { 20 | } 21 | 22 | public static function forSystem(\Stringable|string $content): SystemMessage 23 | { 24 | return new SystemMessage($content instanceof \Stringable ? (string) $content : $content); 25 | } 26 | 27 | /** 28 | * @param ?ToolCall[] $toolCalls 29 | */ 30 | public static function ofAssistant(?string $content = null, ?array $toolCalls = null): AssistantMessage 31 | { 32 | return new AssistantMessage($content, $toolCalls); 33 | } 34 | 35 | public static function ofUser(\Stringable|string|ContentInterface ...$content): UserMessage 36 | { 37 | $content = array_map( 38 | static fn (\Stringable|string|ContentInterface $entry) => $entry instanceof ContentInterface ? $entry : (\is_string($entry) ? new Text($entry) : new Text((string) $entry)), 39 | $content, 40 | ); 41 | 42 | return new UserMessage(...$content); 43 | } 44 | 45 | public static function ofToolCall(ToolCall $toolCall, string $content): ToolCallMessage 46 | { 47 | return new ToolCallMessage($toolCall, $content); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Platform/Contract/JsonSchema/DescriptionParser.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | final readonly class DescriptionParser 11 | { 12 | public function getDescription(\ReflectionProperty|\ReflectionParameter $reflector): string 13 | { 14 | if ($reflector instanceof \ReflectionProperty) { 15 | return $this->fromProperty($reflector); 16 | } 17 | 18 | return $this->fromParameter($reflector); 19 | } 20 | 21 | private function fromProperty(\ReflectionProperty $property): string 22 | { 23 | $comment = $property->getDocComment(); 24 | 25 | if (\is_string($comment) && preg_match('/@var\s+[a-zA-Z\\\\]+\s+((.*)(?=\*)|.*)/', $comment, $matches)) { 26 | return trim($matches[1]); 27 | } 28 | 29 | $class = $property->getDeclaringClass(); 30 | if ($class->hasMethod('__construct')) { 31 | return $this->fromParameter( 32 | new \ReflectionParameter([$class->getName(), '__construct'], $property->getName()) 33 | ); 34 | } 35 | 36 | return ''; 37 | } 38 | 39 | private function fromParameter(\ReflectionParameter $parameter): string 40 | { 41 | $comment = $parameter->getDeclaringFunction()->getDocComment(); 42 | if (!$comment) { 43 | return ''; 44 | } 45 | 46 | if (preg_match('/@param\s+\S+\s+\$'.preg_quote($parameter->getName(), '/').'\s+((.*)(?=\*)|.*)/', $comment, $matches)) { 47 | return trim($matches[1]); 48 | } 49 | 50 | return ''; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Platform/Contract/Normalizer/Message/UserMessageNormalizer.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | final class UserMessageNormalizer implements NormalizerInterface, NormalizerAwareInterface 17 | { 18 | use NormalizerAwareTrait; 19 | 20 | public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool 21 | { 22 | return $data instanceof UserMessage; 23 | } 24 | 25 | public function getSupportedTypes(?string $format): array 26 | { 27 | return [ 28 | UserMessage::class => true, 29 | ]; 30 | } 31 | 32 | /** 33 | * @param UserMessage $data 34 | * 35 | * @return array{role: 'assistant', content: string} 36 | */ 37 | public function normalize(mixed $data, ?string $format = null, array $context = []): array 38 | { 39 | $array = ['role' => $data->getRole()->value]; 40 | 41 | if (1 === \count($data->content) && $data->content[0] instanceof Text) { 42 | $array['content'] = $data->content[0]->text; 43 | 44 | return $array; 45 | } 46 | 47 | $array['content'] = $this->normalizer->normalize($data->content, $format, $context); 48 | 49 | return $array; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Platform/Bridge/Bedrock/Nova/Contract/MessageBagNormalizer.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | final class MessageBagNormalizer extends ModelContractNormalizer implements NormalizerAwareInterface 18 | { 19 | use NormalizerAwareTrait; 20 | 21 | protected function supportedDataClass(): string 22 | { 23 | return MessageBagInterface::class; 24 | } 25 | 26 | protected function supportsModel(Model $model): bool 27 | { 28 | return $model instanceof Nova; 29 | } 30 | 31 | /** 32 | * @param MessageBagInterface $data 33 | * 34 | * @return array{ 35 | * messages: array>, 36 | * system?: array, 37 | * } 38 | */ 39 | public function normalize(mixed $data, ?string $format = null, array $context = []): array 40 | { 41 | $array = []; 42 | 43 | if ($data->getSystemMessage()) { 44 | $array['system'][]['text'] = $data->getSystemMessage()->content; 45 | } 46 | 47 | $array['messages'] = $this->normalizer->normalize($data->withoutSystemMessage()->getMessages(), $format, $context); 48 | 49 | return $array; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Platform/Contract/Normalizer/Message/AssistantMessageNormalizer.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | final class AssistantMessageNormalizer implements NormalizerInterface, NormalizerAwareInterface 16 | { 17 | use NormalizerAwareTrait; 18 | 19 | public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool 20 | { 21 | return $data instanceof AssistantMessage; 22 | } 23 | 24 | public function getSupportedTypes(?string $format): array 25 | { 26 | return [ 27 | AssistantMessage::class => true, 28 | ]; 29 | } 30 | 31 | /** 32 | * @param AssistantMessage $data 33 | * 34 | * @return array{role: 'assistant', content?: string, tool_calls?: array>} 35 | */ 36 | public function normalize(mixed $data, ?string $format = null, array $context = []): array 37 | { 38 | $array = [ 39 | 'role' => $data->getRole()->value, 40 | ]; 41 | 42 | if (null !== $data->content) { 43 | $array['content'] = $data->content; 44 | } 45 | 46 | if ($data->hasToolCalls()) { 47 | $array['tool_calls'] = $this->normalizer->normalize($data->toolCalls, $format, $context); 48 | } 49 | 50 | return $array; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Platform/Message/UserMessage.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | final readonly class UserMessage implements MessageInterface 19 | { 20 | /** 21 | * @var list 22 | */ 23 | public array $content; 24 | 25 | public AbstractUid&TimeBasedUidInterface $id; 26 | 27 | public function __construct( 28 | ContentInterface ...$content, 29 | ) { 30 | $this->content = $content; 31 | $this->id = Uuid::v7(); 32 | } 33 | 34 | public function getRole(): Role 35 | { 36 | return Role::User; 37 | } 38 | 39 | public function getId(): AbstractUid&TimeBasedUidInterface 40 | { 41 | return $this->id; 42 | } 43 | 44 | public function hasAudioContent(): bool 45 | { 46 | foreach ($this->content as $content) { 47 | if ($content instanceof Audio) { 48 | return true; 49 | } 50 | } 51 | 52 | return false; 53 | } 54 | 55 | public function hasImageContent(): bool 56 | { 57 | foreach ($this->content as $content) { 58 | if ($content instanceof Image || $content instanceof ImageUrl) { 59 | return true; 60 | } 61 | } 62 | 63 | return false; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Platform/Bridge/Albert/GPTModelClient.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | final readonly class GPTModelClient implements ModelClientInterface 19 | { 20 | private EventSourceHttpClient $httpClient; 21 | 22 | public function __construct( 23 | HttpClientInterface $httpClient, 24 | #[\SensitiveParameter] private string $apiKey, 25 | private string $baseUrl, 26 | ) { 27 | $this->httpClient = $httpClient instanceof EventSourceHttpClient ? $httpClient : new EventSourceHttpClient($httpClient); 28 | 29 | '' !== $apiKey || throw new InvalidArgumentException('The API key must not be empty.'); 30 | '' !== $baseUrl || throw new InvalidArgumentException('The base URL must not be empty.'); 31 | } 32 | 33 | public function supports(Model $model): bool 34 | { 35 | return $model instanceof GPT; 36 | } 37 | 38 | public function request(Model $model, array|string $payload, array $options = []): ResponseInterface 39 | { 40 | return $this->httpClient->request('POST', \sprintf('%s/chat/completions', $this->baseUrl), [ 41 | 'auth_bearer' => $this->apiKey, 42 | 'json' => \is_array($payload) ? array_merge($payload, $options) : $payload, 43 | ]); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Store/Indexer.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | final readonly class Indexer 18 | { 19 | public function __construct( 20 | private Vectorizer $vectorizer, 21 | private StoreInterface $store, 22 | private LoggerInterface $logger = new NullLogger(), 23 | ) { 24 | } 25 | 26 | /** 27 | * @param TextDocument|iterable $documents 28 | * @param int $chunkSize number of documents to vectorize and store in one batch 29 | */ 30 | public function index(TextDocument|iterable $documents, int $chunkSize = 50): void 31 | { 32 | if ($documents instanceof TextDocument) { 33 | $documents = [$documents]; 34 | } 35 | 36 | $counter = 0; 37 | $chunk = []; 38 | foreach ($documents as $document) { 39 | $chunk[] = $document; 40 | ++$counter; 41 | 42 | if ($chunkSize === \count($chunk)) { 43 | $this->store->add(...$this->vectorizer->vectorizeDocuments($chunk)); 44 | $chunk = []; 45 | } 46 | } 47 | 48 | if (\count($chunk) > 0) { 49 | $this->store->add(...$this->vectorizer->vectorizeDocuments($chunk)); 50 | } 51 | 52 | $this->logger->debug(0 === $counter ? 'No documents to index' : \sprintf('Indexed %d documents', $counter)); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Platform/Bridge/Anthropic/Contract/ToolCallMessageNormalizer.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | final class ToolCallMessageNormalizer extends ModelContractNormalizer implements NormalizerAwareInterface 18 | { 19 | use NormalizerAwareTrait; 20 | 21 | protected function supportedDataClass(): string 22 | { 23 | return ToolCallMessage::class; 24 | } 25 | 26 | protected function supportsModel(Model $model): bool 27 | { 28 | return $model instanceof Claude; 29 | } 30 | 31 | /** 32 | * @param ToolCallMessage $data 33 | * 34 | * @return array{ 35 | * role: 'user', 36 | * content: list 41 | * } 42 | */ 43 | public function normalize(mixed $data, ?string $format = null, array $context = []): array 44 | { 45 | return [ 46 | 'role' => 'user', 47 | 'content' => [ 48 | [ 49 | 'type' => 'tool_result', 50 | 'tool_use_id' => $data->toolCall->id, 51 | 'content' => $data->content, 52 | ], 53 | ], 54 | ]; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Platform/Contract/Normalizer/Message/MessageBagNormalizer.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | final class MessageBagNormalizer implements NormalizerInterface, NormalizerAwareInterface 18 | { 19 | use NormalizerAwareTrait; 20 | 21 | public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool 22 | { 23 | return $data instanceof MessageBagInterface; 24 | } 25 | 26 | public function getSupportedTypes(?string $format): array 27 | { 28 | return [ 29 | MessageBagInterface::class => true, 30 | ]; 31 | } 32 | 33 | /** 34 | * @param MessageBagInterface $data 35 | * 36 | * @return array{ 37 | * messages: array, 38 | * model?: string, 39 | * } 40 | */ 41 | public function normalize(mixed $data, ?string $format = null, array $context = []): array 42 | { 43 | $array = [ 44 | 'messages' => $this->normalizer->normalize($data->getMessages(), $format, $context), 45 | ]; 46 | 47 | if (isset($context[Contract::CONTEXT_MODEL]) && $context[Contract::CONTEXT_MODEL] instanceof Model) { 48 | $array['model'] = $context[Contract::CONTEXT_MODEL]->getName(); 49 | } 50 | 51 | return $array; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Platform/Bridge/Albert/PlatformFactory.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | final class PlatformFactory 19 | { 20 | public static function create( 21 | #[\SensitiveParameter] string $apiKey, 22 | string $baseUrl, 23 | ?HttpClientInterface $httpClient = null, 24 | ): Platform { 25 | str_starts_with($baseUrl, 'https://') || throw new InvalidArgumentException('The Albert URL must start with "https://".'); 26 | !str_ends_with($baseUrl, '/') || throw new InvalidArgumentException('The Albert URL must not end with a trailing slash.'); 27 | preg_match('/\/v\d+$/', $baseUrl) || throw new InvalidArgumentException('The Albert URL must include an API version (e.g., /v1, /v2).'); 28 | 29 | $httpClient = $httpClient instanceof EventSourceHttpClient ? $httpClient : new EventSourceHttpClient($httpClient); 30 | 31 | return new Platform( 32 | [ 33 | new GPTModelClient($httpClient, $apiKey, $baseUrl), 34 | new EmbeddingsModelClient($httpClient, $apiKey, $baseUrl), 35 | ], 36 | [new GPTResponseConverter(), new EmbeddingsResponseConverter()], 37 | Contract::create(), 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Platform/Bridge/Azure/OpenAI/PlatformFactory.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | final readonly class PlatformFactory 19 | { 20 | public static function create( 21 | string $baseUrl, 22 | string $deployment, 23 | string $apiVersion, 24 | #[\SensitiveParameter] 25 | string $apiKey, 26 | ?HttpClientInterface $httpClient = null, 27 | ?Contract $contract = null, 28 | ): Platform { 29 | $httpClient = $httpClient instanceof EventSourceHttpClient ? $httpClient : new EventSourceHttpClient($httpClient); 30 | $embeddingsResponseFactory = new EmbeddingsModelClient($httpClient, $baseUrl, $deployment, $apiVersion, $apiKey); 31 | $GPTResponseFactory = new GPTModelClient($httpClient, $baseUrl, $deployment, $apiVersion, $apiKey); 32 | $whisperResponseFactory = new WhisperModelClient($httpClient, $baseUrl, $deployment, $apiVersion, $apiKey); 33 | 34 | return new Platform( 35 | [$GPTResponseFactory, $embeddingsResponseFactory, $whisperResponseFactory], 36 | [new ResponseConverter(), new Embeddings\ResponseConverter(), new \PhpLlm\LlmChain\Platform\Bridge\OpenAI\Whisper\ResponseConverter()], 37 | $contract ?? Contract::create(new AudioNormalizer()), 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Store/Document/Vectorizer.php: -------------------------------------------------------------------------------- 1 | model->supports(Capability::INPUT_MULTIPLE)) { 31 | $response = $this->platform->request($this->model, array_map(fn (TextDocument $document) => $document->content, $documents)); 32 | 33 | $vectors = $response->asVectors(); 34 | } else { 35 | $responses = []; 36 | foreach ($documents as $document) { 37 | $responses[] = $this->platform->request($this->model, $document->content); 38 | } 39 | 40 | $vectors = []; 41 | foreach ($responses as $response) { 42 | $vectors = array_merge($vectors, $response->asVectors()); 43 | } 44 | } 45 | 46 | $vectorDocuments = []; 47 | foreach ($documents as $i => $document) { 48 | $vectorDocuments[] = new VectorDocument($document->id, $vectors[$i], $document->metadata); 49 | } 50 | 51 | return $vectorDocuments; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Chain/Toolbox/Tool/Tavily.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | #[AsTool('tavily_search', description: 'search for information on the internet', method: 'search')] 16 | #[AsTool('tavily_extract', description: 'fetch content from websites', method: 'extract')] 17 | final readonly class Tavily 18 | { 19 | /** 20 | * @param array $options 21 | */ 22 | public function __construct( 23 | private HttpClientInterface $httpClient, 24 | private string $apiKey, 25 | private array $options = ['include_images' => false], 26 | ) { 27 | } 28 | 29 | /** 30 | * @param string $query The search query to use 31 | */ 32 | public function search(string $query): string 33 | { 34 | $response = $this->httpClient->request('POST', 'https://api.tavily.com/search', [ 35 | 'json' => array_merge($this->options, [ 36 | 'query' => $query, 37 | 'api_key' => $this->apiKey, 38 | ]), 39 | ]); 40 | 41 | return $response->getContent(); 42 | } 43 | 44 | /** 45 | * @param string[] $urls URLs to fetch information from 46 | */ 47 | public function extract(array $urls): string 48 | { 49 | $response = $this->httpClient->request('POST', 'https://api.tavily.com/extract', [ 50 | 'json' => [ 51 | 'urls' => $urls, 52 | 'api_key' => $this->apiKey, 53 | ], 54 | ]); 55 | 56 | return $response->getContent(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Platform/Bridge/Google/Contract/AssistantMessageNormalizer.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | final class AssistantMessageNormalizer extends ModelContractNormalizer implements NormalizerAwareInterface 18 | { 19 | use NormalizerAwareTrait; 20 | 21 | protected function supportedDataClass(): string 22 | { 23 | return AssistantMessage::class; 24 | } 25 | 26 | protected function supportsModel(Model $model): bool 27 | { 28 | return $model instanceof Gemini; 29 | } 30 | 31 | /** 32 | * @param AssistantMessage $data 33 | * 34 | * @return array{array{text: string}} 35 | */ 36 | public function normalize(mixed $data, ?string $format = null, array $context = []): array 37 | { 38 | $normalized = []; 39 | 40 | if (isset($data->content)) { 41 | $normalized['text'] = $data->content; 42 | } 43 | 44 | if (isset($data->toolCalls[0])) { 45 | $normalized['functionCall'] = [ 46 | 'id' => $data->toolCalls[0]->id, 47 | 'name' => $data->toolCalls[0]->name, 48 | ]; 49 | 50 | if ($data->toolCalls[0]->arguments) { 51 | $normalized['functionCall']['args'] = $data->toolCalls[0]->arguments; 52 | } 53 | } 54 | 55 | return [$normalized]; 56 | } 57 | } 58 | --------------------------------------------------------------------------------