-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathrag_agent.php
More file actions
executable file
·567 lines (461 loc) · 23.1 KB
/
rag_agent.php
File metadata and controls
executable file
·567 lines (461 loc) · 23.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
#!/usr/bin/env php
<?php
/**
* Tutorial 13: RAG Pattern - Working Example
*
* Demonstrates Retrieval-Augmented Generation for grounding AI responses
* in external knowledge and reducing hallucinations.
*/
require_once __DIR__ . '/../../vendor/autoload.php';
require_once __DIR__ . '/../../examples/helpers.php';
require_once __DIR__ . '/../helpers.php';
use ClaudePhp\ClaudePhp;
loadEnv(__DIR__ . '/../../.env');
$client = new ClaudePhp(apiKey: getApiKey());
echo "╔════════════════════════════════════════════════════════════════════════════╗\n";
echo "║ Tutorial 13: RAG Pattern - Knowledge-Grounded Responses ║\n";
echo "╚════════════════════════════════════════════════════════════════════════════╝\n\n";
// ============================================================================
// RAG System Classes
// ============================================================================
/**
* Simple RAG system with document storage and retrieval
*/
class SimpleRAG
{
private $client;
private $documents = [];
private $chunks = [];
public function __construct($client)
{
$this->client = $client;
}
/**
* Add a document to the knowledge base
*/
public function addDocument($title, $content, $metadata = [])
{
$docId = count($this->documents);
$this->documents[$docId] = [
'id' => $docId,
'title' => $title,
'content' => $content,
'metadata' => $metadata
];
// Chunk and index
$chunks = $this->chunkText($content);
foreach ($chunks as $i => $chunkText) {
$this->chunks[] = [
'doc_id' => $docId,
'chunk_id' => $i,
'source' => $title,
'text' => $chunkText
];
}
return $docId;
}
/**
* Chunk text into smaller pieces
*/
private function chunkText($text, $chunkSize = 500, $overlap = 50)
{
// Split by sentences
$sentences = preg_split('/(?<=[.!?])\s+/', $text, -1, PREG_SPLIT_NO_EMPTY);
$chunks = [];
$currentChunk = '';
$wordCount = 0;
foreach ($sentences as $sentence) {
$sentenceWords = str_word_count($sentence);
if ($wordCount + $sentenceWords > $chunkSize && !empty($currentChunk)) {
$chunks[] = trim($currentChunk);
// Keep overlap
$words = explode(' ', $currentChunk);
$overlapWords = array_slice($words, -$overlap);
$currentChunk = implode(' ', $overlapWords) . ' ' . $sentence;
$wordCount = count($overlapWords) + $sentenceWords;
} else {
$currentChunk .= ' ' . $sentence;
$wordCount += $sentenceWords;
}
}
if (!empty(trim($currentChunk))) {
$chunks[] = trim($currentChunk);
}
return $chunks;
}
/**
* Retrieve relevant chunks for a query
*/
public function retrieve($query, $topK = 3)
{
$queryLower = strtolower($query);
$queryWords = array_filter(
explode(' ', $queryLower),
fn($w) => strlen($w) > 3
);
$scored = [];
foreach ($this->chunks as $chunk) {
$chunkLower = strtolower($chunk['text']);
$score = 0;
// Keyword matching score
foreach ($queryWords as $word) {
$count = substr_count($chunkLower, $word);
$score += $count * strlen($word); // Weight by word length
}
// Bonus for exact phrase
if (strpos($chunkLower, $queryLower) !== false) {
$score += 50;
}
$scored[] = [
'chunk' => $chunk,
'score' => $score
];
}
// Sort by score descending
usort($scored, fn($a, $b) => $b['score'] <=> $a['score']);
// Return top K
return array_slice(
array_map(fn($x) => $x['chunk'], $scored),
0,
$topK
);
}
/**
* Query with RAG
*/
public function query($question, $topK = 3)
{
// 1. Retrieve relevant chunks
$relevant = $this->retrieve($question, $topK);
if (empty($relevant)) {
return "No relevant information found in knowledge base.";
}
// 2. Build context
$context = "Reference Information:\n\n";
foreach ($relevant as $i => $chunk) {
$context .= "[Source {$i}] {$chunk['source']}:\n";
$context .= $chunk['text'] . "\n\n";
}
// 3. Generate answer with context
$prompt = $context .
"Question: {$question}\n\n" .
"Answer the question based on the reference information provided. " .
"Cite sources using [Source N] notation. " .
"If the information is not in the references, say so.";
try {
$response = $this->client->messages()->create([
'model' => 'claude-sonnet-4-5',
'max_tokens' => 1536,
'messages' => [['role' => 'user', 'content' => $prompt]]
]);
return [
'answer' => extractTextContent($response),
'sources' => $relevant
];
} catch (Exception $e) {
return [
'answer' => "Error: {$e->getMessage()}",
'sources' => []
];
}
}
/**
* Get document count
*/
public function getDocumentCount()
{
return count($this->documents);
}
/**
* Get chunk count
*/
public function getChunkCount()
{
return count($this->chunks);
}
}
// ============================================================================
// Sample Documents for Testing
// ============================================================================
$sampleDocs = [
[
'title' => 'PHP 8 Features',
'content' => 'PHP 8 was released in November 2020 and introduced several major features. ' .
'Named arguments allow you to pass parameters to functions by specifying the parameter name. ' .
'Union types enable a value to be of multiple types, declared with Type1|Type2 syntax. ' .
'The match expression is a more powerful alternative to switch statements with strict type comparisons. ' .
'The nullsafe operator ?-> allows chaining on potentially null values without errors. ' .
'Constructor property promotion reduces boilerplate by declaring and initializing properties in one line. ' .
'JIT (Just-In-Time) compilation improves performance for certain workloads. ' .
'Attributes provide a way to add metadata to classes and functions, replacing docblock annotations.'
],
[
'title' => 'Claude API Guide',
'content' => 'The Claude API allows you to integrate Claude into your applications. ' .
'Authentication requires an API key passed in the x-api-key header. ' .
'The Messages API is the primary endpoint for sending prompts and receiving responses. ' .
'You can specify the model, such as claude-sonnet-4-5 or claude-opus-4-20250514. ' .
'Max tokens controls the maximum length of the response. ' .
'Temperature affects randomness, from 0.0 (deterministic) to 1.0 (creative). ' .
'System prompts set the behavior and role of Claude for the conversation. ' .
'Tools enable function calling, allowing Claude to use external capabilities. ' .
'Streaming responses provide real-time token delivery for better user experience.'
],
[
'title' => 'Agentic AI Concepts',
'content' => 'Agentic AI refers to AI systems that can pursue goals autonomously. ' .
'The ReAct pattern combines reasoning and action in iterative loops. ' .
'Tools are external functions that agents can call to interact with the world. ' .
'Multi-step reasoning allows agents to break down complex tasks into manageable subtasks. ' .
'Memory enables agents to maintain context across interactions. ' .
'Planning involves creating action sequences before execution. ' .
'Reflection allows agents to evaluate and improve their own outputs. ' .
'Hierarchical agents organize work through master-worker relationships. ' .
'RAG (Retrieval-Augmented Generation) grounds agent responses in external knowledge.'
]
];
// ============================================================================
// Example 1: Basic RAG Query
// ============================================================================
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n";
echo "Example 1: Basic RAG - Knowledge Base Query\n";
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n";
$rag = new SimpleRAG($client);
echo "Building knowledge base...\n";
foreach ($sampleDocs as $doc) {
$docId = $rag->addDocument($doc['title'], $doc['content']);
echo " ✓ Added: {$doc['title']} (ID: {$docId})\n";
}
echo "\nKnowledge Base Stats:\n";
echo " • Documents: {$rag->getDocumentCount()}\n";
echo " • Chunks: {$rag->getChunkCount()}\n\n";
$question1 = "What is the nullsafe operator in PHP 8?";
echo "Question: {$question1}\n";
echo str_repeat("-", 80) . "\n";
$result1 = $rag->query($question1, 2);
echo "\nAnswer:\n{$result1['answer']}\n\n";
echo "Sources Used:\n";
foreach ($result1['sources'] as $i => $source) {
echo " [{$i}] {$source['source']}\n";
}
echo "\n💡 RAG retrieved relevant context and generated grounded answer!\n";
echo str_repeat("═", 80) . "\n\n";
// ============================================================================
// Example 2: Multiple Source Integration
// ============================================================================
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n";
echo "Example 2: Multi-Source Query\n";
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n";
$question2 = "How do tools work in agentic AI systems?";
echo "Question: {$question2}\n";
echo str_repeat("-", 80) . "\n";
$result2 = $rag->query($question2, 3);
echo "\nAnswer:\n{$result2['answer']}\n\n";
if (!empty($result2['sources'])) {
echo "Information drawn from " . count($result2['sources']) . " sources:\n";
$sourceTitles = array_unique(array_map(fn($s) => $s['source'], $result2['sources']));
foreach ($sourceTitles as $title) {
echo " • {$title}\n";
}
}
echo "\n💡 RAG synthesized information from multiple documents!\n";
echo str_repeat("═", 80) . "\n\n";
// ============================================================================
// Example 3: Comparing With and Without RAG
// ============================================================================
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n";
echo "Example 3: RAG vs No RAG Comparison\n";
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n";
$question3 = "What are the key features of PHP 8?";
echo "Question: {$question3}\n\n";
// Without RAG
echo "╔════ Without RAG (Direct Query) ════╗\n\n";
try {
$noRag = $client->messages()->create([
'model' => 'claude-sonnet-4-5',
'max_tokens' => 1024,
'messages' => [['role' => 'user', 'content' => $question3]]
]);
$noRagAnswer = extractTextContent($noRag);
echo substr($noRagAnswer, 0, 300) . "...\n\n";
echo "Note: Answer based on training data\n\n";
} catch (Exception $e) {
echo "Error: {$e->getMessage()}\n\n";
}
// With RAG
echo "╔════ With RAG (Knowledge-Grounded) ════╗\n\n";
$result3 = $rag->query($question3, 2);
echo substr($result3['answer'], 0, 300) . "...\n\n";
echo "Note: Answer grounded in provided documents with citations\n\n";
echo "💡 RAG provides verifiable, source-attributed answers!\n";
echo str_repeat("═", 80) . "\n\n";
// ============================================================================
// Example 4: Retrieval Quality Analysis
// ============================================================================
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n";
echo "Example 4: Analyzing Retrieval Quality\n";
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n";
$testQueries = [
"What is match expression?",
"How to authenticate Claude API?",
"What is the ReAct pattern?",
"Tell me about constructor property promotion"
];
echo "Testing retrieval for multiple queries...\n\n";
foreach ($testQueries as $query) {
echo "Query: \"{$query}\"\n";
$retrieved = $rag->retrieve($query, 1);
if (!empty($retrieved)) {
$topChunk = $retrieved[0];
echo " → Top match: {$topChunk['source']}\n";
echo " → Preview: " . substr($topChunk['text'], 0, 80) . "...\n";
} else {
echo " → No matches found\n";
}
echo "\n";
}
echo "💡 Good retrieval is critical for RAG quality!\n";
echo str_repeat("═", 80) . "\n\n";
// ============================================================================
// Example 5: Handling No Relevant Information
// ============================================================================
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n";
echo "Example 5: Graceful Handling of Missing Information\n";
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n";
$offTopicQuestion = "What is the capital of France?";
echo "Question: {$offTopicQuestion}\n";
echo "(Not in knowledge base)\n";
echo str_repeat("-", 80) . "\n";
$result5 = $rag->query($offTopicQuestion, 3);
echo "\nAnswer:\n{$result5['answer']}\n\n";
echo "💡 RAG agents should acknowledge when information is unavailable!\n";
echo str_repeat("═", 80) . "\n\n";
// ============================================================================
// Example 6: Chunk Size Impact
// ============================================================================
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n";
echo "Example 6: Document Chunking Strategy\n";
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n";
$longDoc = str_repeat($sampleDocs[0]['content'] . ' ', 3);
echo "Testing different chunking strategies on long document...\n\n";
// Create test RAG systems with different chunk sizes
$strategies = [
'Small Chunks' => 200,
'Medium Chunks' => 500,
'Large Chunks' => 1000
];
foreach ($strategies as $name => $chunkSize) {
$testRag = new SimpleRAG($client);
$testRag->addDocument('Test Doc', $longDoc);
echo "{$name} ({$chunkSize} words):\n";
echo " • Total chunks: {$testRag->getChunkCount()}\n";
echo " • Avg chunk size: ~{$chunkSize} words\n";
echo " • Trade-off: " . ($chunkSize < 500 ? "Precision > Context" : "Context > Precision") . "\n\n";
}
echo "💡 Chunk size affects retrieval precision and context!\n";
echo str_repeat("═", 80) . "\n\n";
// ============================================================================
// Example 7: RAG System Architecture
// ============================================================================
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n";
echo "Example 7: RAG Architecture Overview\n";
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n";
echo "RAG Pipeline:\n\n";
echo " 1. INDEXING (Offline)\n";
echo " ┌──────────┐\n";
echo " │Documents │\n";
echo " └────┬─────┘\n";
echo " │\n";
echo " ┌────▼──────┐\n";
echo " │ Chunk │\n";
echo " └────┬──────┘\n";
echo " │\n";
echo " ┌────▼──────┐\n";
echo " │ Index │\n";
echo " └───────────┘\n\n";
echo " 2. RETRIEVAL (Online)\n";
echo " ┌──────────┐\n";
echo " │ Query │\n";
echo " └────┬─────┘\n";
echo " │\n";
echo " ┌────▼──────┐\n";
echo " │ Search │\n";
echo " └────┬──────┘\n";
echo " │\n";
echo " ┌────▼──────┐\n";
echo " │Top K Docs │\n";
echo " └───────────┘\n\n";
echo " 3. GENERATION\n";
echo " ┌──────────┐ ┌──────────┐\n";
echo " │ Query │ + │Retrieved │\n";
echo " └────┬─────┘ │ Docs │\n";
echo " │ └────┬─────┘\n";
echo " └────────┬────────┘\n";
echo " │\n";
echo " ┌────▼────┐\n";
echo " │ Claude │\n";
echo " └────┬────┘\n";
echo " │\n";
echo " ┌────▼────┐\n";
echo " │ Answer │\n";
echo " │+ Sources│\n";
echo " └─────────┘\n\n";
echo "Key Components:\n";
echo " • Document Store - Knowledge repository\n";
echo " • Chunker - Breaks docs into retrievable units\n";
echo " • Retriever - Finds relevant chunks (keyword, semantic)\n";
echo " • Context Builder - Formats retrieved content\n";
echo " • Generator (Claude) - Produces grounded answers\n";
echo " • Citation System - Tracks source attribution\n\n";
echo str_repeat("═", 80) . "\n\n";
// ============================================================================
// Summary
// ============================================================================
echo "╔════════════════════════════════════════════════════════════════════════════╗\n";
echo "║ Tutorial Summary ║\n";
echo "╚════════════════════════════════════════════════════════════════════════════╝\n\n";
echo "✅ RAG Pattern Components Demonstrated:\n\n";
echo "1️⃣ Document Indexing\n";
echo " • Add documents to knowledge base\n";
echo " • Chunk into retrievable pieces\n";
echo " • Maintain source metadata\n\n";
echo "2️⃣ Retrieval\n";
echo " • Keyword-based search\n";
echo " • Similarity scoring\n";
echo " • Top-K selection\n\n";
echo "3️⃣ Context Building\n";
echo " • Format retrieved chunks\n";
echo " • Include source attribution\n";
echo " • Manage context length\n\n";
echo "4️⃣ Grounded Generation\n";
echo " • Claude with retrieved context\n";
echo " • Source citation\n";
echo " • Factual grounding\n\n";
echo "5️⃣ Quality Considerations\n";
echo " • Chunk size optimization\n";
echo " • Retrieval accuracy\n";
echo " • Missing information handling\n\n";
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n";
echo "🎯 When to Use RAG:\n\n";
echo " ✓ Need current information\n";
echo " ✓ Domain-specific knowledge\n";
echo " ✓ Private/proprietary data\n";
echo " ✓ Reduce hallucinations\n";
echo " ✓ Require source attribution\n";
echo " ✓ Dynamic knowledge updates\n\n";
echo "⚠️ When RAG May Not Help:\n\n";
echo " • General knowledge queries\n";
echo " • Creative generation tasks\n";
echo " • Small, static knowledge sets\n";
echo " • Latency-critical applications\n\n";
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n";
echo "💡 Key Insights:\n\n";
echo " • RAG grounds responses in facts\n";
echo " • Retrieval quality determines answer quality\n";
echo " • Chunking strategy impacts precision vs context\n";
echo " • Citations build trust and verifiability\n";
echo " • Can update knowledge without retraining\n";
echo " • Combine with other patterns (RAG + ReAct)\n\n";
echo "🚀 RAG enables knowledge-grounded, trustworthy AI agents!\n\n";
echo "Next: Tutorial 14 - Autonomous Agents for goal-directed behavior\n";
echo "→ tutorials/14-autonomous-agents/\n\n";