Alan Balcerowiak

Asystent AI z narzędziami, który nic nie zepsuje

Alan Balcerowiak ·

Asystent, który tylko czyta dokumenty, jest bezpieczny z natury. Ciekawie robi się wtedy, gdy ma sięgać do żywych systemów: zgłoszeń, monitoringu, inwentarza, kalendarzy. Tool calling to umożliwia. I od razu rodzi pytanie, które słyszę na każdym spotkaniu: „a co, jeśli model coś zmieni?”.

Odpowiedź z narzędzi, nie z pamięci

Najpierw o tym, po co w ogóle narzędzia. Model językowy zapytany o stan systemu odpowie coś prawdopodobnego. Nie ma skąd wiedzieć, ile zgłoszeń otwarto wczoraj ani który serwer przestał odpowiadać godzinę temu. Wywołanie narzędzia zmienia źródło odpowiedzi: model nie pamięta, tylko pyta system i składa odpowiedź z tego, co system zwrócił.

Schemat jest prosty. Model dostaje listę dostępnych funkcji z opisem parametrów, na przykład „zdarzenia z ostatnich N godzin dla lokalizacji X” albo „historia statusów hosta”. Na pytanie użytkownika odpowiada nie tekstem, tylko żądaniem wywołania funkcji z argumentami. Aplikacja wykonuje to wywołanie, zwraca wynik modelowi, a model formułuje odpowiedź.

Dwie zasady, których się trzymam:

  • Każda liczba i każdy fakt w odpowiedzi musi pochodzić z wyniku narzędzia. Jeśli narzędzie zwróciło pustą listę, odpowiedź brzmi „brak zdarzeń”, a nie domysł. Jeśli narzędzie zwróciło błąd, użytkownik dowiaduje się o błędzie.
  • Wyniki narzędzi pokazuję obok odpowiedzi. Użytkownik widzi, z jakiego zapytania wzięła się liczba, i może ją sprawdzić. To ta sama idea co cytowanie źródeł w RAG.

Dlaczego „nie zmieniaj niczego” w prompcie to za mało

Najprostsze zabezpieczenie wygląda tak: dajemy modelowi dostęp do API, a w prompcie piszemy, że wolno mu tylko odczytywać dane. To ten sam błąd, który opisuję w notatce o RAG, który odmawia: instrukcja jest prośbą, a nie gwarancją. Model może źle zrozumieć intencję użytkownika. Użytkownik może celowo spróbować go przekonać. Treść zwrócona przez narzędzie – opis zgłoszenia, komentarz, nazwa pliku – może zawierać tekst, który model potraktuje jak polecenie. Tego ostatniego nie da się w pełni wykluczyć żadnym promptem.

Wniosek: pytanie „czy model może coś zmienić” nie powinno zależeć od modelu. Ma na nie odpowiadać architektura.

Tylko do odczytu – konstrukcyjnie

Tryb tylko do odczytu wymuszam na kilku warstwach jednocześnie. Każda z nich działałaby sama, razem dają margines na błąd w którejkolwiek.

1. Narzędzia, których nie ma

Model widzi wyłącznie funkcje odczytujące. Nie istnieje funkcja „zamknij zgłoszenie” z adnotacją „nie używaj”. Jeśli operacji nie ma na liście, model nie ma jak jej zażądać. To lista pozytywna: dopisanie nowego narzędzia wymaga świadomej decyzji, a nie pamiętania o wykluczeniu.

2. Konto bez uprawnień do zapisu

Integracja łączy się z systemami źródłowymi na koncie technicznym, które ma wyłącznie prawa odczytu. Nawet gdyby w kodzie narzędzia znalazł się błąd, system docelowy odrzuci zapis. To najtańsza warstwa i często najskuteczniejsza.

3. Strażnik na poziomie transportu

Klient HTTP, przez który przechodzą wszystkie wywołania, przepuszcza tylko metody odczytu. Żądanie POST, PUT, PATCH czy DELETE kończy się wyjątkiem, zanim opuści proces – niezależnie od tego, który fragment kodu je zbudował. Jeśli dany system wymaga POST do samego wyszukiwania, dopuszczam go wyłącznie dla konkretnych, wymienionych z nazwy adresów, a nie ogólnie. Ten strażnik ma własne testy, które próbują wysłać zapis i oczekują odmowy.

