Skip to content

Commit c78b516

Browse files
committed
.
1 parent ffad32c commit c78b516

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+2352
-659
lines changed

.env.example

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ LOG_CHANNEL=stack
88
LOG_DEPRECATIONS_CHANNEL=null
99
LOG_LEVEL=debug
1010

11+
UNSTRUCTURED_PORT=8000
12+
1113
DB_CONNECTION=pgsql
1214
DB_HOST=pgsql
1315
DB_PORT=5432

app/Features/HasDebug.php

+15
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,19 @@ private function debug($function, $data){
4848

4949
}
5050

51+
private function replaceSensitiveKeys(&$array) {
52+
foreach ($array as $key => &$value) {
53+
if (is_array($value)) {
54+
// Recurse into the array
55+
$this->replaceSensitiveKeys($value);
56+
} else {
57+
// Check if the key is 'abc' or 'xyz'
58+
if ($key === 'openaiApiKey' || $key === 'Authorization') {
59+
$value = '***';
60+
}
61+
}
62+
}
63+
}
5164
/**
5265
* Saves the debug information to the database and a log file.
5366
*
@@ -64,6 +77,8 @@ private function saveDebug(){
6477
'output' => $this->debug['output'] ?? null,
6578
'backtrace' => $this->debug['backtrace'] ?? null,
6679
];
80+
$this->replaceSensitiveKeys($debug['input']);
81+
$this->replaceSensitiveKeys($debug['backtrace']);
6782
Debug::create($debug);
6883
file_put_contents(storage_path('logs/'.time().'.json'), json_encode($debug, JSON_PRETTY_PRINT) . "\n");
6984
//filter keys and short messages?

app/Features/LLMs/OpenAI/HasOpenAI.php

+7
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,9 @@ private function sendMessageToFakeLLM($ChatCompletionOptions = [])
279279
}
280280
}
281281
EOT;
282+
$this->usage['promptTokens'] += $promptTokens;
283+
$this->usage['completionTokens'] += $completionTokens;
284+
$this->usage['totalTokens'] += $totalTokens;
282285
return json_decode($jsonResponse);
283286
}
284287

@@ -316,6 +319,10 @@ private function sendMessageToLLM($messages = [], $ChatCompletionOptions = [], $
316319
)
317320
);
318321
}
322+
$this->usage['promptTokens'] += $response->usage->promptTokens;
323+
$this->usage['completionTokens'] += $response->usage->completionTokens;
324+
$this->usage['totalTokens'] += $response->usage->totalTokens;
325+
319326
$this->debug('sendMessageToLLM()', ['ChatCompletionOptions' => $ChatCompletionOptions, '$messages'=>$messages, '$response'=>$response]);
320327
return $response;
321328
}

app/Filament/Resources/ApiResource.php

+27-8
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,16 @@ class ApiResource extends Resource
4343
public static function form(Form $form): Form
4444
{
4545
if($form->getOperation() == 'create'){
46+
$introDescription = Placeholder::make('')->content('Create an API for an AI service and define the API options & configuration. API creation will help you specify which service configurations can be changeable through an API call (API option) and which configurations are predefined (can not be changed in an API call).')->columnSpanFull();
47+
4648
$serviceInput = Forms\Components\Select::make('service_id')
4749
->relationship(name: 'service', titleAttribute: 'name',modifyQueryUsing: fn (Builder $query) => $query->where('isActive',true))
4850
->live()
4951
->required()
5052
->preload()
5153
->columnSpanFull()
54+
->helperText('Which Service would you like to create an API for it?')
55+
->label('Service')
5256
->afterStateUpdated(function (Set $set, Get $get, string $operation) {
5357
if($get('service_id')){
5458
$serviceModel = Service::where(['id'=>$get('service_id'), 'isActive'=>true])->first();
@@ -66,23 +70,36 @@ public static function form(Form $form): Form
6670
}
6771
});
6872
}else{
73+
$introDescription = Placeholder::make('')->content('')->columnSpanFull();
6974
$serviceInput = Placeholder::make('Service')->content(function (API $record, Set $set){
7075
return $record->service()->value('name');
7176
});
7277

7378
}
7479
return $form
7580
->schema([
76-
Forms\Components\TextInput::make('name')->required()->maxLength(40)->columnSpanFull()->live(debounce: 500)->afterStateUpdated(function (Get $get, Set $set) { $set('endpoint', Str::slug($get('name'))); }),
81+
$introDescription,
82+
Forms\Components\TextInput::make('name')->required()->maxLength(40)->columnSpanFull()->live(debounce: 500)->afterStateUpdated(function (Get $get, Set $set) { $set('endpoint', Str::slug($get('name'))); })->helperText('Any name to help you identify this API.'),
7783
Forms\Components\TextInput::make('description')->maxLength(255)->columnSpanFull(),
78-
Forms\Components\TextInput::make('endpoint')->required()->prefix('https://'.request()->getHost().'/api/../')->maxLength(100)->columnSpanFull()->live(onBlur: true)->afterStateUpdated(function (Get $get, Set $set) {$set('endpoint', Str::slug($get('endpoint')));}),
79-
Forms\Components\Toggle::make('enableUsage')->label('Track Usage')->required()->default(true),
80-
Forms\Components\Toggle::make('isActive')->label('Is Active?')->required()->default(true),
84+
Forms\Components\TextInput::make('endpoint')->required()->prefix('https://'.request()->getHost().'/api/../')->maxLength(100)->columnSpanFull()->live(onBlur: true)->helperText(
85+
function(Get $get){
86+
$url = '';
87+
if($get('endpoint')){
88+
$url = '(Example: <b>https://'.request()->getHost().'/api/xx/'.$get('endpoint').'</b>)';
89+
}
90+
$content = new HtmlString('API end point. '.$url);
91+
return $content;
92+
}
93+
)->afterStateUpdated(function (Get $get, Set $set) {$set('endpoint', Str::slug($get('endpoint')));}),
94+
Forms\Components\Toggle::make('enableUsage')->label('Track Usage')->required()->default(true)->helperText('Track API usage in Admin Portal.'),
95+
Forms\Components\Toggle::make('isActive')->label('Is Active')->required()->default(true)->helperText('Enable/Disable API.'),
8196
$serviceInput,
82-
Forms\Components\Select::make('collection_id')->relationship(name: 'collection', titleAttribute: 'name')->disabled(fn (Get $get): bool => !$get('supportCollection') ?? true)->visible(fn (Get $get): bool => $get('supportCollection') ?? false)->columnSpanFull(),
97+
Forms\Components\Select::make('collection_id')->relationship(name: 'collection', titleAttribute: 'name')->disabled(fn (Get $get): bool => !$get('supportCollection') ?? true)->visible(fn (Get $get): bool => $get('supportCollection') ?? false)->columnSpanFull()
98+
->helperText(new HtmlString('Should we use specific collections to help AI answer user questions? (this method is called RAG "<b>Retrieval Augmented Generation</b>").<br><b>What is RAG?</b> <a class="underline" href="https://research.ibm.com/blog/retrieval-augmented-generation-RAG" target=_blank>What is retrieval-augmented generation?</a> <a class="underline" href="https://huggingface.co/docs/transformers/model_doc/rag" target=_blank>RAG Overview</a>' )),
99+
83100
Grid::make()
84101
->schema(
85-
function (Get $get, Set $set, string $operation, API $record){
102+
function (Get $get, Set $set, string $operation, API $record = null){
86103
if($operation != 'create' && !$get('initiated')){
87104
$serviceModel = Service::where(['id'=>$record->service_id, 'isActive'=>true])->first();
88105
$set('supportCollection', $serviceModel->supportCollection);
@@ -91,7 +108,9 @@ function (Get $get, Set $set, string $operation, API $record){
91108
$set('initiated', true);
92109
}
93110
$groups = $get('optionGroups') ?? [];
94-
$sections = [];
111+
$sections = [
112+
Placeholder::make('')->content('Specify which service configurations can be changeable through an API call (API option) and which configurations are predefined (can not be changed in an API call).')->columnSpanFull()->visible(fn (Get $get): bool => (bool) $get('optionGroups') ?? false),
113+
];
95114
foreach($groups as $group){
96115
$sections[] =Section::make($group)
97116
->schema(
@@ -118,7 +137,7 @@ function (Get $get) use($group) {
118137
}),
119138
TextInput::make('default')->label('Value'),
120139
])->extraAttributes([
121-
'class' => 'shadow-xl bg-slate-500',
140+
// 'class' => 'shadow-xl bg-slate-500',
122141
// 'style' => 'li.border-width: 1px;li.border-color: red;'
123142
])
124143
// ->extraInputAttributes

app/Filament/Resources/AppResource.php

+15-3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
use Filament\Forms\Set;
1616
use Filament\Forms\Components\Actions\Action;
1717
use Filament\Support\Enums\FontWeight;
18+
use Filament\Forms\Components\Placeholder;
19+
use Illuminate\Support\HtmlString;
1820

1921
class AppResource extends Resource
2022
{
@@ -26,20 +28,28 @@ class AppResource extends Resource
2628

2729
public static function form(Form $form): Form
2830
{
31+
if($form->getOperation() == 'create'){
32+
$introDescription = Placeholder::make('')->content('Create an App and associate APIs to it so you can use Auth Token to access them from your application.');
33+
}else{
34+
$introDescription = Placeholder::make('')->content('Edit an App and associate APIs to it so you can use Auth Token to access them from your application.');
35+
}
2936
return $form
3037
->schema([
38+
$introDescription,
3139
Forms\Components\TextInput::make('name')
3240
->required()
41+
->helperText('Any name to help you identify this app.')
3342
->maxLength(40),
3443
Forms\Components\TextInput::make('description')
3544
->maxLength(255),
3645
Forms\Components\TextInput::make('owner')
3746
->maxLength(60),
3847
Forms\Components\TextInput::make('authToken')
48+
->helperText(new HtmlString('Bearer authentication tokens that you can use to access any of the associate APIs. <a href="https://swagger.io/docs/specification/authentication/bearer-authentication/">Bearer Authentication</a>'))
3949
->required()
4050
->minLength(50)
4151
->maxLength(100)
42-
->label('Auth Token')
52+
->label('Bearer Token')
4353
->suffixAction(
4454
Action::make('RegenerateAuthToken')
4555
->icon('heroicon-s-key')
@@ -50,12 +60,14 @@ public static function form(Form $form): Form
5060
->default(static function (): string {
5161
return App::newAuthToken();
5262
}),
53-
Forms\Components\Toggle::make('isActive')->label('Active?')
63+
Forms\Components\Toggle::make('isActive')->label('Is Active')
64+
->helperText('Enable/Disable App.')
5465
->required()->default(true),
5566
Forms\Components\Select::make('services')
67+
->helperText('List of APIs allowed to be used by this app.')
5668
->multiple()
5769
->relationship(name: 'apis', titleAttribute: 'name',modifyQueryUsing: fn (Builder $query) => $query->where('isActive',true))
58-
->preload()
70+
// ->preload()
5971
->searchable(['name', 'description'])
6072
->loadingMessage('Loading...')->live()
6173
])->columns(1);

app/Filament/Resources/CollectionResource.php

+28-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
use Filament\Forms\Set;
1616
use Filament\Forms\Components\Actions\Action;
1717
use Filament\Support\Enums\FontWeight;
18+
use Filament\Forms\Components\Placeholder;
19+
use Filament\Forms\Components\Hidden;
20+
use Illuminate\Support\HtmlString;
1821

1922
class CollectionResource extends Resource
2023
{
@@ -26,14 +29,31 @@ class CollectionResource extends Resource
2629
protected static ?int $navigationSort = 1;
2730
public static function form(Form $form): Form
2831
{
32+
if($form->getOperation() == 'create'){
33+
$introDescription = Placeholder::make('')->content(new HtmlString("A Collection serves as a structured data store for text-based <b>documents</b>. You can populate this Collection either via the Collection API endpoint or through the Admin Portal.
34+
<br><br>
35+
The primary function of a Collection is to extend the knowledge base accessible by an AI service. When creating an API, you can specify which Collection the AI should reference for its responses. This allows you to tailor the AI's behavior and the information it draws upon, depending on the context in which it's used.
36+
<br><br>
37+
<b>Example Use Case:</b>
38+
Suppose you have a chatbot aimed at handling HR-related queries (HRChatBot). You can create a Collection named 'HR_Policies' and upload all relevant HR documents into it. When a user asks a question to your 'HR ChatBot' or your 'ERP', the backend can be configured to call the API, which will then consult the 'HR_Policies' Collection to retrieve and generate a response based on the information it contains.
39+
<br><br>
40+
<b>Technical Note:</b>
41+
This mechanism utilizes a method known as Retrieval-Augmented Generation (RAG). RAG empowers the AI to scan the Collection and identify the most relevant information to construct its responses."))->columnSpanFull();
42+
}else{
43+
$introDescription = Placeholder::make('')->content('')->columnSpanFull();
44+
}
2945
return $form
3046
->schema([
47+
$introDescription,
3148
Forms\Components\TextInput::make('name')
3249
->required()
3350
->maxLength(40),
3451
Forms\Components\TextInput::make('description')
3552
->maxLength(255),
3653
Forms\Components\TextInput::make('authToken')
54+
->helperText('Bearer authentication tokens that you can use to access this Collection via the Collection APIs.')
55+
->label('Bearer Token')
56+
3757
->required()
3858
->minLength(50)
3959
->maxLength(100)
@@ -58,20 +78,27 @@ public static function form(Form $form): Form
5878
6 => '6 Documents',
5979
7 => '7 Documents',
6080
])
61-
->label('Max Documents to inject in LLM requests?')
81+
->label('Max Documents to share with AI service for each API call?')
6282
->required()
6383
->default(3),
6484
Forms\Components\Select::make('loader_id')
85+
->label('Documents Loader')
86+
->helperText('Document loader provides a "load/download" and "extract text" method for any specified URL or file (will work only when using Collection API).')
6587
->relationship(name: 'loader', titleAttribute: 'name')
6688
->preload()
6789
->searchable(['name', 'description'])
6890
->loadingMessage('Loading...')->live(),
6991
Forms\Components\Select::make('splitter_id')
92+
->label('Text Splitter')
93+
->helperText('Often times you want to split large text into smaller chunks to better work with language models. TextSplitters are responsible for splitting up large text into smaller documents (will work only when using Collection API).')
7094
->relationship(name: 'splitter', titleAttribute: 'name')
7195
->preload()
7296
->searchable(['name', 'description'])
7397
->loadingMessage('Loading...')->live(),
7498
Forms\Components\Select::make('embedder_id')
99+
->label('Embedder')
100+
->helperText('Embeddings create a vector representation of a document to do things like semantic search where we look for pieces of text that are most similar to an API query.')
101+
75102
->relationship(name: 'embedder', titleAttribute: 'name')
76103
->preload()
77104
->searchable(['name', 'description'])

app/Filament/Resources/DocumentResource.php

+17-3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
use Filament\Forms\Components\KeyValue;
1616
use App\Filament\Resources\CollectionResource\RelationManagers\DocumentsRelationManager;
1717
use Filament\Tables\Filters\SelectFilter;
18+
use Filament\Forms\Components\Placeholder;
19+
use Illuminate\Support\HtmlString;
1820

1921
class DocumentResource extends Resource
2022
{
@@ -27,8 +29,16 @@ class DocumentResource extends Resource
2729
// protected static bool $shouldRegisterNavigation = false;
2830
public static function form(Form $form): Form
2931
{
32+
if($form->getOperation() == 'create'){
33+
$introDescription = Placeholder::make('')->content(new HtmlString('Use this form to upload documents to any collection. You can also use the Collection APIs to integrate with other systems.
34+
<br><br>We will not use Text Splitter for documents created via this page.'))->columnSpanFull();
35+
}else{
36+
$introDescription = Placeholder::make('')->content('')->columnSpanFull();
37+
}
38+
3039
return $form
3140
->schema([
41+
$introDescription,
3242
Forms\Components\Select::make('collection_id')
3343
->relationship(name: 'collection', titleAttribute: 'name')
3444
->preload()
@@ -37,11 +47,15 @@ public static function form(Form $form): Form
3747
->required()
3848
->hiddenOn(DocumentsRelationManager::class),
3949
Forms\Components\Textarea::make('content')
40-
->required()
41-
->rows(8)
42-
->columnSpanFull(),
50+
->label('Document Content')
51+
->helperText(new HtmlString('<b>- Make sure you don\'t have any sensitive information in the document.<br>- Consider the LLM token limits that can be processed in a single interaction (System message + Document(s) + History + User Question).</b>'))
52+
->required()
53+
->rows(8)
54+
->columnSpanFull(),
4355

4456
Forms\Components\KeyValue::make('meta')
57+
->label('Meta Data')
58+
->helperText('Specify any metadata you would like to associate with this document (will be returned in the API response).')
4559
->addActionLabel('Add property'),
4660
])->columns(1);
4761
}

app/Filament/Resources/LoaderResource.php

+2-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ class LoaderResource extends Resource
2323
protected static ?string $navigationGroup = 'Settings';
2424
protected static ?int $navigationSort = 6;
2525

26+
2627
public static function form(Form $form): Form
2728
{
2829
return $form
@@ -35,7 +36,7 @@ public static function form(Form $form): Form
3536
Forms\Components\TextInput::make('className')
3637
->required()
3738
->maxLength(100),
38-
Forms\Components\KeyValue::make('options'),
39+
\InvadersXX\FilamentJsoneditor\Forms\JSONEditor::make('options'),
3940
])->columns(1);
4041
}
4142

app/Filament/Resources/LoaderResource/Pages/CreateLoader.php

+14
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,18 @@ class CreateLoader extends CreateRecord
1010
{
1111
protected static string $resource = LoaderResource::class;
1212
protected static bool $canCreateAnother = false;
13+
14+
protected function mutateFormDataBeforeFill(array $data): array
15+
{
16+
$data['options'] = json_encode($data['options']);
17+
18+
return $data;
19+
}
20+
21+
protected function mutateFormDataBeforeSave(array $data): array
22+
{
23+
$data['options'] = json_decode($data['options']);
24+
25+
return $data;
26+
}
1327
}

app/Filament/Resources/LoaderResource/Pages/EditLoader.php

+14
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,18 @@ protected function getHeaderActions(): array
1616
Actions\DeleteAction::make(),
1717
];
1818
}
19+
20+
protected function mutateFormDataBeforeFill(array $data): array
21+
{
22+
$data['options'] = json_encode($data['options']);
23+
24+
return $data;
25+
}
26+
27+
protected function mutateFormDataBeforeSave(array $data): array
28+
{
29+
$data['options'] = json_decode($data['options']);
30+
31+
return $data;
32+
}
1933
}

app/Filament/Resources/SplitterResource.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public static function form(Form $form): Form
3434
Forms\Components\TextInput::make('className')
3535
->required()
3636
->maxLength(100),
37-
Forms\Components\KeyValue::make('options'),
37+
\InvadersXX\FilamentJsoneditor\Forms\JSONEditor::make('options'),
3838
])->columns(1);
3939
}
4040

0 commit comments

Comments
 (0)