Uwagi praktyczne: Stworzyłem lokalnego agenta AI za pomocą Ollamy — i trudna część
Krok po kroku praktyczne wskazówki: Stworzyłem lokalnego agenta AI za pomocą Ollamy — oraz trudne aspekty: umowy, weryfikacje i miejsca na kod do łatwego wdrożenia dla zespołów stosujących ten model.
To przewodnictwo pokazuje, jak przejść od surowców do działającego systemu w przypadku: Stworzyłem lokalnego agenta AI za pomocą Ollamy — a trudna część to nie był model. Skupiamy się na krokach operacyjnych, wyraźnych sprawdzeniach oraz kodzie, który można bez problemu dodać do repozytorium, nie musząc zgadywać intencji. Na etapie przeglądu należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed zmianą kodu. Operatorzy powinni móc ponownie uruchomić dany krok na podstawie znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Lepiej używać małych, testowalnych jednostek niż rozbudowanych skryptów. Gdy jakiś krok zawiedzie, powinien wskazywać na konkretną odpowiedzialność, a nie na skomplikowany łańcuch operacji.
Dlaczego wybraliście Ollamę
Gdy przechodzisz przez etap „Dlaczego wybrałeś Ollamę”, najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Traktuj ten etap jako umowę pomiędzy danymi wejściowymi a zweryfikowanymi wynikami. Nadaj nazwy poszczególnym elementom, zdefiniuj kryteria sukcesu i odrzuć przypadkowe, częściowe ukończenie zadania. Zapisuj ID żądania, ID modelu oraz czas opóźnienia przy każdej próbie połączenia. Bez tych informacji przerywane błędy dostawcy wyglądają jak błędy aplikacji.
ollama pull qwen3
pip install ollama
from ollama import chat
response = chat(
model="qwen3",
messages=[
{"role": "user", "content": "Explain what an overdue invoice is."}
],
)print(response.message.content)
Chatbot odpowiada; agent podejmuje działania
Gdy przechodzisz przez poszczególne etapy, na których chatbot udziela odpowiedzi, najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Zapisz czas trwania oraz koszt tokenów lub zapytań obok wyników funkcjonalnych. Wczesna widoczność kosztów zapobiega niespodziewanym rachunkom, gdy przechodzi się z środowiska demonstracyjnego do współdzielonych środowisk. Zapisz ID żądania, ID modelu oraz opóźnienie przy każdym wywołaniu. Bez takiego śladu przerywane błędy dostawcy wyglądają jak błędy aplikacji.
Zacznij od prostych narzędzi
Gdy przechodzisz przez etap „Zacznij od ograniczonych narzędzi”, najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Trzymaj konfigurację poza kodem aplikacji. Pliki środowiskowe, magazyny tajnych danych oraz flagi funkcjonalne powinny znajdować się w jednym miejscu, które operatorzy mogą sprawdzić bez konieczności czytania całej struktury. Zapisuj ID żądania, ID modelu oraz opóźnienie przy każdej próbie połączenia. Bez tych informacji przerywane błędy dostawcy wyglądają jak błędy aplikacji. Gdy przechodzisz przez etap „Zacznij od ograniczonych narzędzi”, najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Wolij małe, testowalne jednostki nad rozbudowane skrypty. Gdy jakiś krok się nie powiedzie, błąd powinien wskazywać na konkretną odpowiedzialność, a nie na skomplikowaną sekwencję operacji.
CUSTOMERS = {
"acme plumbing": {
"customer_id": "cus_1042",
"name": "Acme Plumbing",
"email": "billing@example.com",
}
}
INVOICES = [
{
"invoice_id": "INV-2048",
"customer_id": "cus_1042",
"amount": 1850.00,
"days_overdue": 18,
}
]
def find_customer(name: str) -> dict:
customer = CUSTOMERS.get(name.strip().lower())
return customer or {"error": "customer_not_found"}
def get_overdue_invoices(customer_id: str) -> dict:
matches = [
invoice
for invoice in INVOICES
if invoice["customer_id"] == customer_id
and invoice["days_overdue"] > 0
]
return {"invoices": matches, "count": len(matches)}
Daj modelowi narzędzia, a nie wyimaginowany dostęp
Etap dostarczania modelowi narzędzi działa najlepiej, gdy traktuje się go jako mierzalną powierzchnię. Zapisz jeden idealny przykład działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia zmian przed rozszerzaniem zakresu. Traktuj ten etap jako umowę pomiędzy danymi wejściowymi a zweryfikowanymi wynikami. Nazwij poszczególne elementy, zdefiniuj kryteria sukcesu i odrzuć ciche, częściowe ukończenie zadań. Ustal stałą wersję interpretera oraz plik blokujący zależności przed nauczeniem pętli. Rozbieżności pomiędzy laptopem a środowiskiem CI są najczęstszą przyczyną cichych awarii w demonstracjach API.
import json
from ollama import chat
def find_customer(name: str) -> dict:
"""Find a customer by business name and return its verified record."""
customer = CUSTOMERS.get(name.strip().lower())
return customer or {"error": "customer_not_found"}
def get_overdue_invoices(customer_id: str) -> dict:
"""Return overdue invoices for a verified customer ID."""
matches = [
invoice
for invoice in INVOICES
if invoice["customer_id"] == customer_id
and invoice["days_overdue"] > 0
]
return {"invoices": matches, "count": len(matches)}
TOOLS = {
"find_customer": find_customer,
"get_overdue_invoices": get_overdue_invoices,
}
Buduj pętlę agenta
Etap budowy pętli agenta działa najlepiej, gdy traktuje się go jako mierzalną powierzchnię do analizy. Zapisz jeden idealny przykład działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia działań, zanim rozszerzysz zakres pracy. Zapisuj czasy wykonywania oraz koszt tokenów lub zapytań obok wyników funkcjonalnych. Wczesna widoczność kosztów zapobiega niespodziewanym rachunkom, gdy przechodzi się z środowiska demonstracyjnego do współdzielonych środowisk. Ustal stałe wartości interpretera oraz pliku blokującego zależności przed nauczeniem pętli. Różnice między laptopem a środowiskiem CI są najczęstszą przyczyną ukrytych awarii w demonstracjach API.
SYSTEM_PROMPT = """
You are an invoice assistant.
Rules:
- Never invent a customer, invoice, email address, balance, or date.
- Use find_customer before requesting invoices.
- Only use customer IDs returned by tools.
- If a tool returns an error or no records, explain that clearly.
- You may draft communication, but you cannot send it.
"""
def run_agent(user_request: str) -> str:
messages = [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": user_request},
] for _ in range(6):
response = chat(
model="qwen3",
messages=messages,
tools=list(TOOLS.values()),
) messages.append(response.message) if not response.message.tool_calls:
return response.message.content for call in response.message.tool_calls:
name = call.function.name
arguments = call.function.arguments if name not in TOOLS:
result = {"error": "tool_not_allowed"}
else:
try:
result = TOOLS[name](**arguments)
except (TypeError, ValueError) as error:
result = {
"error": "invalid_tool_arguments",
"detail": str(error),
} messages.append(
{
"role": "tool",
"tool_name": name,
"content": json.dumps(result),
}
) return "I stopped because the task exceeded the maximum number of steps."
Prawdziwym rozwiązaniem nie był lepszy prompt
Prawdziwym rozwiązaniem jest traktowanie etapów w procesie jako mierzalnych obszarów. Zapisz jeden idealny zapis działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia zmian przed rozszerzaniem zakresu. Trzymaj konfigurację poza kodem aplikacji. Pliki środowiskowe, magazyny tajnych danych oraz flagi funkcjonalne powinny znajdować się w jednym miejscu, które operatorzy mogą sprawdzić bez konieczności czytania całej struktury. Ustal stałe wartości interpretera oraz pliku blokującego zależności przed uruchomieniem pętli. Różnice między laptopem a środowiskiem CI to najczęstsza przyczyna ukrytych awarii w demonstracjach API. Prawdziwym rozwiązaniem jest traktowanie etapów w procesie jako mierzalnych obszarów. Zapisz jeden idealny zapis działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia zmian przed rozszerzaniem zakresu. Wolij małe, testowalne jednostki nad rozbudowane skrypty. Gdy jakiś krok zawiedzie, awaria powinna wskazywać na konkretną odpowiedzialność, a nie na skomplikowaną sekwencję operacji.
Dodaj ustrukturyzowany wynik na granicy
Aby dodać ustrukturyzowany wynik na tym etapie, należy zdefiniować dane wejściowe, osobę odpowiedzialną za daną czynność oraz kryteria zakończenia przed modyfikacją kodu. Operatorzy powinni móc ponownie uruchomić tę czynność od znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Traktuj ten etap jako umowę pomiędzy danymi wejściowymi a zweryfikowanymi wynikami. Nadaj nazwy artefaktom, zdefiniuj sprawdzenia sukcesu i odrzuć ciche, częściowe ukończenie zadania. Oddziel konstrukcję klienta od pętli komunikatów, aby można było zmieniać dostawców bez konieczności przepisywania maszyny stanu rozmowy.
from pydantic import BaseModel, Field
class ReminderReview(BaseModel):
customer_name: str
invoice_ids: list[str]
total_due: float = Field(ge=0)
draft_subject: str
draft_body: str
requires_approval: bool = True
review_response = chat(
model="qwen3",
messages=messages,
format=ReminderReview.model_json_schema(),
)
review = ReminderReview.model_validate_json(
review_response.message.content
)
Stan i pamięć to różne rzeczy
Dla państwa oraz pamięci stan jest sceną – należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed modyfikacją kodu. Operatorzy powinni móc ponownie uruchomić dany krok na podstawie znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Należy rejestrować czasy wykonywania oraz koszt tokenów lub zapytań obok wyników funkcjonalnych. Wczesna widoczność kosztów zapobiega niespodziewanym rachunkom, gdy ścieżka przechodzi z środowiska demonstracyjnego do współdzielonych środowisk. Należy oddzielić budowanie klienta od pętli komunikatów, aby można było zmieniać dostawców bez konieczności przepisywania maszyny stanu rozmowy.
task_state = {
"customer_id": "cus_1042",
"verified_invoice_ids": ["INV-2048"],
"approved_actions": [],
}
Lokalne nie oznacza automatycznie bezpiecznego
Dla For the Local, które nie przeprowadza automatycznie przygotowania środowiska, należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed modyfikacją kodu. Operatorzy powinni móc ponownie uruchomić dany krok od znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Konfigurację należy przechowywać poza kodem aplikacji. Pliki środowiskowe, magazyny tajnych danych oraz flagi funkcjonalne powinny znajdować się w jednym miejscu, które operatorzy mogą sprawdzić bez konieczności czytania całej struktury. Należy oddzielić budowę klienta od pętli przekazywania wiadomości, aby można było wymieniać dostawców bez konieczności przepisywania maszyny stanu rozmowy. Dla For the Local, które nie przeprowadza automatycznie przygotowania środowiska, należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed modyfikacją kodu. Operatorzy powinni móc ponownie uruchomić dany krok od znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Należy preferować małe, testowalne jednostki nad rozbudowanymi skryptami. Gdy dany krok zawiedzie, powinno to wskazywać na konkretną przyczynę, a nie na całą grupę problemów.
przepływ danych.Jak testować agenta
Gdy przechodzisz przez etap „Jak testować”, najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Traktuj ten etap jako umowę pomiędzy danymi wejściowymi a zweryfikowanymi wynikami. Nadaj nazwy artefaktom, zdefiniuj sprawdzenia sukcesu i odrzuć ciche, częściowe ukończenie zadania. Zapisuj ID żądania, ID modelu oraz opóźnienie przy każdej wywołaniu. Bez tych informacji przerywane błędy dostawcy wyglądają jak błędy aplikacji.
Jak wyglądała działająca wersja
Gdy przechodzisz przez etap „Jaka jest wersja robocza”, najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Obok wyników funkcjonalnych zapisz czas trwania oraz koszt tokena lub zapytania. Wczesna widoczność kosztów zapobiega niespodziewanym rachunkom, gdy ścieżka przechodzi z wersji demonstracyjnej do środowisk współdzielonych. Zapisuj ID żądania, ID modelu oraz opóźnienie przy każdym wywołaniu. Bez tych informacji przerywane błędy dostawcy wyglądają jak błędy aplikacji.
User request
→ find_customer(name="Acme Plumbing")
→ verified customer_id: cus_1042
→ get_overdue_invoices(customer_id="cus_1042")
→ verified invoice: INV-2048, $1,850, 18 days overdue
→ generate draft
→ wait for human approval
Ostatnia lekcja
Gdy przechodzisz przez ostatni etap lekcji, najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Trzymaj konfigurację poza kodem aplikacji. Pliki środowiskowe, magazyny tajnych danych oraz flagi funkcjonalne powinny znajdować się w jednym miejscu, które operatorzy mogą sprawdzić bez konieczności czytania całej struktury. Zapisuj ID żądania, ID modelu oraz opóźnienie przy każdym wywołaniu. Bez tych informacji przerywane błędy dostawcy wyglądają jak błędy aplikacji. Gdy przechodzisz przez ostatni etap lekcji, najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Wolij małe, testowalne jednostki nad rozbudowane skrypty. Gdy jakiś krok się nie powiedzie, błąd powinien wskazywać na konkretną odpowiedzialność, a nie na skomplikowaną sekwencję działań.
Faza listy kontrolnej operacyjnej działa najlepiej, gdy traktuje się ją jako mierzalną powierzchnię. Zapisz jeden idealny przepis działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia działań, zanim rozszerzysz zakres.
Zdokumentuj zarówno prawidłowy przebieg operacji, jak i ścieżkę przywracania stanu. Próby ponownych działań, kontrolne punkty ludzkie oraz obsługa wiadomości nieodebranych stanowią część produktu, a nie elementy dodawane później.
Zabezpiecz interpreter oraz plik blokujący zależności przed rozpoczęciem wykonywania pętli. Różnice między laptopem a środowiskiem CI są najczęstszą przyczyną ukrytych awarii w demonstracjach API.
Zamiast swobodnego tekstu, preferuj strukturyzowane wyniki z walidacją schematu, gdy następnym krokiem jest kod lub wywołanie narzędzia.
Ustaw punkt kontrolny po kosztownych krokach. System kontynuacji nie powinien ponownie naliczać opłat za to samo wywołanie LLM, gdy operator próbuje ponownie uruchomić późniejszy węzeł.
Zabezpiecz wersje zależności i zapisz hash obrazu, który służył do uruchomienia demonstracji. Reprodukowalność jest ważniejsza od wiedzy przekazywanej ustnie.
Zanim uruchomisz cały stack, zamroź wersje, utwórz dokładny zapis dla kluczowych etapów i potwierdź kroki odwracania zmian. Środowiska współdzielone wymagają ograniczeń szybkości, weryfikacji użytkowników oraz wyraźnego właściciela odpowiedzialnego za rotację haseł. Wolimy nudną niezawodność od sprytnych, jednorazowych demonstracji.
Uwaga dotycząca a5f763eecd03: trzymaj klucze dostawcy poza repozytorium, ustaw ograniczenie liczby tokenów na sesję i przechowuj zapisy obok plików testowych, aby późniejsze zmiany modeli pozostawały porównywalne.