From 720de6f009d0c6b33efd3456326daddf2b0dc291 Mon Sep 17 00:00:00 2001 From: ddebowczyk Date: Sun, 20 Oct 2024 07:02:08 +0200 Subject: [PATCH] Evals --- .../examples/extras/image_car_damage.mdx | 5 +- .../examples/extras/image_to_data.mdx | 3 +- .../extras/image_to_data_anthropic.mdx | 4 +- .../examples/extras/image_to_data_gemini.mdx | 4 +- .../examples/extras/web_to_objects.mdx | 2 +- docs/internals/script.mdx | 7 +- evals/ComplexExtraction/run.php | 2 +- evals/LLMModes/CompanyEval.php | 10 +- examples/A05_Extras/ImageCarDamage/run.php | 18 ++-- examples/A05_Extras/WebToObjects/run.php | 2 +- src/Extras/Evals/Console/Display.php | 8 +- src/Extras/Evals/Data/Evaluation.php | 18 ++-- src/Extras/Evals/Data/Feedback.php | 15 +-- ...ParameterFeedback.php => FeedbackItem.php} | 13 ++- src/Extras/Evals/Enums/FeedbackCategory.php | 10 ++ .../Evals/Evaluators/ArrayMatchEval.php | 21 ++-- .../Data/BooleanCorrectnessAnalysis.php | 4 +- .../Data/GradedCorrectnessAnalysis.php | 4 +- .../Evaluators/LLMBooleanCorrectnessEval.php | 2 +- .../Evaluators/LLMGradedCorrectnessEval.php | 2 +- src/Extras/Evals/Execution.php | 30 +++--- src/Extras/Evals/Executors/RunInference.php | 6 +- src/Extras/Evals/Executors/RunInstructor.php | 6 +- src/Extras/Evals/Experiment.php | 12 +-- src/Extras/Image/Image.php | 26 +++++ .../Modules/Web/ConvertHtmlToMarkdown.php | 2 +- .../Module/Modules/Web/GetHtmlLinks.php | 4 +- .../Module/Modules/Web/GetUrlContent.php | 2 +- .../Module/Modules/Web/GetWebpageDetails.php | 2 +- .../Codebase/Actions/ExtractClasses.php | 4 +- .../Codebase/Actions/ExtractFiles.php | 6 +- .../Codebase/Actions/ExtractFunctions.php | 8 +- .../Codebase/Actions/ExtractNamespaces.php | 4 +- .../Codebase/Actions/MakeClass.php | 10 +- src/{Extras => Utils}/Codebase/Codebase.php | 8 +- .../Codebase/ComposerJson.php | 2 +- .../Codebase/Data/CodeClass.php | 2 +- .../Codebase/Data/CodeFile.php | 4 +- .../Codebase/Data/CodeFunction.php | 2 +- .../Codebase/Data/CodeNamespace.php | 2 +- .../Codebase/Data/CodeParameter.php | 2 +- .../Codebase/Data/CodeProperty.php | 2 +- .../Codebase/Enums/CodeFileType.php | 2 +- src/{Extras => Utils}/Codebase/NodeUtils.php | 2 +- src/Utils/DataMap.php | 50 ++-------- src/Utils/Git/Branches.php | 37 +++++++ src/Utils/Git/Commit.php | 35 +++++++ src/Utils/Git/Commits.php | 26 +++++ src/Utils/Git/Diff.php | 23 +++++ src/Utils/Git/File.php | 42 ++++++++ src/Utils/Git/Git.php | 99 +++++++++++++++++++ src/Utils/Git/GitService.php | 26 +++++ src/Utils/Git/Remote.php | 23 +++++ src/Utils/Git/Stash.php | 25 +++++ .../Web/Contracts/CanConvertToMarkdown.php | 2 +- .../Web/Contracts/CanFilterContent.php | 2 +- .../Web/Contracts/CanGetUrlContent.php | 2 +- .../Web/Contracts/CanProcessHtml.php | 2 +- .../Web/Filters/AnyKeywordFilter.php | 4 +- .../Filters/EmbeddingsSimilarityFilter.php | 4 +- .../Web/Filters/NoFilter.php | 4 +- .../Web/Html/HtmlProcessor.php | 6 +- src/{Extras => Utils}/Web/Link.php | 2 +- src/{Extras => Utils}/Web/Scraper.php | 16 +-- .../Web/Scrapers/BasicReader.php | 4 +- .../Web/Scrapers/BrowsershotDriver.php | 4 +- .../Web/Scrapers/JinaReaderDriver.php | 4 +- .../Web/Scrapers/ScrapFlyDriver.php | 4 +- .../Web/Scrapers/ScrapingBeeDriver.php | 4 +- .../Web/Traits/HandlesContent.php | 2 +- .../Web/Traits/HandlesCreation.php | 8 +- .../Web/Traits/HandlesExtraction.php | 4 +- .../Web/Traits/HandlesLinks.php | 4 +- src/{Extras => Utils}/Web/Webpage.php | 16 +-- src/{Extras => Utils}/Web/Website.php | 8 +- tests/Feature/Utils/DataMapTest.php | 6 ++ 76 files changed, 576 insertions(+), 226 deletions(-) rename src/Extras/Evals/Data/{ParameterFeedback.php => FeedbackItem.php} (53%) create mode 100644 src/Extras/Evals/Enums/FeedbackCategory.php rename src/{Extras => Utils}/Codebase/Actions/ExtractClasses.php (82%) rename src/{Extras => Utils}/Codebase/Actions/ExtractFiles.php (71%) rename src/{Extras => Utils}/Codebase/Actions/ExtractFunctions.php (80%) rename src/{Extras => Utils}/Codebase/Actions/ExtractNamespaces.php (92%) rename src/{Extras => Utils}/Codebase/Actions/MakeClass.php (89%) rename src/{Extras => Utils}/Codebase/Codebase.php (90%) rename src/{Extras => Utils}/Codebase/ComposerJson.php (95%) rename src/{Extras => Utils}/Codebase/Data/CodeClass.php (90%) rename src/{Extras => Utils}/Codebase/Data/CodeFile.php (77%) rename src/{Extras => Utils}/Codebase/Data/CodeFunction.php (84%) rename src/{Extras => Utils}/Codebase/Data/CodeNamespace.php (83%) rename src/{Extras => Utils}/Codebase/Data/CodeParameter.php (80%) rename src/{Extras => Utils}/Codebase/Data/CodeProperty.php (82%) rename src/{Extras => Utils}/Codebase/Enums/CodeFileType.php (75%) rename src/{Extras => Utils}/Codebase/NodeUtils.php (89%) create mode 100644 src/Utils/Git/Branches.php create mode 100644 src/Utils/Git/Commit.php create mode 100644 src/Utils/Git/Commits.php create mode 100644 src/Utils/Git/Diff.php create mode 100644 src/Utils/Git/File.php create mode 100644 src/Utils/Git/Git.php create mode 100644 src/Utils/Git/GitService.php create mode 100644 src/Utils/Git/Remote.php create mode 100644 src/Utils/Git/Stash.php rename src/{Extras => Utils}/Web/Contracts/CanConvertToMarkdown.php (62%) rename src/{Extras => Utils}/Web/Contracts/CanFilterContent.php (60%) rename src/{Extras => Utils}/Web/Contracts/CanGetUrlContent.php (65%) rename src/{Extras => Utils}/Web/Contracts/CanProcessHtml.php (78%) rename src/{Extras => Utils}/Web/Filters/AnyKeywordFilter.php (74%) rename src/{Extras => Utils}/Web/Filters/EmbeddingsSimilarityFilter.php (86%) rename src/{Extras => Utils}/Web/Filters/NoFilter.php (52%) rename src/{Extras => Utils}/Web/Html/HtmlProcessor.php (95%) rename src/{Extras => Utils}/Web/Link.php (93%) rename src/{Extras => Utils}/Web/Scraper.php (72%) rename src/{Extras => Utils}/Web/Scrapers/BasicReader.php (59%) rename src/{Extras => Utils}/Web/Scrapers/BrowsershotDriver.php (65%) rename src/{Extras => Utils}/Web/Scrapers/JinaReaderDriver.php (82%) rename src/{Extras => Utils}/Web/Scrapers/ScrapFlyDriver.php (89%) rename src/{Extras => Utils}/Web/Scrapers/ScrapingBeeDriver.php (89%) rename src/{Extras => Utils}/Web/Traits/HandlesContent.php (90%) rename src/{Extras => Utils}/Web/Traits/HandlesCreation.php (77%) rename src/{Extras => Utils}/Web/Traits/HandlesExtraction.php (91%) rename src/{Extras => Utils}/Web/Traits/HandlesLinks.php (93%) rename src/{Extras => Utils}/Web/Webpage.php (57%) rename src/{Extras => Utils}/Web/Website.php (88%) diff --git a/docs/cookbook/examples/extras/image_car_damage.mdx b/docs/cookbook/examples/extras/image_car_damage.mdx index 971c0015..1d79c833 100644 --- a/docs/cookbook/examples/extras/image_car_damage.mdx +++ b/docs/cookbook/examples/extras/image_car_damage.mdx @@ -27,10 +27,7 @@ Here's the image we're going to extract data from. $loader = require 'vendor/autoload.php'; $loader->add('Cognesy\\Instructor\\', __DIR__ . '../../src/'); -use Cognesy\Instructor\Extras\Image\Image; -use Cognesy\Instructor\Features\Schema\Attributes\Description; -use Cognesy\Instructor\Instructor; -use Cognesy\Instructor\Utils\Str; +use Cognesy\Instructor\Extras\Image\Image;use Cognesy\Instructor\Features\Schema\Attributes\Description;use Cognesy\Instructor\Instructor;use Cognesy\Instructor\Utils\Str; enum DamageSeverity : string { case Minor = 'minor'; diff --git a/docs/cookbook/examples/extras/image_to_data.mdx b/docs/cookbook/examples/extras/image_to_data.mdx index 8947abcf..2924dd35 100644 --- a/docs/cookbook/examples/extras/image_to_data.mdx +++ b/docs/cookbook/examples/extras/image_to_data.mdx @@ -27,8 +27,7 @@ Here's the image we're going to extract data from. $loader = require 'vendor/autoload.php'; $loader->add('Cognesy\\Instructor\\', __DIR__ . '../../src/'); -use Cognesy\Instructor\Extras\Image\Image; -use Cognesy\Instructor\Instructor; +use Cognesy\Instructor\Extras\Image\Image;use Cognesy\Instructor\Instructor; class Vendor { public ?string $name = ''; diff --git a/docs/cookbook/examples/extras/image_to_data_anthropic.mdx b/docs/cookbook/examples/extras/image_to_data_anthropic.mdx index c095401e..6cb7ea1f 100644 --- a/docs/cookbook/examples/extras/image_to_data_anthropic.mdx +++ b/docs/cookbook/examples/extras/image_to_data_anthropic.mdx @@ -27,9 +27,7 @@ Here's the image we're going to extract data from. $loader = require 'vendor/autoload.php'; $loader->add('Cognesy\\Instructor\\', __DIR__ . '../../src/'); -use Cognesy\Instructor\Enums\Mode; -use Cognesy\Instructor\Extras\Image\Image; -use Cognesy\Instructor\Instructor; +use Cognesy\Instructor\Enums\Mode;use Cognesy\Instructor\Extras\Image\Image;use Cognesy\Instructor\Instructor; class Vendor { public ?string $name = ''; diff --git a/docs/cookbook/examples/extras/image_to_data_gemini.mdx b/docs/cookbook/examples/extras/image_to_data_gemini.mdx index 6fdd5ff4..939162b7 100644 --- a/docs/cookbook/examples/extras/image_to_data_gemini.mdx +++ b/docs/cookbook/examples/extras/image_to_data_gemini.mdx @@ -27,9 +27,7 @@ Here's the image we're going to extract data from. $loader = require 'vendor/autoload.php'; $loader->add('Cognesy\\Instructor\\', __DIR__ . '../../src/'); -use Cognesy\Instructor\Enums\Mode; -use Cognesy\Instructor\Extras\Image\Image; -use Cognesy\Instructor\Instructor; +use Cognesy\Instructor\Enums\Mode;use Cognesy\Instructor\Extras\Image\Image;use Cognesy\Instructor\Instructor; class Vendor { public ?string $name = ''; diff --git a/docs/cookbook/examples/extras/web_to_objects.mdx b/docs/cookbook/examples/extras/web_to_objects.mdx index 044735b6..bdc6f354 100644 --- a/docs/cookbook/examples/extras/web_to_objects.mdx +++ b/docs/cookbook/examples/extras/web_to_objects.mdx @@ -32,9 +32,9 @@ $loader = require 'vendor/autoload.php'; $loader->add('Cognesy\\Instructor\\', __DIR__ . '../../src/'); use Cognesy\Instructor\Enums\Mode; -use Cognesy\Instructor\Extras\Web\Webpage; use Cognesy\Instructor\Features\Schema\Attributes\Instructions; use Cognesy\Instructor\Instructor; +use Cognesy\Instructor\Utils\Web\Webpage; class Company { public string $name = ''; diff --git a/docs/internals/script.mdx b/docs/internals/script.mdx index 81154e59..ff150732 100644 --- a/docs/internals/script.mdx +++ b/docs/internals/script.mdx @@ -28,7 +28,8 @@ The Script class serves as a flexible and dynamic container for managing, hydrat ```php expectations['events']; /** @var Sequence $events */ - $events = $execution->data()->get('response')?->value(); + $events = $execution->get('response')?->value(); $result = ($expectedEvents - count($events->list)) / $expectedEvents; return new Evaluation( metric: new PercentageCorrectness('found', $result), diff --git a/evals/LLMModes/CompanyEval.php b/evals/LLMModes/CompanyEval.php index bdbb73ab..1bd07379 100644 --- a/evals/LLMModes/CompanyEval.php +++ b/evals/LLMModes/CompanyEval.php @@ -20,7 +20,8 @@ public function __construct(array $expectations) { } public function evaluate(Execution $execution) : Evaluation { - $isCorrect = match ($execution->data()->get('mode')) { + $mode = $execution->get('case.mode'); + $isCorrect = match ($mode) { Mode::Text => $this->validateText($execution), Mode::Tools => $this->validateToolsData($execution), default => $this->validateDefault($execution), @@ -35,21 +36,22 @@ public function evaluate(Execution $execution) : Evaluation { // INTERNAL ///////////////////////////////////////////////// private function validateToolsData(Execution $execution) : bool { - $data = $execution->data()->get('response')->toolsData[0]; + $data = $execution->get('response')->toolsData[0]; return 'store_company' === ($data['name'] ?? '') && 'ACME' === ($data['arguments']['name'] ?? '') && 2020 === (int) ($data['arguments']['year'] ?? 0); } private function validateDefault(Execution $execution) : bool { - $decoded = $execution->data()->get('response')?->json()->toArray(); + $decoded = $execution->get('response')?->json()->toArray(); return $this->expectations['name'] === ($decoded['name'] ?? '') && $this->expectations['year'] === ($decoded['year'] ?? 0); } private function validateText(Execution $execution) : bool { + $content = $execution->get('response')?->content(); return Str::contains( - $execution->data()->get('response')?->content(), + $content, [ $this->expectations['name'], (string) $this->expectations['year'] diff --git a/examples/A05_Extras/ImageCarDamage/run.php b/examples/A05_Extras/ImageCarDamage/run.php index 971c0015..5f302506 100644 --- a/examples/A05_Extras/ImageCarDamage/run.php +++ b/examples/A05_Extras/ImageCarDamage/run.php @@ -17,7 +17,7 @@ Here's the image we're going to extract data from. -![Receipt](/images/car-damage.jpg) +![Car Photo](/images/car-damage.jpg) ## Example @@ -29,7 +29,6 @@ use Cognesy\Instructor\Extras\Image\Image; use Cognesy\Instructor\Features\Schema\Attributes\Description; -use Cognesy\Instructor\Instructor; use Cognesy\Instructor\Utils\Str; enum DamageSeverity : string { @@ -66,13 +65,14 @@ class DamageAssessment { public string $summary; } -$assessment = (new Instructor)->respond( - input: Image::fromFile(__DIR__ . '/car-damage.jpg'), - responseModel: DamageAssessment::class, - prompt: 'Identify and assess each car damage location and severity separately.', - model: 'gpt-4o', - options: ['max_tokens' => 4096] -); +$assessment = Image::fromFile(__DIR__ . '/car-damage.jpg') + ->toData( + responseModel: DamageAssessment::class, + prompt: 'Identify and assess each car damage location and severity separately.', + connection: 'openai', + model: 'gpt-4o', + options: ['max_tokens' => 4096] + ); dump($assessment); assert(Str::contains($assessment->make, 'Toyota', false)); diff --git a/examples/A05_Extras/WebToObjects/run.php b/examples/A05_Extras/WebToObjects/run.php index 044735b6..bdc6f354 100644 --- a/examples/A05_Extras/WebToObjects/run.php +++ b/examples/A05_Extras/WebToObjects/run.php @@ -32,9 +32,9 @@ $loader->add('Cognesy\\Instructor\\', __DIR__ . '../../src/'); use Cognesy\Instructor\Enums\Mode; -use Cognesy\Instructor\Extras\Web\Webpage; use Cognesy\Instructor\Features\Schema\Attributes\Instructions; use Cognesy\Instructor\Instructor; +use Cognesy\Instructor\Utils\Web\Webpage; class Company { public string $name = ''; diff --git a/src/Extras/Evals/Console/Display.php b/src/Extras/Evals/Console/Display.php index 4f64d6dc..c9b220d4 100644 --- a/src/Extras/Evals/Console/Display.php +++ b/src/Extras/Evals/Console/Display.php @@ -43,9 +43,9 @@ public function footer(Experiment $experiment) { } public function before(Execution $execution) : void { - $connection = $execution->data()->get('connection'); - $mode = $execution->data()->get('mode')->value; - $streamed = $execution->data()->get('isStreamed'); + $connection = $execution->get('case.connection'); + $mode = $execution->get('case.mode')->value; + $streamed = $execution->get('case.isStreamed'); Console::printColumns([ [10, $connection, STR_PAD_RIGHT, Color::WHITE], @@ -85,7 +85,7 @@ public function displayExceptions(array $exceptions) : void { // INTERNAL ///////////////////////////////////////////////// private function displayResult(Execution $execution) : void { - $answer = $execution->data()->get('notes'); + $answer = $execution->get('output.notes'); $answerLine = str_replace("\n", '\n', $answer); $timeElapsed = $execution->timeElapsed(); $tokensPerSec = $execution->outputTps(); diff --git a/src/Extras/Evals/Data/Evaluation.php b/src/Extras/Evals/Data/Evaluation.php index 68e72355..ae2c2043 100644 --- a/src/Extras/Evals/Data/Evaluation.php +++ b/src/Extras/Evals/Data/Evaluation.php @@ -4,6 +4,7 @@ use Cognesy\Instructor\Extras\Evals\Contracts\Metric; use Cognesy\Instructor\Features\LLM\Data\Usage; +use Cognesy\Instructor\Utils\DataMap; use Cognesy\Instructor\Utils\Uuid; use DateTime; @@ -13,7 +14,7 @@ class Evaluation private ?DateTime $startedAt; private float $timeElapsed = 0.0; private ?Usage $usage; - private array $metadata; + private DataMap $data; public ?Metric $metric = null; public ?Feedback $feedback = null; @@ -29,7 +30,7 @@ public function __construct( $this->metric = $metric; $this->feedback = $feedback; $this->usage = $usage; - $this->metadata = $metadata; + $this->data = new DataMap($metadata); } public function id() : string { @@ -53,21 +54,16 @@ public function usage() : Usage { return $this->usage; } - public function metric() : Metric { - return $this->metric; + public function data() : DataMap { + return $this->data; } public function feedback() : Feedback { return $this->feedback; } - public function metadata(string $key, mixed $default = null) : mixed { - return $this->metadata[$key] ?? $default; - } - - public function withMetadata(string $key, mixed $value) : self { - $this->metadata[$key] = $value; - return $this; + public function metric() : Metric { + return $this->metric; } public function hasMetric(string $metricName) : bool { diff --git a/src/Extras/Evals/Data/Feedback.php b/src/Extras/Evals/Data/Feedback.php index 8db5d655..26f3a7c3 100644 --- a/src/Extras/Evals/Data/Feedback.php +++ b/src/Extras/Evals/Data/Feedback.php @@ -4,9 +4,12 @@ class Feedback { - /** @var ParameterFeedback[] $items */ + /** @var FeedbackItem[] $items */ private array $items; + /** + * @param string|FeedbackItem[] $items + */ public function __construct( string|array $items = [] ) { @@ -17,12 +20,12 @@ public static function none() : static { return new static(); } - /** @return ParameterFeedback[] */ + /** @return FeedbackItem[] */ public function items() : array { return $this->items; } - public function add(?ParameterFeedback $item) : static { + public function add(?FeedbackItem $item) : static { if (is_null($item)) { return $this; } @@ -50,7 +53,7 @@ public function __toString() : string { return implode( separator: "\n", array: array_map( - callback: fn(ParameterFeedback $item) => $item->parameterName . ': ' . $item->feedback, + callback: fn(FeedbackItem $item) => $item->context . ': ' . $item->feedback, array: $this->items )); } @@ -59,7 +62,7 @@ public function __toString() : string { /** * @param array|string $items - * @return ParameterFeedback[] + * @return FeedbackItem[] */ private function toFeedbackItems(array|string $items) : array { $feedbackItems = []; @@ -70,7 +73,7 @@ private function toFeedbackItems(array|string $items) : array { } $param = $item['parameterName'] ?? ''; $feedback = $item['feedback'] ?? ''; - $feedbackItems[] = new ParameterFeedback($param, $feedback); + $feedbackItems[] = new FeedbackItem($param, $feedback); } return $feedbackItems; } diff --git a/src/Extras/Evals/Data/ParameterFeedback.php b/src/Extras/Evals/Data/FeedbackItem.php similarity index 53% rename from src/Extras/Evals/Data/ParameterFeedback.php rename to src/Extras/Evals/Data/FeedbackItem.php index f1018932..e2dfe3bb 100644 --- a/src/Extras/Evals/Data/ParameterFeedback.php +++ b/src/Extras/Evals/Data/FeedbackItem.php @@ -2,20 +2,25 @@ namespace Cognesy\Instructor\Extras\Evals\Data; +use Cognesy\Instructor\Extras\Evals\Enums\FeedbackCategory; use Cognesy\Instructor\Features\Schema\Attributes\Description; -class ParameterFeedback +class FeedbackItem { #[Description('The name of the parameter that the feedback is about.')] - public string $parameterName = ''; + public string $context = ''; #[Description('The feedback on the parameters correctness or the issues with its value.')] public string $feedback = ''; + #[Description('The category of the feedback.')] + public FeedbackCategory $category; public function __construct( - string $parameterName = '', + string $context = '', string $feedback = '', + FeedbackCategory $category = FeedbackCategory::Other, ) { - $this->parameterName = $parameterName; + $this->context = $context; $this->feedback = $feedback; + $this->category = $category; } } diff --git a/src/Extras/Evals/Enums/FeedbackCategory.php b/src/Extras/Evals/Enums/FeedbackCategory.php new file mode 100644 index 00000000..db4ad1c1 --- /dev/null +++ b/src/Extras/Evals/Enums/FeedbackCategory.php @@ -0,0 +1,10 @@ +data()->get('response')?->json()->toArray(); + $data = $execution->get('response')?->json()->toArray(); $differences = (new CompareNestedArrays)->compare($this->expected, $data); $total = count((new Dot($data))->flatten()); $matches = $total - count($differences); @@ -44,15 +45,17 @@ private function makeFeedback(array $differences) : Feedback { return $feedback; } - private function getFeedback(string $key, mixed $expectedVal, mixed $actualVal) : ?ParameterFeedback { + private function getFeedback(string $key, mixed $expectedVal, mixed $actualVal) : ?FeedbackItem { return match(true) { - ($expectedVal !== null) && ($actualVal === null) => new ParameterFeedback( - parameterName: $key, - feedback: "Expected `$key`, but param not found in result" + ($expectedVal !== null) && ($actualVal === null) => new FeedbackItem( + context: $key, + feedback: "Expected `$key`, but param not found in result", + category: FeedbackCategory::Error ), - ($actualVal !== $expectedVal) => new ParameterFeedback( - parameterName: $key, - feedback: "Expected `$key` value `$expectedVal`, but actual is `$actualVal`" + ($actualVal !== $expectedVal) => new FeedbackItem( + context: $key, + feedback: "Expected `$key` value `$expectedVal`, but actual is `$actualVal`", + category: FeedbackCategory::Error ), default => null, }; diff --git a/src/Extras/Evals/Evaluators/Data/BooleanCorrectnessAnalysis.php b/src/Extras/Evals/Evaluators/Data/BooleanCorrectnessAnalysis.php index c2f1e3f1..a91b8df8 100644 --- a/src/Extras/Evals/Evaluators/Data/BooleanCorrectnessAnalysis.php +++ b/src/Extras/Evals/Evaluators/Data/BooleanCorrectnessAnalysis.php @@ -2,7 +2,7 @@ namespace Cognesy\Instructor\Extras\Evals\Evaluators\Data; -use Cognesy\Instructor\Extras\Evals\Data\ParameterFeedback; +use Cognesy\Instructor\Extras\Evals\Data\FeedbackItem; use Cognesy\Instructor\Features\Schema\Attributes\Description; #[Description("The result of correctness evaluation.")] @@ -13,6 +13,6 @@ class BooleanCorrectnessAnalysis #[Description("Decision if the actual result is correct.")] public bool $isCorrect; #[Description("If the result is incorrect - list of individual issues found in the actual result considering the expected values. Otherwise empty.")] - /** @var ParameterFeedback[] */ + /** @var FeedbackItem[] */ public array $feedback; } diff --git a/src/Extras/Evals/Evaluators/Data/GradedCorrectnessAnalysis.php b/src/Extras/Evals/Evaluators/Data/GradedCorrectnessAnalysis.php index 208467a7..0a3b89f1 100644 --- a/src/Extras/Evals/Evaluators/Data/GradedCorrectnessAnalysis.php +++ b/src/Extras/Evals/Evaluators/Data/GradedCorrectnessAnalysis.php @@ -2,7 +2,7 @@ namespace Cognesy\Instructor\Extras\Evals\Evaluators\Data; -use Cognesy\Instructor\Extras\Evals\Data\ParameterFeedback; +use Cognesy\Instructor\Extras\Evals\Data\FeedbackItem; use Cognesy\Instructor\Extras\Evals\Enums\CorrectnessGrade; use Cognesy\Instructor\Features\Schema\Attributes\Description; @@ -14,6 +14,6 @@ class GradedCorrectnessAnalysis #[Description("Graded correctness of the result.")] public CorrectnessGrade $correctness; #[Description("If the result is incorrect - list of individual issues found in the actual result considering the expected values. Otherwise empty.")] - /** @var ParameterFeedback[] */ + /** @var FeedbackItem[] */ public array $feedback; } diff --git a/src/Extras/Evals/Evaluators/LLMBooleanCorrectnessEval.php b/src/Extras/Evals/Evaluators/LLMBooleanCorrectnessEval.php index 52ccde0a..16add299 100644 --- a/src/Extras/Evals/Evaluators/LLMBooleanCorrectnessEval.php +++ b/src/Extras/Evals/Evaluators/LLMBooleanCorrectnessEval.php @@ -23,7 +23,7 @@ public function __construct( } public function evaluate(Execution $execution) : Evaluation { - /** @var \Cognesy\Instructor\Extras\Evals\Evaluators\Data\BooleanCorrectnessAnalysis $result */ + /** @var BooleanCorrectnessAnalysis $result */ $request = $this->instructor->request( input: [ 'expected_result' => $this->expected, diff --git a/src/Extras/Evals/Evaluators/LLMGradedCorrectnessEval.php b/src/Extras/Evals/Evaluators/LLMGradedCorrectnessEval.php index 378014fe..2a441e95 100644 --- a/src/Extras/Evals/Evaluators/LLMGradedCorrectnessEval.php +++ b/src/Extras/Evals/Evaluators/LLMGradedCorrectnessEval.php @@ -41,7 +41,7 @@ public function evaluate(Execution $execution) : Evaluation { return new Evaluation( metric: new GradedCorrectness( name: $this->name, - grade: $result->correctness, + value: $result->correctness, ), feedback: new Feedback($result->feedback), usage: $request->response()->usage(), diff --git a/src/Extras/Evals/Execution.php b/src/Extras/Evals/Execution.php index 79233e1c..66ef937e 100644 --- a/src/Extras/Evals/Execution.php +++ b/src/Extras/Evals/Execution.php @@ -2,7 +2,6 @@ namespace Cognesy\Instructor\Extras\Evals; -use Cognesy\Instructor\Enums\Mode; use Cognesy\Instructor\Extras\Evals\Contracts\CanEvaluateExecution; use Cognesy\Instructor\Extras\Evals\Contracts\CanRunExecution; use Cognesy\Instructor\Extras\Evals\Data\Evaluation; @@ -29,17 +28,11 @@ class Execution private ?Exception $exception = null; public function __construct( - string $label = '', - string $connection = '', - Mode $mode = Mode::Json, - bool $isStreamed = false, + array $case, ) { $this->id = Uuid::uuid4(); $this->data = new DataMap(); - $this->data->set('label', $label); - $this->data->set('connection', $connection); - $this->data->set('mode', $mode); - $this->data->set('isStreamed', $isStreamed); + $this->data->set('case', $case); } public function id() : string { @@ -74,6 +67,19 @@ public function hasException() : bool { return $this->exception !== null; } + public function status() : string { + return $this->exception ? 'failed' : 'success'; + } + + public function get(string $key) : mixed { + return $this->data->get($key); + } + + public function set(string $key, mixed $value) : self { + $this->data->set($key, $value); + return $this; + } + public function data() : DataMap { return $this->data; } @@ -123,13 +129,13 @@ public function execute() : void { $time = microtime(true); $this->executor->execute($this); $this->timeElapsed = microtime(true) - $time; - $this->usage = $this->data()->get('response')?->usage(); - $this->data()->set('notes', $this->data()->get('response')?->content()); + $this->usage = $this->get('response')?->usage(); + $this->data()->set('output.notes', $this->get('response')?->content()); $this->evaluations = $this->evaluate(); } catch(Exception $e) { $this->timeElapsed = microtime(true) - $time; - $this->data()->set('notes', $e->getMessage()); + $this->data()->set('output.notes', $e->getMessage()); $this->exception = $e; throw $e; } diff --git a/src/Extras/Evals/Executors/RunInference.php b/src/Extras/Evals/Executors/RunInference.php index 6fdb5a67..70a8baec 100644 --- a/src/Extras/Evals/Executors/RunInference.php +++ b/src/Extras/Evals/Executors/RunInference.php @@ -26,9 +26,9 @@ public function execute(Execution $execution) : Execution { private function makeLLMResponse(Execution $execution) : LLMResponse { return $this->inferenceAdapter->callInferenceFor( - connection: $execution->data()->get('connection'), - mode: $execution->data()->get('mode'), - isStreamed: $execution->data()->get('isStreamed'), + connection: $execution->get('case.connection'), + mode: $execution->get('case.mode'), + isStreamed: $execution->get('case.isStreaming'), messages: $this->inferenceData->messages, evalSchema: $this->inferenceData->inferenceSchema(), maxTokens: $this->inferenceData->maxTokens, diff --git a/src/Extras/Evals/Executors/RunInstructor.php b/src/Extras/Evals/Executors/RunInstructor.php index 5357a160..a2284853 100644 --- a/src/Extras/Evals/Executors/RunInstructor.php +++ b/src/Extras/Evals/Executors/RunInstructor.php @@ -25,7 +25,7 @@ public function execute(Execution $execution) : Execution { private function makeInstructorResponse(Execution $execution) : InstructorResponse { return (new Instructor) - ->withConnection($execution->data()->get('connection')) + ->withConnection($execution->get('case.connection')) ->request( messages: $this->instructorData->messages, input: $this->instructorData->input, @@ -38,12 +38,12 @@ private function makeInstructorResponse(Execution $execution) : InstructorRespon options: [ 'max_tokens' => $this->instructorData->maxTokens, 'temperature' => $this->instructorData->temperature, - 'stream' => $execution->data()->get('isStreamed'), + 'stream' => $execution->get('case.isStreamed'), ], toolName: $this->instructorData->toolName, toolDescription: $this->instructorData->toolDescription, retryPrompt: $this->instructorData->retryPrompt, - mode: $execution->data()->get('mode'), + mode: $execution->get('case.mode'), ); } } diff --git a/src/Extras/Evals/Experiment.php b/src/Extras/Evals/Experiment.php index b000ffae..b8087400 100644 --- a/src/Extras/Evals/Experiment.php +++ b/src/Extras/Evals/Experiment.php @@ -155,12 +155,12 @@ private function executeCase(mixed $case) : void { } private function makeExecution(mixed $case) : Execution { - return (new Execution( - label: (string) $case, - connection: $case->connection, - mode: $case->mode, - isStreamed: $case->isStreaming, - )) + $caseData = match(true) { + is_array($case) => $case, + method_exists($case, 'toArray') => $case->toArray(), + default => (array) $case, + }; + return (new Execution(case: $caseData)) ->withExecutor($this->executor) ->withEvaluators($this->evaluators); } diff --git a/src/Extras/Image/Image.php b/src/Extras/Image/Image.php index d51e075c..8407f830 100644 --- a/src/Extras/Image/Image.php +++ b/src/Extras/Image/Image.php @@ -3,6 +3,8 @@ namespace Cognesy\Instructor\Extras\Image; use Cognesy\Instructor\Contracts\CanProvideMessages; +use Cognesy\Instructor\Enums\Mode; +use Cognesy\Instructor\Instructor; use Cognesy\Instructor\Utils\Messages\Messages; use Exception; @@ -63,6 +65,30 @@ public function toImageUrl(): string { return $this->url ?: $this->base64bytes; } + public function toData( + string|array|object $responseModel, + string $prompt, + string $connection = '', + string $model = '', + string $system = '', + array $examples = [], + int $maxRetries = 0, + array $options = [], + Mode $mode = Mode::Tools, + ) : mixed { + return (new Instructor)->withConnection($connection)->request( + input: $this, + responseModel: $responseModel, + system: $system, + prompt: $prompt, + examples: $examples, + model: $model, + maxRetries: $maxRetries, + options: $options, + mode: $mode, + )->get(); + } + public function getBase64Bytes(): string { return $this->base64bytes; } diff --git a/src/Extras/Module/Modules/Web/ConvertHtmlToMarkdown.php b/src/Extras/Module/Modules/Web/ConvertHtmlToMarkdown.php index 33c3838d..84e81848 100644 --- a/src/Extras/Module/Modules/Web/ConvertHtmlToMarkdown.php +++ b/src/Extras/Module/Modules/Web/ConvertHtmlToMarkdown.php @@ -5,7 +5,7 @@ use Cognesy\Instructor\Extras\Module\Core\Module; use Cognesy\Instructor\Extras\Module\Signature\Attributes\ModuleDescription; use Cognesy\Instructor\Extras\Module\Signature\Attributes\ModuleSignature; -use Cognesy\Instructor\Extras\Web\Html\HtmlProcessor; +use Cognesy\Instructor\Utils\Web\Html\HtmlProcessor; #[ModuleSignature('html:string -> markdown:string')] #[ModuleDescription('Convert HTML to Markdown')] diff --git a/src/Extras/Module/Modules/Web/GetHtmlLinks.php b/src/Extras/Module/Modules/Web/GetHtmlLinks.php index 2abd603c..124d1995 100644 --- a/src/Extras/Module/Modules/Web/GetHtmlLinks.php +++ b/src/Extras/Module/Modules/Web/GetHtmlLinks.php @@ -4,8 +4,8 @@ use Cognesy\Instructor\Extras\Module\Core\Module; use Cognesy\Instructor\Extras\Module\Signature\Attributes\ModuleDescription; use Cognesy\Instructor\Extras\Module\Signature\Attributes\ModuleSignature; -use Cognesy\Instructor\Extras\Web\Link; use Cognesy\Instructor\Utils\Str; +use Cognesy\Instructor\Utils\Web\Link; #[ModuleSignature('html:string -> links:Link[]')] #[ModuleDescription('Extract links from HTML')] @@ -44,7 +44,7 @@ private function extractLinks(string $page, string $baseUrl = '') : array { $links = []; preg_match_all('/]+href\s*=\s*([\'"])(?.+?)\1[^>]*>(?.*?)<\/a>/i', $page, $matches); foreach ($matches['href'] as $key => $href) { - $link = new Link(); + $link = new \Cognesy\Instructor\Utils\Web\Link(); $link->url = $href; $link->title = strip_tags($matches['text'][$key]); $link->isInternal = $this->isInternal($href, $baseUrl); diff --git a/src/Extras/Module/Modules/Web/GetUrlContent.php b/src/Extras/Module/Modules/Web/GetUrlContent.php index 82a5be0b..a29e0020 100644 --- a/src/Extras/Module/Modules/Web/GetUrlContent.php +++ b/src/Extras/Module/Modules/Web/GetUrlContent.php @@ -5,7 +5,7 @@ use Cognesy\Instructor\Extras\Module\Core\Module; use Cognesy\Instructor\Extras\Module\Signature\Attributes\ModuleDescription; use Cognesy\Instructor\Extras\Module\Signature\Attributes\ModuleSignature; -use Cognesy\Instructor\Extras\Web\Scraper; +use Cognesy\Instructor\Utils\Web\Scraper; #[ModuleSignature('url:string -> content:string')] #[ModuleDescription('Retrieve the content of a URL')] diff --git a/src/Extras/Module/Modules/Web/GetWebpageDetails.php b/src/Extras/Module/Modules/Web/GetWebpageDetails.php index 17f279a7..9c9cde19 100644 --- a/src/Extras/Module/Modules/Web/GetWebpageDetails.php +++ b/src/Extras/Module/Modules/Web/GetWebpageDetails.php @@ -6,7 +6,7 @@ use Cognesy\Instructor\Extras\Module\Signature\Attributes\ModuleDescription; use Cognesy\Instructor\Extras\Module\Signature\Attributes\ModuleSignature; use Cognesy\Instructor\Extras\Web\Data\PageData; -use Cognesy\Instructor\Extras\Web\Html\HtmlProcessor; +use Cognesy\Instructor\Utils\Web\Html\HtmlProcessor; #[ModuleSignature('url:string -> pageDetails:PageData')] #[ModuleDescription('Retrieve information from a webpage')] diff --git a/src/Extras/Codebase/Actions/ExtractClasses.php b/src/Utils/Codebase/Actions/ExtractClasses.php similarity index 82% rename from src/Extras/Codebase/Actions/ExtractClasses.php rename to src/Utils/Codebase/Actions/ExtractClasses.php index a33fb9cd..1b68958c 100644 --- a/src/Extras/Codebase/Actions/ExtractClasses.php +++ b/src/Utils/Codebase/Actions/ExtractClasses.php @@ -1,8 +1,8 @@ - */ - private array $data = []; - - /** - * Dot notation handler. + * Dot object */ private Dot $dot; @@ -32,8 +25,7 @@ class DataMap implements JsonSerializable */ public function __construct(array $data = []) { - $this->data = $data; - $this->dot = new Dot($this->data); + $this->dot = new Dot($data); } /** @@ -65,10 +57,8 @@ public function set(string $key, mixed $value): self { if ($value instanceof self) { $this->dot->set($key, $value->toArray()); - $this->data[$key] = $value; } else { $this->dot->set($key, $value); - $this->data = $this->dot->all(); } return $this; @@ -98,13 +88,7 @@ public function getType(string $key): string if (!$this->has($key)) { throw new InvalidArgumentException("Key '{$key}' does not exist."); } - $value = $this->dot->get($key); - - if ($value instanceof self) { - return 'DataMap'; - } - return gettype($value); } @@ -194,19 +178,7 @@ public static function fromJson(string $json): self */ public function toArray(): array { - $result = []; - - foreach ($this->data as $key => $value) { - if ($value instanceof self) { - $result[$key] = $value->toArray(); - } elseif (is_callable($value)) { - $result[$key] = 'callable'; - } else { - $result[$key] = $value; - } - } - - return $result; + return $this->dot->all(); } /** @@ -217,17 +189,7 @@ public function toArray(): array */ public static function fromArray(array $array): self { - $data = []; - - foreach ($array as $key => $value) { - if (is_array($value)) { - $data[$key] = self::fromArray($value); - } else { - $data[$key] = $value; - } - } - - return new self($data); + return new self($array); } /** @@ -237,7 +199,7 @@ public static function fromArray(array $array): self */ public function fields(): array { - return array_keys($this->data); + return array_keys($this->toArray()); } /** @@ -301,7 +263,7 @@ private function collectValues(string $path): Map { private function collectWildcardValues(string $path): array { $pathParts = explode('.', $path); - return $this->traverseWithWildcards($this->data, $pathParts); + return $this->traverseWithWildcards($this->toArray(), $pathParts); } /** diff --git a/src/Utils/Git/Branches.php b/src/Utils/Git/Branches.php new file mode 100644 index 00000000..aba6aa8d --- /dev/null +++ b/src/Utils/Git/Branches.php @@ -0,0 +1,37 @@ +gitService = $gitService; + } + + public function current(): string + { + return $this->gitService->runCommand('rev-parse --abbrev-ref HEAD'); + } + + public function all(): array + { + $branches = $this->gitService->runCommand('branch'); + return array_map('trim', explode("\n", $branches)); + } + + public function create(string $branchName): self + { + $this->gitService->runCommand(sprintf('checkout -b %s', escapeshellarg($branchName))); + return $this; + } + + public function delete(string $branchName, bool $force = false): self + { + $command = $force ? 'branch -D' : 'branch -d'; + $this->gitService->runCommand(sprintf('%s %s', $command, escapeshellarg($branchName))); + return $this; + } +} diff --git a/src/Utils/Git/Commit.php b/src/Utils/Git/Commit.php new file mode 100644 index 00000000..5c81c25e --- /dev/null +++ b/src/Utils/Git/Commit.php @@ -0,0 +1,35 @@ +hash = $hash; + $this->gitService = $gitService; + } + + public function hash(): string + { + return $this->hash; + } + + public function author(): string + { + return $this->gitService->runCommand(sprintf('log -1 --pretty=format:%%an %%ae %s', escapeshellarg($this->hash))); + } + + public function message(): string + { + return $this->gitService->runCommand(sprintf('log -1 --pretty=format:%%B %s', escapeshellarg($this->hash))); + } + + public function date(): string + { + return $this->gitService->runCommand(sprintf('log -1 --pretty=format:%%ad %s', escapeshellarg($this->hash))); + } +} diff --git a/src/Utils/Git/Commits.php b/src/Utils/Git/Commits.php new file mode 100644 index 00000000..322de7d6 --- /dev/null +++ b/src/Utils/Git/Commits.php @@ -0,0 +1,26 @@ +gitService = $gitService; + } + + public function last(): Commit + { + $hash = $this->gitService->runCommand('rev-parse HEAD'); + return new Commit($hash, $this->gitService); + } + + public function log(int $number = 10): array + { + $log = $this->gitService->runCommand(sprintf('log -n %d --pretty=format:%%H', $number)); + $hashes = array_filter(array_map('trim', explode("\n", $log))); + return array_map(fn($hash) => new Commit($hash, $this->gitService), $hashes); + } +} diff --git a/src/Utils/Git/Diff.php b/src/Utils/Git/Diff.php new file mode 100644 index 00000000..d9cadae1 --- /dev/null +++ b/src/Utils/Git/Diff.php @@ -0,0 +1,23 @@ +gitService = $gitService; + } + + public function against(string $target = 'HEAD'): string + { + return $this->gitService->runCommand(sprintf('diff %s', escapeshellarg($target))); + } + + public function between(string $commitA, string $commitB): string + { + return $this->gitService->runCommand(sprintf('diff %s %s', escapeshellarg($commitA), escapeshellarg($commitB))); + } +} diff --git a/src/Utils/Git/File.php b/src/Utils/Git/File.php new file mode 100644 index 00000000..372de3d7 --- /dev/null +++ b/src/Utils/Git/File.php @@ -0,0 +1,42 @@ +gitService = $gitService; + } + + public function log(string $path, int $number = 10): array + { + $log = $this->gitService->runCommand(sprintf('log -n %d --pretty=format:%%H -- %s', $number, escapeshellarg($path))); + $hashes = array_filter(array_map('trim', explode("\n", $log))); + return array_map(fn($hash) => new Commit($hash, $this->gitService), $hashes); + } + + public function diff(string $path): string + { + return $this->gitService->runCommand(sprintf('diff HEAD -- %s', escapeshellarg($path))); + } + + public function versions(string $path, int $number = 10): array + { + $log = $this->gitService->runCommand(sprintf('log -n %d --pretty=format:%%H -- %s', $number, escapeshellarg($path))); + $hashes = array_filter(array_map('trim', explode("\n", $log))); + $versions = []; + + foreach ($hashes as $hash) { + $content = $this->gitService->runCommand(sprintf('show %s:%s', escapeshellarg($hash), escapeshellarg($path))); + $versions[] = [ + 'hash' => $hash, + 'content' => $content, + ]; + } + + return $versions; + } +} \ No newline at end of file diff --git a/src/Utils/Git/Git.php b/src/Utils/Git/Git.php new file mode 100644 index 00000000..8296ace5 --- /dev/null +++ b/src/Utils/Git/Git.php @@ -0,0 +1,99 @@ +repoPath = $repoPath; + $this->gitService = new GitService($repoPath); + } + + public static function dir(string $repoPath): self + { + return new self($repoPath); + } + + public function add(string $path): self + { + $this->gitService->runCommand(sprintf('add %s', escapeshellarg($path))); + return $this; + } + + public function commit(string $message): self + { + $this->gitService->runCommand(sprintf('commit -m %s', escapeshellarg($message))); + return $this; + } + + public function push(string $remote = 'origin', string $branch = 'HEAD'): self + { + $this->gitService->runCommand(sprintf('push %s %s', escapeshellarg($remote), escapeshellarg($branch))); + return $this; + } + + public function pull(string $remote = 'origin', string $branch = 'HEAD'): self + { + $this->gitService->runCommand(sprintf('pull %s %s', escapeshellarg($remote), escapeshellarg($branch))); + return $this; + } + + public function branches(): Branches + { + return new Branches($this->gitService); + } + + public function commits(): Commits + { + return new Commits($this->gitService); + } + + public function reset(string $commit = 'HEAD', bool $hard = false): self + { + $command = $hard ? 'reset --hard' : 'reset'; + $this->gitService->runCommand(sprintf('%s %s', $command, escapeshellarg($commit))); + return $this; + } + + public function merge(string $branch): self + { + $this->gitService->runCommand(sprintf('merge %s', escapeshellarg($branch))); + return $this; + } + + public function rebase(string $branch): self + { + $this->gitService->runCommand(sprintf('rebase %s', escapeshellarg($branch))); + return $this; + } + + public function status(): array + { + $status = $this->gitService->runCommand('status --short'); + return array_filter(array_map('trim', explode("\n", $status))); + } + + public function remote(): Remote + { + return new Remote($this->gitService); + } + + public function stash(): Stash + { + return new Stash($this->gitService); + } + + public function diff(): Diff + { + return new Diff($this->gitService); + } + + public function file(): File + { + return new File($this->gitService); + } +} diff --git a/src/Utils/Git/GitService.php b/src/Utils/Git/GitService.php new file mode 100644 index 00000000..552974ce --- /dev/null +++ b/src/Utils/Git/GitService.php @@ -0,0 +1,26 @@ +repoPath = $repoPath; + } + + public function runCommand(string $command): string + { + $fullCommand = sprintf('cd %s && git %s', escapeshellarg($this->repoPath), $command); + $output = shell_exec($fullCommand); + if ($output === null) { + throw new RuntimeException("Git command failed: {$command}"); + } + + return trim($output); + } +} diff --git a/src/Utils/Git/Remote.php b/src/Utils/Git/Remote.php new file mode 100644 index 00000000..88380340 --- /dev/null +++ b/src/Utils/Git/Remote.php @@ -0,0 +1,23 @@ +gitService = $gitService; + } + + public function url(): string + { + return $this->gitService->runCommand('config --get remote.origin.url'); + } + + public function diff(string $branch): string + { + return $this->gitService->runCommand(sprintf('diff origin/%s', escapeshellarg($branch))); + } +} diff --git a/src/Utils/Git/Stash.php b/src/Utils/Git/Stash.php new file mode 100644 index 00000000..da2b7323 --- /dev/null +++ b/src/Utils/Git/Stash.php @@ -0,0 +1,25 @@ +gitService = $gitService; + } + + public function save(): self + { + $this->gitService->runCommand('stash'); + return $this; + } + + public function apply(): self + { + $this->gitService->runCommand('stash apply'); + return $this; + } +} diff --git a/src/Extras/Web/Contracts/CanConvertToMarkdown.php b/src/Utils/Web/Contracts/CanConvertToMarkdown.php similarity index 62% rename from src/Extras/Web/Contracts/CanConvertToMarkdown.php rename to src/Utils/Web/Contracts/CanConvertToMarkdown.php index 4ceee82b..038e90b9 100644 --- a/src/Extras/Web/Contracts/CanConvertToMarkdown.php +++ b/src/Utils/Web/Contracts/CanConvertToMarkdown.php @@ -1,6 +1,6 @@ dataMap->set('name', 'Jane Doe'); $this->dataMap->set('address.zip', '12345'); $this->dataMap->set('skills', ['coding', 'design']); + $this->dataMap->set('newValue', '654'); + $this->dataMap->set('newArray.subValue', '432'); + $this->dataMap->set('newArrayWithSubarray.subValue.subsubValue', '210'); expect($this->dataMap->get('name'))->toBe('Jane Doe'); expect($this->dataMap->get('address.zip'))->toBe('12345'); expect($this->dataMap->get('skills')->toArray())->toBe(['coding', 'design']); + expect($this->dataMap->get('newValue'))->toBe('654'); + expect($this->dataMap->get('newArray.subValue'))->toBe('432'); + expect($this->dataMap->get('newArrayWithSubarray.subValue.subsubValue'))->toBe('210'); }); test('has method checks for key existence', function () {