1. Go to this page and download the library: Download redberry/pest-plugin-evals library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
redberry / pest-plugin-evals example snippets
test('sales coach provides constructive feedback', function () {
evaluate(SalesCoach::class)
->whenPrompted('The customer said "too expensive" and I hung up.')
->toMeet('The response should offer negotiation tactics')
->toMeet('The tone should be encouraging, not critical');
});
use App\Ai\Agents\PostWriter;
test('PostWriter writes engaging content', function () {
evaluate(PostWriter::class)
->whenPrompted('Write a blog post about Laravel')
->toMeet('The content is engaging and informative');
});
test('PostWriter writes engaging content about Laravel', function () {
evaluate(PostWriter::class)
->whenPrompted('Write a blog post about Laravel')
->toMeet('The content is engaging and informative')
->assertContains('Laravel')
->assertLengthGreaterThan(200);
});
use App\Ai\Agents\SalesCoach;
use App\Models\User;
// Pass the class name — resolved via Laravel's container
evaluate(SalesCoach::class);
// Pass constructor arguments for the container to inject
evaluate(SalesCoach::class, ['user' => $user]);
// Pass an already-built instance
evaluate(new SalesCoach($user));
// Pass a closure that returns an agent
evaluate(fn () => SalesCoach::make(user: $user));
evaluate(SalesCoach::class)
->whenPrompted('Analyze this sales call transcript...')
->toMeet('The feedback is constructive');
use Laravel\Ai\Enums\Lab;
evaluate(SalesCoach::class)
->prompt(
'Analyze this transcript...',
provider: Lab::Anthropic,
model: 'claude-3-5-sonnet',
timeout: 120,
)
->toMeet('The feedback is constructive');
evaluate(SalesCoach::class)
->provider(Lab::Anthropic)
->model('claude-3-5-sonnet')
->timeout(120)
->whenPrompted('Analyze this transcript...')
->toMeet('The feedback is constructive');
use Laravel\Ai\Files;
// Inline with prompt
evaluate(DocumentAnalyzer::class)
->prompt(
'Summarize this document',
attachments: [
Files\Document::fromStorage('contracts/agreement.pdf'),
Files\Image::fromStorage('screenshot.png'),
],
)
->toMeet('Summary captures key contract terms');
// Or as a separate fluent method
evaluate(DocumentAnalyzer::class)
->attachments([
Files\Document::fromStorage('contracts/agreement.pdf'),
])
->whenPrompted('Summarize this document')
->toMeet('Summary captures key contract terms');
use Redberry\Evals\EvalCase;
$case = EvalCase::make()
->prompt('Kindly ask to contact us at [email protected]')
->expected('Please, contact us at [email protected]');
evaluate(SupportAgent::class)
->withCase($case)
->toMeet('Asks the user to contact at [email protected]')
->toBeSimilarTo($case->expected);
evaluate(SalesCoach::class)
->whenPrompted('The customer said "too expensive" and I hung up.')
->toMeet('The response should offer negotiation tactics')
->toMeet('The tone should be encouraging, not critical');
evaluate(SalesCoach::class)
->whenPrompted('Review this call transcript...')
->toMeet('The feedback is constructive and actionable', 80); // Must score >= 80
evaluate(SalesCoach::class)
->whenPrompted('Review this call transcript...')
->assertDoesNotMeet('The response contains profanity or insults');
evaluate(Summarizer::class)
->whenPrompted('Summarize this article...')
->toBeSimilarTo('Expected summary mentioning key points X, Y, and Z');
evaluate(Summarizer::class)
->expected('A concise summary mentioning key points X, Y, and Z')
->whenPrompted('Summarize this article...')
->toBeSimilar(); // Compares output against the expected value set above
$result = evaluate(SalesCoach::class)
->whenPrompted('Review this call transcript...')
->judge('Is the response helpful?');
$result->passed; // bool
$result->score; // int|null (0-100, only for scored judges)
$result->reasoning; // string — the judge's explanation
expect($result->score)->toBeGreaterThan(80);
expect($result->passed)->toBeTrue();
use Laravel\Ai\Enums\Lab;
evaluate(SalesCoach::class)
->judgeWith(Lab::OpenAI, 'gpt-4o-mini')
->whenPrompted('Review this call...')
->toMeet('The feedback is constructive');
evaluate(SalesCoach::class)
->judgeInstructions('The agent is a sales coaching tool — evaluate from a sales training perspective.')
->whenPrompted('Review this call...')
->toMeet('Professional and actionable advice');
evaluate(CopyWriter::class)
->whenPrompted('Write a tweet about Laravel')
->assertContains('Laravel') // Contains this string
->assertContains(['Laravel', 'PHP']) // Contains ALL of these
->assertContainsAny(['Laravel', 'Symfony']) // Contains at least one
->assertNotContains('bad word') // Does NOT contain
->assertMatches('/Laravel \d+/'); // Matches regex
evaluate(CopyWriter::class)
->whenPrompted('Write a tweet about Laravel')
->assertLengthLessThan(280)
->assertLengthGreaterThan(10)
->assertLengthBetween(50, 280); // Inclusive
evaluate(ApiAgent::class)
->whenPrompted('Return user data as JSON')
->assertJson() // Valid JSON
->assertJsonPath('user.name', 'Taylor') // Dot-notation path
->assertJsonStructure(['user' => ['name', 'email']]); // Has this shape
evaluate(Agent::class)
->whenPrompted('...')
->assertString() // Plain text (no structured output)
->assertNotEmpty();
evaluate(StructuredAgent::class)
->whenPrompted('...')
->assertArray(); // Has structured (array) output
evaluate(CopyWriter::class)
->whenPrompted('Write a tweet about Laravel')
->toMeet('The tone is enthusiastic') // LLM judge
->assertContains('Laravel') // Deterministic
->assertLengthLessThan(280); // Deterministic
use App\Ai\Tools\WebSearch;
// By class (recommended)
evaluate(ResearchAgent::class)
->whenPrompted('Find Laravel 12 release notes')
->assertToolUsed(WebSearch::class);
// By string name
evaluate(ResearchAgent::class)
->whenPrompted('Find Laravel 12 release notes')
->assertToolUsed('web_search');
use Redberry\Evals\ToolInvocation;
// Exact argument match
->assertToolUsed(WebSearch::class, ['query' => 'Laravel 12'])
// Closure — inspect arguments freely
->assertToolUsed(WebSearch::class, function (ToolInvocation $tool) {
return str_contains($tool->query, 'Laravel 12');
})
->assertToolNotUsed(DangerousTool::class)
use App\Ai\Tools\WebSearch;
use App\Ai\Tools\Summarize;
->assertToolUseSequence([WebSearch::class, Summarize::class])
->assertToolUsedTimes(WebSearch::class, 2) // Exactly 2 times
->assertToolUsedAtLeast(WebSearch::class, 1) // At least once
->assertToolUsedAtMost(WebSearch::class, 5) // No more than 5
->assertToolUsedAtLeast(WebSearch::class, 2, function (ToolInvocation $tool) {
return str_contains($tool->query, 'Laravel');
})
evaluate(DataExtractor::class)
->whenPrompted('Extract user info from: John Doe, [email protected]')
->assertHasKey('name') // Key exists
->assertHasKey('address.city') // Supports dot notation
->assertHasKey('name', 'John Doe') // Key exists with this value
->assertHasKeys(['name', 'email']); // Multiple keys exist
$result = evaluate(DataExtractor::class)
->whenPrompted('Extract: John Doe, [email protected]')
->run();
// EvalResult implements ArrayAccess — access structured keys directly
$result['name']; // 'John Doe'
$result['email']; // '[email protected]'
// Or use Pest expectations
expect($result['name'])->toBe('John Doe');
expect($result->text)->not->toBeEmpty();
evaluate(SalesCoach::class)
->whenPrompted('Review this sales call...')
->samples(5)
->toMeet('The feedback is constructive');
evaluate(SalesCoach::class)
->whenPrompted('Review this sales call...')
->samples(5, minimum: 4) // At least 4 of 5 must pass
->toMeet('The feedback is constructive');
->repeat(5)
->repeat(5, minimum: 4)
evaluate(SalesCoach::class)
->whenPrompted('...')
->samples(5, minimum: 4)
->toMeet('Professional tone', 80); // At least 4 of 5 must score >= 80
evaluate(CopyWriter::class)
->whenPrompted('Write a tweet about Laravel')
->samples(3)
->assertContains('Laravel') // All 3 must contain "Laravel"
->assertLengthLessThan(280) // All 3 must be under 280 chars
->toMeet('The tone is enthusiastic'); // All 3 must pass
evaluate(ResearchAgent::class)
->whenPrompted('Find information about Laravel 12')
->samples(3, minimum: 2)
->assertToolUsed(WebSearch::class) // At least 2 of 3 must use WebSearch
->assertToolUsedAtMost(WebSearch::class, 3); // Each run uses it at most 3 times
$samples = evaluate(DataExtractor::class)
->whenPrompted('Extract: John, [email protected]')
->samples(5)
->run();
$samples->count(); // 5
$samples->outputs(); // Collection of all EvalResult objects
$samples->first(); // First sample result
$samples->last(); // Last sample result
$samples = evaluate(SalesCoach::class)
->whenPrompted('...')
->samples(5)
->judge('Is the response helpful?');
$samples->passRate(); // e.g. 80.0 (4 of 5 passed)
$samples->averageScore(); // e.g. 82.0
$samples->passed(); // true/false based on minimum + threshold
$samples->judgeResults(); // Collection of individual JudgeResult objects
$samples->each(function (JudgeResult $result, int $index) {
dump("Sample #{$index}: score={$result->score}, passed={$result->passed}");
});
use Redberry\Evals\EvalCase;
dataset('sales_scenarios', [
'angry customer' => fn () => EvalCase::make()
->prompt('I want a refund NOW!')
->expected('Calm de-escalation response'),
'confused customer' => fn () => EvalCase::make()
->prompt('How do I log in?')
->expected('Step-by-step instructions'),
]);
it('handles customer scenarios', function (EvalCase $case) {
evaluate(SupportBot::class)
->withCase($case)
->toMeet($case->expected);
})->with('sales_scenarios');
// Prompt only
EvalCase::make()
->prompt('Write a haiku about PHP');
// With attachments
EvalCase::make()
->prompt('What are the key terms in this contract?')
->attachments([
Files\Document::fromStorage('contracts/agreement.pdf'),
])
->expected('Contract summary with dates and parties');
namespace App\Evals\Rubrics;
use Redberry\Evals\Contracts\Rubric;
class ProfessionalTone extends Rubric
{
public function description(): string
{
return <<<'PROMPT'
Evaluate if the response maintains a professional tone:
- No slang or informal language
- Proper grammar and punctuation
- Respectful and courteous
- Appropriate for business communication
PROMPT;
}
// Optional: return a 0-100 score instead of binary pass/fail
public function scored(): bool
{
return true;
}
}
evaluate(SalesCoach::class)
->whenPrompted('Review this call...')
->toMeet(new ProfessionalTone)
->toMeet(new ActionableAdvice);
namespace App\Evals\Judges;
use Redberry\Evals\Contracts\Judge;
use Redberry\Evals\EvalContext;
use Redberry\Evals\JudgeResult;
class CustomSimilarityJudge implements Judge
{
public function __construct(
private float $threshold = 80
) {}
public function evaluate(EvalContext $context): JudgeResult
{
$input = $context->input; // The prompt sent to the agent
$actual = $context->output; // The agent's text response
$expected = $context->expected; // The expected output (if set)
$result = $context->result; // The full EvalResult object
// Your evaluation logic here (embeddings, another LLM, etc.)
$similarity = /* ... */ 85;
return new JudgeResult(
passed: $similarity >= $this->threshold,
score: $similarity,
reasoning: "Similarity score: {$similarity}",
);
}
}
evaluate(SalesCoach::class)
->judgeInstructions('This agent is a sales coaching tool. Evaluate advice quality from a sales training perspective.')
->whenPrompted('Review this call transcript...')
->toMeet('Professional and actionable advice');
use Laravel\Ai\Enums\Lab;
evaluate(SalesCoach::class)
->judgeWith(Lab::OpenAI, 'gpt-4o-mini')
->whenPrompted('...')
->toMeet('...');
test('PostWriter writes engaging content', function () {
evaluate(PostWriter::class)
->whenPrompted('Write a blog post about Laravel')
->toMeet('The content is engaging and informative');
})->group('evals');
uses()->group('evals');
test('PostWriter writes engaging content', function () {
evaluate(PostWriter::class)
->whenPrompted('Write a blog post about Laravel')
->toMeet('The content is engaging and informative');
})->skipOnCi();
use App\Ai\Agents\BlogWriter;
test('BlogWriter creates engaging content', function () {
evaluate(BlogWriter::class)
->whenPrompted('Write a blog post about PHP 8.4 features')
->toMeet('The content explains at least 3 new features')
->toMeet('The writing style is engaging and accessible')
->assertContains('PHP')
->assertLengthGreaterThan(500)
->assertDoesNotMeet('Contains factual errors about PHP');
});
use App\Ai\Agents\ResearchAssistant;
use App\Ai\Tools\WebSearch;
use Redberry\Evals\ToolInvocation;
test('ResearchAssistant uses web search appropriately', function () {
evaluate(ResearchAssistant::class)
->whenPrompted('What are the latest Laravel 12 features?')
->assertToolUsed(WebSearch::class)
->assertToolUsed(WebSearch::class, function (ToolInvocation $tool) {
return str_contains($tool->query, 'Laravel 12');
})
->assertToolUsedAtMost(WebSearch::class, 3)
->toMeet('Response cites sources from the web search')
->toMeet('Information is current and accurate');
});