diff --git a/src/Builders/PromptBuilder.php b/src/Builders/PromptBuilder.php index a6fb8cad..70cd322d 100644 --- a/src/Builders/PromptBuilder.php +++ b/src/Builders/PromptBuilder.php @@ -203,6 +203,32 @@ public function usingModel(ModelInterface $model): self return $this; } + /** + * Sets the model configuration. + * + * Merges the provided configuration with the builder's configuration, + * with builder configuration taking precedence. + * + * @since n.e.x.t + * + * @param ModelConfig $config The model configuration to merge. + * @return self + */ + public function usingModelConfig(ModelConfig $config): self + { + // Convert both configs to arrays + $builderConfigArray = $this->modelConfig->toArray(); + $providedConfigArray = $config->toArray(); + + // Merge arrays with builder config taking precedence + $mergedArray = array_merge($providedConfigArray, $builderConfigArray); + + // Create new config from merged array + $this->modelConfig = ModelConfig::fromArray($mergedArray); + + return $this; + } + /** * Sets the provider to use for generation. * diff --git a/src/Providers/Models/DTO/ModelConfig.php b/src/Providers/Models/DTO/ModelConfig.php index 9555be05..86a8e63e 100644 --- a/src/Providers/Models/DTO/ModelConfig.php +++ b/src/Providers/Models/DTO/ModelConfig.php @@ -909,7 +909,9 @@ static function (FunctionDeclaration $function_declaration): array { $data[self::KEY_OUTPUT_MEDIA_ASPECT_RATIO] = $this->outputMediaAspectRatio; } - $data[self::KEY_CUSTOM_OPTIONS] = $this->customOptions; + if (!empty($this->customOptions)) { + $data[self::KEY_CUSTOM_OPTIONS] = $this->customOptions; + } return $data; } diff --git a/tests/unit/Builders/PromptBuilderTest.php b/tests/unit/Builders/PromptBuilderTest.php index c2324ef3..437674e3 100644 --- a/tests/unit/Builders/PromptBuilderTest.php +++ b/tests/unit/Builders/PromptBuilderTest.php @@ -642,6 +642,99 @@ public function testUsingModel(): void $this->assertSame($model, $actualModel); } + /** + * Tests usingModelConfig method. + * + * @return void + */ + public function testUsingModelConfig(): void + { + $builder = new PromptBuilder($this->registry); + + // Set some initial config values on the builder + $builder->usingSystemInstruction('Builder instruction') + ->usingMaxTokens(500) + ->usingTemperature(0.5); + + // Create a config to merge + $config = new ModelConfig(); + $config->setSystemInstruction('Config instruction'); + $config->setMaxTokens(1000); + $config->setTopP(0.9); + $config->setTopK(40); + + $result = $builder->usingModelConfig($config); + + // Assert fluent interface + $this->assertSame($builder, $result); + + // Get the merged config + $reflection = new \ReflectionClass($builder); + $configProperty = $reflection->getProperty('modelConfig'); + $configProperty->setAccessible(true); + + /** @var ModelConfig $mergedConfig */ + $mergedConfig = $configProperty->getValue($builder); + + // Assert builder values take precedence + $this->assertEquals('Builder instruction', $mergedConfig->getSystemInstruction()); + $this->assertEquals(500, $mergedConfig->getMaxTokens()); + $this->assertEquals(0.5, $mergedConfig->getTemperature()); + + // Assert config values are used when builder doesn't have them + $this->assertEquals(0.9, $mergedConfig->getTopP()); + $this->assertEquals(40, $mergedConfig->getTopK()); + } + + /** + * Tests usingModelConfig with custom options. + * + * @return void + */ + public function testUsingModelConfigWithCustomOptions(): void + { + $builder = new PromptBuilder($this->registry); + + // Create a config with custom options + $config = new ModelConfig(); + $config->setCustomOption('stopSequences', ['CONFIG_STOP']); + $config->setCustomOption('otherOption', 'value'); + + $builder->usingModelConfig($config); + + // Get the merged config + $reflection = new \ReflectionClass($builder); + $configProperty = $reflection->getProperty('modelConfig'); + $configProperty->setAccessible(true); + + /** @var ModelConfig $mergedConfig */ + $mergedConfig = $configProperty->getValue($builder); + $customOptions = $mergedConfig->getCustomOptions(); + + // Assert config custom options are preserved + $this->assertArrayHasKey('stopSequences', $customOptions); + $this->assertIsArray($customOptions['stopSequences']); + $this->assertEquals(['CONFIG_STOP'], $customOptions['stopSequences']); + $this->assertArrayHasKey('otherOption', $customOptions); + $this->assertEquals('value', $customOptions['otherOption']); + + // Now set a builder value that overrides one of the custom options + $builder->usingStopSequences('STOP'); + + // Get the config again + $mergedConfig = $configProperty->getValue($builder); + $customOptions = $mergedConfig->getCustomOptions(); + + // Assert builder's stop sequences override the config's + $this->assertArrayHasKey('stopSequences', $customOptions); + $this->assertIsArray($customOptions['stopSequences']); + $this->assertEquals(['STOP'], $customOptions['stopSequences']); + + // Assert other custom options are still preserved + $this->assertArrayHasKey('otherOption', $customOptions); + $this->assertEquals('value', $customOptions['otherOption']); + } + /** * Tests usingProvider method. * diff --git a/tests/unit/Providers/Models/DTO/ModelConfigTest.php b/tests/unit/Providers/Models/DTO/ModelConfigTest.php index 7f5b353b..742132a4 100644 --- a/tests/unit/Providers/Models/DTO/ModelConfigTest.php +++ b/tests/unit/Providers/Models/DTO/ModelConfigTest.php @@ -304,9 +304,8 @@ public function testToArrayNoProperties(): void $array = $config->toArray(); $this->assertIsArray($array); - $this->assertCount(1, $array); - $this->assertArrayHasKey(ModelConfig::KEY_CUSTOM_OPTIONS, $array); - $this->assertEquals([], $array[ModelConfig::KEY_CUSTOM_OPTIONS]); + $this->assertCount(0, $array); + $this->assertArrayNotHasKey(ModelConfig::KEY_CUSTOM_OPTIONS, $array); } /** @@ -323,14 +322,49 @@ public function testToArrayPartialProperties(): void $array = $config->toArray(); $this->assertIsArray($array); - $this->assertCount(3, $array); + $this->assertCount(2, $array); $this->assertEquals(0.5, $array[ModelConfig::KEY_TEMPERATURE]); $this->assertEquals(100, $array[ModelConfig::KEY_MAX_TOKENS]); - $this->assertEquals([], $array[ModelConfig::KEY_CUSTOM_OPTIONS]); + $this->assertArrayNotHasKey(ModelConfig::KEY_CUSTOM_OPTIONS, $array); $this->assertArrayNotHasKey(ModelConfig::KEY_SYSTEM_INSTRUCTION, $array); $this->assertArrayNotHasKey(ModelConfig::KEY_TOP_P, $array); } + /** + * Tests custom options are only included when not empty. + * + * @return void + */ + public function testToArrayCustomOptionsOnlyIncludedWhenNotEmpty(): void + { + // Test with empty custom options (default) + $config = new ModelConfig(); + $config->setTemperature(0.7); + + $array = $config->toArray(); + $this->assertArrayNotHasKey(ModelConfig::KEY_CUSTOM_OPTIONS, $array); + + // Test with non-empty custom options + $config->setCustomOption('key1', 'value1'); + $array = $config->toArray(); + $this->assertArrayHasKey(ModelConfig::KEY_CUSTOM_OPTIONS, $array); + $this->assertEquals(['key1' => 'value1'], $array[ModelConfig::KEY_CUSTOM_OPTIONS]); + + // Test with multiple custom options + $config->setCustomOption('key2', ['nested' => 'value']); + $array = $config->toArray(); + $this->assertArrayHasKey(ModelConfig::KEY_CUSTOM_OPTIONS, $array); + $this->assertEquals([ + 'key1' => 'value1', + 'key2' => ['nested' => 'value'] + ], $array[ModelConfig::KEY_CUSTOM_OPTIONS]); + + // Test resetting custom options to empty + $config->setCustomOptions([]); + $array = $config->toArray(); + $this->assertArrayNotHasKey(ModelConfig::KEY_CUSTOM_OPTIONS, $array); + } + /** * Tests creating from array with all properties. *