lwazi 1.8.6 → 1.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/Console/LogsCommand.php +206 -0
- package/src/Http/Controllers/LogsStreamController.php +122 -0
- package/src/Installer/ProjectAnalyzer.php +10 -10
- package/src/Providers/LwaziServiceProvider.php +7 -0
- package/src/Rag/RagService.php +69 -15
- package/src/Services/KnowledgeBaseGenerator.php +7 -7
- package/src/Services/LwaziAgent.php +95 -21
- package/src/Services/LwaziLogger.php +610 -0
- package/src/Services/LwaziService.php +245 -107
- package/src/Services/NavigationGraph.php +722 -0
package/package.json
CHANGED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Lwazi\Core\Console;
|
|
4
|
+
|
|
5
|
+
use Illuminate\Console\Command;
|
|
6
|
+
use Lwazi\Core\Services\LwaziLogger;
|
|
7
|
+
|
|
8
|
+
class LogsCommand extends Command
|
|
9
|
+
{
|
|
10
|
+
protected $signature = 'lwazi:logs
|
|
11
|
+
{action=show : Action to perform (show, clear, trace, tail, watch)}
|
|
12
|
+
{trace_id? : Trace ID to show details}
|
|
13
|
+
{--limit=20 : Number of recent traces to show}
|
|
14
|
+
{--interval=2 : Refresh interval in seconds for tail/watch}';
|
|
15
|
+
|
|
16
|
+
protected $description = 'View and manage Lwazi trace logs for debugging';
|
|
17
|
+
|
|
18
|
+
public function handle()
|
|
19
|
+
{
|
|
20
|
+
$action = $this->argument('action');
|
|
21
|
+
|
|
22
|
+
match ($action) {
|
|
23
|
+
'show' => $this->showLogs(),
|
|
24
|
+
'list' => $this->showLogs(),
|
|
25
|
+
'trace' => $this->showTrace($this->argument('trace_id')),
|
|
26
|
+
'clear' => $this->clearLogs(),
|
|
27
|
+
'info' => $this->showLogInfo(),
|
|
28
|
+
'tail' => $this->tailLogs(),
|
|
29
|
+
'watch' => $this->tailLogs(),
|
|
30
|
+
default => $this->showHelp(),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
protected function showLogs(): void
|
|
35
|
+
{
|
|
36
|
+
$limit = (int) $this->option('limit');
|
|
37
|
+
$traces = LwaziLogger::getRecentTraces($limit);
|
|
38
|
+
|
|
39
|
+
if (empty($traces)) {
|
|
40
|
+
$this->warn('No traces found. Make sure the Lwazi chatbot has been used.');
|
|
41
|
+
$this->info('Log path: ' . LwaziLogger::getLogPath());
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
$this->info("Recent Traces ({$limit} most recent)");
|
|
46
|
+
$this->line(str_repeat('=', 80));
|
|
47
|
+
|
|
48
|
+
foreach ($traces as $trace) {
|
|
49
|
+
$id = $trace['id'] ?? 'unknown';
|
|
50
|
+
$message = $trace['message'] ?? 'no message';
|
|
51
|
+
$duration = round($trace['duration_ms'] ?? 0, 2);
|
|
52
|
+
$spans = count($trace['spans'] ?? []);
|
|
53
|
+
$events = count($trace['events'] ?? []);
|
|
54
|
+
$startTime = $trace['start_human'] ?? 'unknown';
|
|
55
|
+
$endTime = $trace['end_human'] ?? 'incomplete';
|
|
56
|
+
|
|
57
|
+
$this->line("");
|
|
58
|
+
$this->line("<fg=yellow>ID:</> {$id}");
|
|
59
|
+
$this->line("<fg=cyan>Time:</> {$startTime} → {$endTime}");
|
|
60
|
+
$this->line("<fg=cyan>Duration:</> {$duration}ms");
|
|
61
|
+
$this->line("<fg=cyan>Message:</> " . substr($message, 0, 60) . (strlen($message) > 60 ? '...' : ''));
|
|
62
|
+
$this->line("<fg=cyan>Spans:</> {$spans} | <fg=cyan>Events:</> {$events}");
|
|
63
|
+
|
|
64
|
+
if (!empty($trace['response'])) {
|
|
65
|
+
$response = substr($trace['response'], 0, 100);
|
|
66
|
+
$this->line("<fg=cyan>Response:</> " . $response . (strlen($trace['response']) > 100 ? '...' : ''));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
$this->line("<fg=gray>View details:</> php artisan lwazi:logs trace {$id}");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
$this->line("");
|
|
73
|
+
$this->line(str_repeat('=', 80));
|
|
74
|
+
$this->info("Total traces: " . count($traces));
|
|
75
|
+
$this->info("Log path: " . LwaziLogger::getLogPath());
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
protected function showTrace(?string $traceId): void
|
|
79
|
+
{
|
|
80
|
+
if (!$traceId) {
|
|
81
|
+
$this->error('Please provide a trace ID: php artisan lwazi:logs trace <trace_id>');
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
$trace = LwaziLogger::getTrace($traceId);
|
|
86
|
+
|
|
87
|
+
if (!$trace) {
|
|
88
|
+
$this->error("Trace not found: {$traceId}");
|
|
89
|
+
$this->info("Available traces:");
|
|
90
|
+
$recent = LwaziLogger::getRecentTraces(5);
|
|
91
|
+
foreach ($recent as $t) {
|
|
92
|
+
$this->line(" - {$t['id']}: " . substr($t['message'] ?? '', 0, 50));
|
|
93
|
+
}
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
$output = LwaziLogger::formatTraceForDebug($traceId);
|
|
98
|
+
$this->line($output);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
protected function clearLogs(): void
|
|
102
|
+
{
|
|
103
|
+
if (!$this->confirm('Are you sure you want to clear all Lwazi trace logs?')) {
|
|
104
|
+
$this->warn('Cancelled.');
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
LwaziLogger::clearTraces();
|
|
109
|
+
$this->info('All trace logs cleared.');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
protected function showLogInfo(): void
|
|
113
|
+
{
|
|
114
|
+
$this->info('Lwazi Trace Logging System');
|
|
115
|
+
$this->line(str_repeat('=', 50));
|
|
116
|
+
$this->line("");
|
|
117
|
+
$this->line("<fg=cyan>Log Path:</> " . LwaziLogger::getLogPath());
|
|
118
|
+
$this->line("<fg=cyan>Enabled:</> " . (LwaziLogger::isEnabled() ? '<fg=green>Yes' : '<fg=red>No'));
|
|
119
|
+
|
|
120
|
+
$traces = LwaziLogger::getRecentTraces(100);
|
|
121
|
+
$this->line("<fg=cyan>Total Traces:</> " . count($traces));
|
|
122
|
+
|
|
123
|
+
$totalDuration = 0;
|
|
124
|
+
foreach ($traces as $t) {
|
|
125
|
+
$totalDuration += $t['duration_ms'] ?? 0;
|
|
126
|
+
}
|
|
127
|
+
$avgDuration = count($traces) > 0 ? round($totalDuration / count($traces), 2) : 0;
|
|
128
|
+
$this->line("<fg=cyan>Avg Duration:</> {$avgDuration}ms");
|
|
129
|
+
|
|
130
|
+
$this->line("");
|
|
131
|
+
$this->line('Commands:');
|
|
132
|
+
$this->line(" <fg=yellow>php artisan lwazi:logs</> Show recent traces");
|
|
133
|
+
$this->line(" <fg=yellow>php artisan lwazi:logs show</> Show recent traces");
|
|
134
|
+
$this->line(" <fg=yellow>php artisan lwazi:logs show --limit=50</> Show more traces");
|
|
135
|
+
$this->line(" <fg=yellow>php artisan lwazi:logs trace <id></> Show trace details");
|
|
136
|
+
$this->line(" <fg=yellow>php artisan lwazi:logs clear</> Clear all logs");
|
|
137
|
+
$this->line(" <fg=yellow>php artisan lwazi:logs info</> Show this info");
|
|
138
|
+
$this->line("");
|
|
139
|
+
$this->line('Real-time SSE endpoints:');
|
|
140
|
+
$this->line(" <fg=cyan>GET /lwazi/logs/stream</> Live stream all traces");
|
|
141
|
+
$this->line(" <fg=cyan>GET /lwazi/logs/stream/{id}</> Live stream specific trace");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
protected function showHelp(): void
|
|
145
|
+
{
|
|
146
|
+
$this->info('Lwazi Logs Command');
|
|
147
|
+
$this->line(str_repeat('=', 50));
|
|
148
|
+
$this->line("");
|
|
149
|
+
$this->line('Usage:');
|
|
150
|
+
$this->line(" <fg=yellow>php artisan lwazi:logs</> Show recent traces");
|
|
151
|
+
$this->line(" <fg=yellow>php artisan lwazi:logs show</> Show recent traces");
|
|
152
|
+
$this->line(" <fg=yellow>php artisan lwazi:logs show --limit=50</> Show more traces");
|
|
153
|
+
$this->line(" <fg=yellow>php artisan lwazi:logs trace <id></> Show trace details");
|
|
154
|
+
$this->line(" <fg=yellow>php artisan lwazi:logs clear</> Clear all logs");
|
|
155
|
+
$this->line(" <fg=yellow>php artisan lwazi:logs info</> Show log info");
|
|
156
|
+
$this->line(" <fg=yellow>php artisan lwazi:logs tail</> Live tail new traces");
|
|
157
|
+
$this->line(" <fg=yellow>php artisan lwazi:logs tail --interval=1</> Faster refresh");
|
|
158
|
+
$this->line("");
|
|
159
|
+
$this->line('The trace logs show the flow of a message through the Lwazi system,');
|
|
160
|
+
$this->line('including intent detection, data fetching, and response generation.');
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
protected function tailLogs(): void
|
|
164
|
+
{
|
|
165
|
+
$interval = (int) $this->option('interval');
|
|
166
|
+
$lastCount = 0;
|
|
167
|
+
$lastTraceId = null;
|
|
168
|
+
|
|
169
|
+
$this->info("Live tailing Lwazi traces (Ctrl+C to stop)");
|
|
170
|
+
$this->info("Refresh interval: {$interval}s");
|
|
171
|
+
$this->line(str_repeat('=', 80));
|
|
172
|
+
|
|
173
|
+
while (true) {
|
|
174
|
+
$traces = LwaziLogger::getRecentTraces(10);
|
|
175
|
+
$currentCount = count($traces);
|
|
176
|
+
|
|
177
|
+
if ($currentCount !== $lastCount || ($traces && $traces[0]['id'] ?? null) !== $lastTraceId) {
|
|
178
|
+
$this->newLine();
|
|
179
|
+
$this->info("[" . date('H:i:s') . "] New activity detected ({$currentCount} traces)");
|
|
180
|
+
|
|
181
|
+
foreach ($traces as $trace) {
|
|
182
|
+
$id = $trace['id'] ?? 'unknown';
|
|
183
|
+
$message = substr($trace['message'] ?? 'no message', 0, 50);
|
|
184
|
+
$duration = round($trace['duration_ms'] ?? 0, 2);
|
|
185
|
+
$spans = count($trace['spans'] ?? []);
|
|
186
|
+
$endTime = $trace['end_human'] ?? 'running';
|
|
187
|
+
|
|
188
|
+
$this->line(" <fg=yellow>ID:</> {$id}");
|
|
189
|
+
$this->line(" <fg=cyan>Message:</> {$message}");
|
|
190
|
+
$this->line(" <fg=cyan>Duration:</> {$duration}ms | Spans: {$spans} | Ended: {$endTime}");
|
|
191
|
+
|
|
192
|
+
if (!empty($trace['response'])) {
|
|
193
|
+
$response = substr($trace['response'], 0, 80);
|
|
194
|
+
$this->line(" <fg=green>Response:</> {$response}...");
|
|
195
|
+
}
|
|
196
|
+
$this->line("");
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
$lastCount = $currentCount;
|
|
200
|
+
$lastTraceId = $traces[0]['id'] ?? null;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
sleep($interval);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Lwazi\Core\Http\Controllers;
|
|
4
|
+
|
|
5
|
+
use Illuminate\Routing\Controller;
|
|
6
|
+
use Illuminate\Http\Request;
|
|
7
|
+
use Lwazi\Core\Services\LwaziLogger;
|
|
8
|
+
use Illuminate\Support\Facades\Response;
|
|
9
|
+
|
|
10
|
+
class LogsStreamController extends Controller
|
|
11
|
+
{
|
|
12
|
+
protected array $seenTraceIds = [];
|
|
13
|
+
|
|
14
|
+
public function stream(Request $request): \Symfony\Component\HttpFoundation\StreamedResponse
|
|
15
|
+
{
|
|
16
|
+
$lastCount = 0;
|
|
17
|
+
|
|
18
|
+
return Response::stream(function () {
|
|
19
|
+
$seenTraceIds = [];
|
|
20
|
+
$lastCount = 0;
|
|
21
|
+
|
|
22
|
+
while (true) {
|
|
23
|
+
$traces = LwaziLogger::getRecentTraces(50);
|
|
24
|
+
$currentCount = count($traces);
|
|
25
|
+
|
|
26
|
+
$newTraces = array_filter($traces, function($trace) use (&$seenTraceIds) {
|
|
27
|
+
$id = $trace['id'] ?? null;
|
|
28
|
+
if ($id && !in_array($id, $seenTraceIds)) {
|
|
29
|
+
$seenTraceIds[] = $id;
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
return false;
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
$updatedTraces = [];
|
|
36
|
+
foreach ($traces as $trace) {
|
|
37
|
+
$id = $trace['id'] ?? null;
|
|
38
|
+
if ($id && isset($trace['end_time'])) {
|
|
39
|
+
$key = "updated_{$id}";
|
|
40
|
+
if (!in_array($key, $seenTraceIds)) {
|
|
41
|
+
$seenTraceIds[] = $key;
|
|
42
|
+
$updatedTraces[] = $trace;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (!empty($newTraces) || !empty($updatedTraces) || $currentCount !== $lastCount) {
|
|
48
|
+
$payload = [
|
|
49
|
+
'type' => 'update',
|
|
50
|
+
'timestamp' => date('Y-m-d H:i:s'),
|
|
51
|
+
'traces' => array_values(array_merge(
|
|
52
|
+
array_map(fn($t) => array_merge($t, ['_event' => 'new']), $newTraces),
|
|
53
|
+
array_map(fn($t) => array_merge($t, ['_event' => 'completed']), $updatedTraces)
|
|
54
|
+
)),
|
|
55
|
+
'stats' => [
|
|
56
|
+
'total' => $currentCount,
|
|
57
|
+
],
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
echo "data: " . json_encode($payload) . "\n\n";
|
|
61
|
+
ob_flush();
|
|
62
|
+
flush();
|
|
63
|
+
|
|
64
|
+
$lastCount = $currentCount;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
echo ": heartbeat\n\n";
|
|
68
|
+
ob_flush();
|
|
69
|
+
flush();
|
|
70
|
+
|
|
71
|
+
sleep(2);
|
|
72
|
+
}
|
|
73
|
+
}, 200, [
|
|
74
|
+
'Content-Type' => 'text/event-stream',
|
|
75
|
+
'Cache-Control' => 'no-cache',
|
|
76
|
+
'Connection' => 'keep-alive',
|
|
77
|
+
'X-Accel-Buffering' => 'no',
|
|
78
|
+
]);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
public function trace(string $traceId): \Symfony\Component\HttpFoundation\StreamedResponse
|
|
82
|
+
{
|
|
83
|
+
return Response::stream(function () use ($traceId) {
|
|
84
|
+
$lastData = null;
|
|
85
|
+
|
|
86
|
+
while (true) {
|
|
87
|
+
$trace = LwaziLogger::getTrace($traceId);
|
|
88
|
+
|
|
89
|
+
if ($trace && $trace !== $lastData) {
|
|
90
|
+
echo "data: " . json_encode([
|
|
91
|
+
'type' => 'trace_update',
|
|
92
|
+
'trace' => $trace,
|
|
93
|
+
]) . "\n\n";
|
|
94
|
+
ob_flush();
|
|
95
|
+
flush();
|
|
96
|
+
$lastData = $trace;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if ($trace && isset($trace['end_time'])) {
|
|
100
|
+
echo "data: " . json_encode([
|
|
101
|
+
'type' => 'trace_complete',
|
|
102
|
+
'trace' => $trace,
|
|
103
|
+
]) . "\n\n";
|
|
104
|
+
ob_flush();
|
|
105
|
+
flush();
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
echo ": heartbeat\n\n";
|
|
110
|
+
ob_flush();
|
|
111
|
+
flush();
|
|
112
|
+
|
|
113
|
+
sleep(1);
|
|
114
|
+
}
|
|
115
|
+
}, 200, [
|
|
116
|
+
'Content-Type' => 'text/event-stream',
|
|
117
|
+
'Cache-Control' => 'no-cache',
|
|
118
|
+
'Connection' => 'keep-alive',
|
|
119
|
+
'X-Accel-Buffering' => 'no',
|
|
120
|
+
]);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -4,7 +4,7 @@ namespace Lwazi\Core\Installer;
|
|
|
4
4
|
|
|
5
5
|
use Illuminate\Support\Facades\DB;
|
|
6
6
|
use Lwazi\Core\Support\ModelScanner;
|
|
7
|
-
use Lwazi\Core\Services\
|
|
7
|
+
use Lwazi\Core\Services\NavigationGraph;
|
|
8
8
|
|
|
9
9
|
class ProjectAnalyzer
|
|
10
10
|
{
|
|
@@ -41,7 +41,7 @@ class ProjectAnalyzer
|
|
|
41
41
|
$this->analyzePages();
|
|
42
42
|
$this->analyzeRoutes();
|
|
43
43
|
$this->analyzeInformationArchitecture();
|
|
44
|
-
$this->
|
|
44
|
+
$this->buildNavigationGraph();
|
|
45
45
|
$this->analyzeProjectType();
|
|
46
46
|
$this->generateSummary();
|
|
47
47
|
|
|
@@ -329,11 +329,11 @@ class ProjectAnalyzer
|
|
|
329
329
|
$this->log('IA pages: ' . count($iaPages) . ', links: ' . count($iaLinks));
|
|
330
330
|
}
|
|
331
331
|
|
|
332
|
-
protected function
|
|
332
|
+
protected function buildNavigationGraph(): void
|
|
333
333
|
{
|
|
334
|
-
$this->log('Building navigation
|
|
334
|
+
$this->log('Building navigation graph...');
|
|
335
335
|
|
|
336
|
-
$
|
|
336
|
+
$graph = new NavigationGraph();
|
|
337
337
|
|
|
338
338
|
$pages = $this->knowledge['pages'] ?? [];
|
|
339
339
|
$routes = $this->knowledge['routes'] ?? [];
|
|
@@ -352,13 +352,13 @@ class ProjectAnalyzer
|
|
|
352
352
|
return true;
|
|
353
353
|
});
|
|
354
354
|
|
|
355
|
-
$
|
|
356
|
-
$
|
|
355
|
+
$graph->buildFromPages($pages);
|
|
356
|
+
$graph->buildFromRoutes(array_values($publicRoutes));
|
|
357
357
|
|
|
358
|
-
$this->knowledge['
|
|
358
|
+
$this->knowledge['navigation_graph'] = $graph->toArray();
|
|
359
359
|
|
|
360
|
-
$flatCount = count($
|
|
361
|
-
$this->log("Navigation
|
|
360
|
+
$flatCount = count($graph->getFlatIndex());
|
|
361
|
+
$this->log("Navigation graph built with $flatCount indexed paths");
|
|
362
362
|
}
|
|
363
363
|
|
|
364
364
|
protected function normalizePath(string $href): ?string
|
|
@@ -6,6 +6,7 @@ use Illuminate\Support\ServiceProvider;
|
|
|
6
6
|
use Illuminate\Support\Facades\Route;
|
|
7
7
|
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
|
|
8
8
|
use Lwazi\Core\Services\LwaziService;
|
|
9
|
+
use Lwazi\Core\Services\LwaziLogger;
|
|
9
10
|
use Lwazi\Core\Tools\ToolRegistry;
|
|
10
11
|
use Lwazi\Core\Rag\RagService;
|
|
11
12
|
use Lwazi\Core\Services\KnowledgeBaseGenerator;
|
|
@@ -14,6 +15,7 @@ use Lwazi\Core\Console\AnalyzeProjectCommand;
|
|
|
14
15
|
use Lwazi\Core\Console\LwaziIngestCommand;
|
|
15
16
|
use Lwazi\Core\Console\SetupCommand;
|
|
16
17
|
use Lwazi\Core\Console\BuildContentIndexCommand;
|
|
18
|
+
use Lwazi\Core\Console\LogsCommand;
|
|
17
19
|
use Lwazi\Core\Http\Middleware\InjectLwaziChat;
|
|
18
20
|
|
|
19
21
|
class LwaziServiceProvider extends ServiceProvider
|
|
@@ -38,17 +40,22 @@ class LwaziServiceProvider extends ServiceProvider
|
|
|
38
40
|
SetupCommand::class,
|
|
39
41
|
LwaziIngestCommand::class,
|
|
40
42
|
BuildContentIndexCommand::class,
|
|
43
|
+
LogsCommand::class,
|
|
41
44
|
]);
|
|
42
45
|
}
|
|
43
46
|
}
|
|
44
47
|
|
|
45
48
|
public function boot(): void
|
|
46
49
|
{
|
|
50
|
+
new LwaziLogger();
|
|
51
|
+
|
|
47
52
|
$this->loadViewsFrom(__DIR__ . '/../../resources/views', 'lwazi');
|
|
48
53
|
|
|
49
54
|
Route::prefix(config('lwazi.route_prefix', 'lwazi'))->withoutMiddleware([\Illuminate\Foundation\Http\Middleware\VerifyCsrfToken::class])->group(function () {
|
|
50
55
|
Route::post('/chat', \Lwazi\Core\Http\Controllers\ChatController::class . '@chat');
|
|
51
56
|
Route::get('/status', \Lwazi\Core\Http\Controllers\ChatController::class . '@status');
|
|
57
|
+
Route::get('/logs/stream', \Lwazi\Core\Http\Controllers\LogsStreamController::class . '@stream');
|
|
58
|
+
Route::get('/logs/stream/{traceId}', \Lwazi\Core\Http\Controllers\LogsStreamController::class . '@trace');
|
|
52
59
|
});
|
|
53
60
|
|
|
54
61
|
$this->publishes(
|
package/src/Rag/RagService.php
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
namespace Lwazi\Core\Rag;
|
|
4
4
|
|
|
5
|
-
use Lwazi\Core\Services\
|
|
5
|
+
use Lwazi\Core\Services\NavigationGraph;
|
|
6
6
|
use Lwazi\Core\Services\LwaziService;
|
|
7
|
+
use Lwazi\Core\Services\LwaziLogger;
|
|
7
8
|
use Lwazi\Core\Services\EmbeddingService;
|
|
8
9
|
use Illuminate\Support\Facades\Http;
|
|
9
10
|
|
|
@@ -11,7 +12,7 @@ class RagService
|
|
|
11
12
|
{
|
|
12
13
|
protected array $projectKnowledge = [];
|
|
13
14
|
protected string $knowledgePath;
|
|
14
|
-
protected ?
|
|
15
|
+
protected ?NavigationGraph $navigationGraph = null;
|
|
15
16
|
protected ?EmbeddingService $embeddingService = null;
|
|
16
17
|
|
|
17
18
|
public function __construct()
|
|
@@ -35,34 +36,87 @@ class RagService
|
|
|
35
36
|
}
|
|
36
37
|
}
|
|
37
38
|
|
|
38
|
-
public function
|
|
39
|
+
public function getNavigationGraph(): ?NavigationGraph
|
|
39
40
|
{
|
|
40
|
-
if ($this->
|
|
41
|
-
return $this->
|
|
41
|
+
if ($this->navigationGraph !== null) {
|
|
42
|
+
return $this->navigationGraph;
|
|
42
43
|
}
|
|
43
44
|
|
|
44
|
-
$
|
|
45
|
-
if (!$
|
|
45
|
+
$graphData = $this->projectKnowledge['navigation_graph'] ?? null;
|
|
46
|
+
if (!$graphData) {
|
|
46
47
|
return null;
|
|
47
48
|
}
|
|
48
49
|
|
|
49
|
-
$this->
|
|
50
|
-
return $this->
|
|
50
|
+
$this->navigationGraph = NavigationGraph::fromArray($graphData);
|
|
51
|
+
return $this->navigationGraph;
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
public function findPath(string $query): ?array
|
|
54
55
|
{
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
LwaziLogger::step('rag_findPath_start', [
|
|
57
|
+
'query' => $query,
|
|
58
|
+
'query_length' => strlen($query),
|
|
59
|
+
]);
|
|
60
|
+
|
|
61
|
+
LwaziLogger::trackInput('LwaziService', [
|
|
62
|
+
'method' => 'findPath',
|
|
63
|
+
'query' => $query,
|
|
64
|
+
]);
|
|
65
|
+
|
|
66
|
+
$graph = $this->getNavigationGraph();
|
|
67
|
+
if (!$graph) {
|
|
68
|
+
LwaziLogger::step('rag_no_graph');
|
|
69
|
+
LwaziLogger::trackOutput('RagService', [
|
|
70
|
+
'result' => null,
|
|
71
|
+
'reason' => 'no navigation graph available',
|
|
72
|
+
]);
|
|
57
73
|
return null;
|
|
58
74
|
}
|
|
59
75
|
|
|
60
|
-
$
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
76
|
+
$flatIndex = $graph->getFlatIndex();
|
|
77
|
+
$nodeCount = count($flatIndex);
|
|
78
|
+
$nodePaths = array_keys($flatIndex);
|
|
79
|
+
|
|
80
|
+
LwaziLogger::step('rag_graph_loaded', [
|
|
81
|
+
'node_count' => $nodeCount,
|
|
82
|
+
'available_paths' => $nodePaths,
|
|
83
|
+
]);
|
|
84
|
+
|
|
85
|
+
LwaziLogger::trackDataFlow(
|
|
86
|
+
'RagService',
|
|
87
|
+
'NavigationGraph::search',
|
|
88
|
+
'search graph',
|
|
89
|
+
['query' => $query, 'total_nodes' => $nodeCount]
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
$result = $graph->search($query);
|
|
93
|
+
|
|
94
|
+
if ($result && !empty($result['path'])) {
|
|
95
|
+
LwaziLogger::step('rag_search_found', [
|
|
96
|
+
'path' => $result['path'],
|
|
97
|
+
'label' => $result['label'] ?? '',
|
|
98
|
+
'score' => $result['score'] ?? 0,
|
|
99
|
+
]);
|
|
100
|
+
} else {
|
|
101
|
+
LwaziLogger::step('rag_search_not_found', ['query' => $query]);
|
|
102
|
+
|
|
103
|
+
$result = $graph->searchWithStemming($query);
|
|
104
|
+
if ($result) {
|
|
105
|
+
LwaziLogger::step('rag_stemming_search_found', [
|
|
106
|
+
'path' => $result['path'],
|
|
107
|
+
'label' => $result['label'] ?? '',
|
|
108
|
+
'score' => $result['score'] ?? 0,
|
|
109
|
+
]);
|
|
110
|
+
} else {
|
|
111
|
+
LwaziLogger::step('rag_stemming_search_not_found', ['query' => $query]);
|
|
112
|
+
}
|
|
64
113
|
}
|
|
65
114
|
|
|
115
|
+
LwaziLogger::trackOutput('RagService', [
|
|
116
|
+
'result' => $result,
|
|
117
|
+
'method' => 'findPath',
|
|
118
|
+
]);
|
|
119
|
+
|
|
66
120
|
return $result;
|
|
67
121
|
}
|
|
68
122
|
|
|
@@ -31,7 +31,7 @@ class KnowledgeBaseGenerator
|
|
|
31
31
|
$database = $this->getDatabaseSchema();
|
|
32
32
|
$dataActions = $this->inferDataActions($models);
|
|
33
33
|
$informationArchitecture = $this->buildInformationArchitecture($pages, $routes);
|
|
34
|
-
$
|
|
34
|
+
$navigationGraph = $this->buildNavigationGraphFromRoutes($pages, $routes);
|
|
35
35
|
|
|
36
36
|
return [
|
|
37
37
|
'generated_at' => now()->toIso8601String(),
|
|
@@ -43,13 +43,13 @@ class KnowledgeBaseGenerator
|
|
|
43
43
|
'database' => $database,
|
|
44
44
|
'data_actions' => $dataActions,
|
|
45
45
|
'information_architecture' => $informationArchitecture,
|
|
46
|
-
'
|
|
46
|
+
'navigation_graph' => $navigationGraph,
|
|
47
47
|
];
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
protected function
|
|
50
|
+
protected function buildNavigationGraphFromRoutes(array $pages, array $routes): array
|
|
51
51
|
{
|
|
52
|
-
$
|
|
52
|
+
$graph = new NavigationGraph();
|
|
53
53
|
|
|
54
54
|
$publicRoutes = array_filter($routes, function($r) {
|
|
55
55
|
$uri = $r['uri'] ?? '';
|
|
@@ -65,9 +65,9 @@ class KnowledgeBaseGenerator
|
|
|
65
65
|
return true;
|
|
66
66
|
});
|
|
67
67
|
|
|
68
|
-
$
|
|
69
|
-
$
|
|
70
|
-
return $
|
|
68
|
+
$graph->buildFromPages($pages);
|
|
69
|
+
$graph->buildFromRoutes(array_values($publicRoutes));
|
|
70
|
+
return $graph->toArray();
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
/**
|