4. Lokalna kopia zamiast żywego systemu

Tam, gdzie to możliwe, asystent nie rozmawia z systemem produkcyjnym wcale, tylko z lokalnym lustrem danych odświeżanym cyklicznie. Dane są wtedy kilka minut starsze, ale ścieżka zapisu do źródła fizycznie nie istnieje. Przy okazji odciąża to systemy źródłowe i uniezależnia asystenta od ich chwilowych awarii.

Walidacja argumentów

Odczyt też potrafi zaszkodzić. Zapytanie o zdarzenia z ostatnich pięciu lat dla wszystkich lokalizacji może położyć bazę równie skutecznie jak zapis. Dlatego argumenty od modelu traktuję jak dane od nieznanego użytkownika: sprawdzam typy, zakresy, długości list, a zakresy dat obcinam do rozsądnego maksimum. Narzędzie nigdy nie przyjmuje surowego zapytania SQL ani dowolnego adresu URL – zawsze nazwane parametry, z których sam składam zapytanie.

Uprawnienia użytkownika przenoszę na narzędzia. Jeśli osoba pytająca nie widzi danych danej lokalizacji w systemie źródłowym, narzędzie wywołane w jej imieniu też ich nie zwróci. Asystent nie może być bocznymi drzwiami do danych.

Audyt

Każde wywołanie narzędzia zapisuję: kto pytał, jakie było pytanie, jakie narzędzie model wybrał, z jakimi argumentami, co narzędzie zwróciło (albo przynajmniej skrót i rozmiar wyniku) i jaka odpowiedź trafiła do użytkownika. Dzięki temu na pytanie „skąd asystent to wziął?” odpowiadam z logu, a nie z domysłu.

Ten sam log jest najlepszym materiałem do ulepszania systemu. Widać w nim pytania, na które model wybrał złe narzędzie, i pytania, do których narzędzia brakuje. Każdy taki przypadek trafia do zestawu testowego – podobnie jak pytania spoza zakresu w RAG.

Lista kontrolna przed wdrożeniem

  • Czy na liście narzędzi jest wyłącznie odczyt – i czy dopisanie nowego wymaga przeglądu kodu?
  • Czy konto techniczne integracji ma odebrane prawa zapisu w każdym systemie źródłowym?
  • Czy istnieje test, który próbuje wysłać zapis przez klienta HTTP i oczekuje wyjątku?
  • Czy argumenty od modelu mają limity zakresów, a narzędzia przyjmują tylko nazwane parametry?
  • Czy uprawnienia użytkownika filtrują wyniki narzędzi, a nie tylko interfejs?
  • Czy z logu da się odtworzyć każdą odpowiedź: pytanie, narzędzie, argumenty, wynik?

Jeśli na którekolwiek z tych pytań odpowiedź brzmi „w prompcie jest napisane, żeby…”, to jest miejsce do poprawy.

Kompromisy

To podejście ma koszty. Asystent tylko do odczytu nie zamknie zgłoszenia ani nie zrestartuje usługi – użytkownik musi zrobić to sam, choć asystent podpowie, gdzie i jak. Lokalne lustro oznacza opóźnienie danych. Wąska lista narzędzi oznacza, że część pytań skończy się odpowiedzią „nie mam do tego narzędzia”.

Uważam, że to dobra wymiana. Zaufanie do asystenta, który może coś zepsuć, zawsze będzie warunkowe. Do asystenta, który konstrukcyjnie nie może, ludzie przyzwyczajają się szybko. Jeśli kiedyś przyjdzie czas na operacje zapisu, wolę dodać je jako osobną ścieżkę z jawnym potwierdzeniem człowieka niż poluzować tę, która działa.

Fundamenty takiego wdrożenia – model, embeddingi, serwowanie – opisuję w notatce Bielik on-premise: od czego zacząć.

  1. Dlaczego instrukcja w prompcie nie zatrzyma halucynacji i jak przenieść decyzję o odmowie do architektury: filtr trafności, warstwa decyzyjna, zestaw regresyjny.

    Czytaj →

  2. Cztery klocki lokalnego stosu (vLLM, BGE-M3, ChromaDB, FastAPI), jak myśleć o pamięci GPU i gdzie naprawdę traci się czas przy wdrożeniu.

    Czytaj →