366 lines
11 KiB
Markdown
366 lines
11 KiB
Markdown
# AI Agent Person Processor ⚡
|
||
|
||
Zaawansowana aplikacja w Go oparta o Clean Architecture, która wykorzystuje **ZOPTYMALIZOWANY** proces z LLM Function Calling + równoległymi obliczeniami do inteligentnego przetwarzania danych osób i generowania raportów lokalizacyjnych.
|
||
|
||
## 🚀 Kluczowa optymalizacja:
|
||
- **Tylko 2 wywołania LLM na osobę** (poprzednio: 30-50)
|
||
- **Równoległe obliczenia odległości** (wszystkie elektrownie jednocześnie)
|
||
- **20x szybsze** przetwarzanie
|
||
- **Wielowątkowe goroutines** w Go
|
||
|
||
## Funkcjonalność
|
||
|
||
### Zoptymalizowany proces przetwarzania:
|
||
|
||
**Dla każdej osoby sekwencyjnie - TYLKO 2 WYWOŁANIA LLM:**
|
||
|
||
1. **[LLM Call 1/2] Pobierz lokalizacje osoby** (function calling: `get_location`)
|
||
- Zwraca **WSZYSTKIE** możliwe lokalizacje osoby (np. 7, 12, 17 lokalizacji)
|
||
|
||
2. **[Lokalne obliczenia RÓWNOLEGŁE] Znajdź globalnie najbliższą elektrownię**
|
||
- **Dla KAŻDEJ lokalizacji osoby** (pętla):
|
||
- Dla KAŻDEJ elektrowni uruchamia oddzielną goroutine
|
||
- Każda goroutine pobiera współrzędne z geocoding cache
|
||
- Równolegle oblicza odległość Haversine (bez LLM!)
|
||
- Znajduje najbliższą elektrownię dla tej konkretnej lokalizacji
|
||
- **Wybiera MINIMUM ze wszystkich lokalizacji×elektrowni**
|
||
- **Wielowątkowe przetwarzanie** - wszystkie elektrownie jednocześnie
|
||
|
||
3. **[LLM Call 2/2] Pobierz poziom dostępu** (function calling: `get_access_level`)
|
||
- Używa tylko roku urodzenia (integer)
|
||
|
||
4. **Generuje raport dla osoby**
|
||
- Zapisuje w `output/person_reports/{Name}_{Surname}.json`
|
||
- Zawiera: nazwę elektrowni, kod, odległość, poziom dostępu, współrzędne
|
||
|
||
**Po przetworzeniu wszystkich osób:**
|
||
|
||
- Znajduje osobę z **najmniejszą odległością** do jej najbliższej elektrowni
|
||
- Generuje `output/final_answer.json` do weryfikacji
|
||
|
||
## Architektura
|
||
|
||
```
|
||
.
|
||
├── cmd/app/ # Punkt wejścia
|
||
├── internal/
|
||
│ ├── config/ # Konfiguracja
|
||
│ ├── domain/ # Modele i interfejsy
|
||
│ │ ├── person.go # Model osoby
|
||
│ │ ├── location.go # Haversine i obliczenia odległości
|
||
│ │ ├── report.go # Modele raportów
|
||
│ │ ├── llm.go # LLM z function calling
|
||
│ │ └── tools.go # Definicje narzędzi dla agenta
|
||
│ ├── infrastructure/
|
||
│ │ ├── api/ # Klient API hub.ag3nts.org
|
||
│ │ ├── json/ # Repozytoria JSON
|
||
│ │ └── llm/ # Providery LLM
|
||
│ └── usecase/
|
||
│ └── person_agent_processor.go # Logika przetwarzania osób przez agenta
|
||
├── output/
|
||
│ ├── locations/ # Dane lokalizacji osób (z API)
|
||
│ ├── accesslevel/ # Dane poziomów dostępu (z API)
|
||
│ ├── person_reports/ # Raport dla każdej osoby
|
||
│ ├── findhim_locations.json # Lista elektrowni z kodami
|
||
│ └── final_answer.json # Końcowa odpowiedź do weryfikacji
|
||
└── lista.json # Dane wejściowe
|
||
```
|
||
|
||
## Przykładowy raport osoby
|
||
|
||
Raport dla osoby (`output/person_reports/Oskar_Sieradzki.json`):
|
||
|
||
```json
|
||
{
|
||
"name": "Oskar",
|
||
"surname": "Sieradzki",
|
||
"nearest_plant": "Grudziądz",
|
||
"plant_code": "PWR7264PL",
|
||
"distance_km": 83.38,
|
||
"access_level": 7,
|
||
"primary_latitude": 53.483,
|
||
"primary_longitude": 18.754
|
||
}
|
||
```
|
||
|
||
Każdy raport zawiera najbliższą elektrownię dla danej osoby wraz z odległością i kodem elektrowni.
|
||
|
||
## Optymalizacje
|
||
|
||
### ⚡ EKSTREMALNA OPTYMALIZACJA - 2 WYWOŁANIA LLM NA OSOBĘ:
|
||
|
||
**Poprzednia wersja:** 30-50 iteracji LLM na osobę
|
||
**Nowa wersja:** **TYLKO 2 wywołania LLM na osobę** ✨
|
||
|
||
### Jak to działa:
|
||
|
||
**FAZA INICJALIZACJI (jednokrotnie przy starcie):**
|
||
- Pobiera listę elektrowni z API
|
||
- **Geocoding API (OpenStreetMap Nominatim):** dla każdej elektrowni pobiera dokładne współrzędne
|
||
- Zapisuje współrzędne w pamięci cache
|
||
- Fallback do `CityCoordinates` jeśli API nie odpowiada
|
||
|
||
**FAZA PRZETWARZANIA (dla każdej osoby):**
|
||
1. **LLM Call 1:** `get_location` - pobiera **WSZYSTKIE** lokalizacje osoby (function calling)
|
||
2. **Lokalne obliczenia (RÓWNOLEGŁE dla WSZYSTKICH lokalizacji):**
|
||
- **Dla KAŻDEJ lokalizacji osoby:**
|
||
- Dla każdej elektrowni: osobna goroutine
|
||
- Pobiera współrzędne z cache (dokładne z geocoding API!)
|
||
- Oblicza odległość Haversine
|
||
- Znajduje najbliższą elektrownię dla tej lokalizacji
|
||
- **Wybiera GLOBALNIE najmniejszą odległość** ze wszystkich kombinacji
|
||
- Wszystko dzieje się **jednocześnie** (wielowątkowo)
|
||
- **BEZ użycia LLM!**
|
||
3. **LLM Call 2:** `get_access_level` - pobiera poziom dostępu (function calling)
|
||
|
||
### Kluczowe ulepszenia:
|
||
- **Redukcja wywołań LLM z ~40 do 2** (20x szybciej! 💨)
|
||
- **Równoległe przetwarzanie** - wszystkie elektrownie jednocześnie
|
||
- **Dokładne współrzędne** - OpenStreetMap Nominatim API (geocoding)
|
||
- **Geocoding przy starcie** - jednokrotne pobranie współrzędnych wszystkich elektrowni
|
||
- **Haversine bez LLM** - lokalne obliczenia w Go
|
||
- **Fallback coordinates** - jeśli geocoding API zawiedzie, używamy CityCoordinates
|
||
- Temperature = 0.0 dla deterministycznych wyników
|
||
|
||
### Zalety tego podejścia:
|
||
- ⚡ **Dramatycznie szybsze** - 20x mniej wywołań LLM
|
||
- 💰 **Tańsze** - minimalna konsumpcja API
|
||
- 🚀 **Równoległe obliczenia** - wykorzystanie wszystkich rdzeni CPU
|
||
- 🎯 **Deterministyczne** - te same wyniki za każdym razem
|
||
- 📊 **Szczegółowe logi** - każdy krok widoczny
|
||
- 🔧 **Łatwe debugowanie** - jasna struktura 2-fazowa
|
||
|
||
### System logowania:
|
||
Aplikacja generuje bardzo szczegółowe logi pokazujące:
|
||
- **Fazę ładowania danych** (osoby, elektrownie)
|
||
- **Dla każdej osoby:**
|
||
- `[LLM Call 1/2]` - wywołanie get_location
|
||
- `[Local Processing]` - równoległe goroutines dla każdej elektrowni
|
||
- Wyniki z każdej goroutine: `Goroutine [Zabrze]: 123.45 km`
|
||
- `[LLM Call 2/2]` - wywołanie get_access_level
|
||
- Finalne wyniki z podsumowaniem `Total LLM calls: 2`
|
||
- **Fazę znajdowania minimalnej odległości**
|
||
- **Ostateczny wynik** z instrukcją weryfikacji
|
||
|
||
## Konfiguracja
|
||
|
||
```json
|
||
{
|
||
"api_key": "your-api-key",
|
||
"input_file": "lista.json",
|
||
"output_dir": "output",
|
||
"locations_api": "https://hub.ag3nts.org/api/location",
|
||
"access_level_api": "https://hub.ag3nts.org/api/accesslevel",
|
||
"locations_url": "https://hub.ag3nts.org",
|
||
"llm": {
|
||
"provider": "lmstudio",
|
||
"model": "bielik-11b-v3.0-instruct-gptq-marlin@q8_0",
|
||
"base_url": "http://localhost:1234"
|
||
}
|
||
}
|
||
```
|
||
|
||
### Providery LLM
|
||
|
||
#### LM Studio (lokalny)
|
||
```json
|
||
"llm": {
|
||
"provider": "lmstudio",
|
||
"model": "bielik-11b-v3.0-instruct-gptq-marlin@q8_0",
|
||
"base_url": "http://localhost:1234"
|
||
}
|
||
```
|
||
|
||
#### OpenRouter (API)
|
||
```json
|
||
"llm": {
|
||
"provider": "openrouter",
|
||
"model": "anthropic/claude-3.5-sonnet",
|
||
"api_key": "your-openrouter-api-key"
|
||
}
|
||
```
|
||
|
||
**Uwaga**: Model Bielik jest zalecany dla function calling - działa szybciej i stabilniej niż DeepSeek R1.
|
||
|
||
## Budowanie i uruchomienie
|
||
|
||
```bash
|
||
# Budowanie
|
||
go build -o person-processor ./cmd/app
|
||
|
||
# Uruchomienie
|
||
./person-processor -config config.json
|
||
```
|
||
|
||
## Weryfikacja odpowiedzi
|
||
|
||
Po zakończeniu przetwarzania, aplikacja zapisuje końcową odpowiedź w pliku `output/final_answer.json`.
|
||
|
||
Aby wysłać odpowiedź do weryfikacji:
|
||
|
||
```bash
|
||
curl -X POST https://hub.ag3nts.org/verify \
|
||
-H "Content-Type: application/json" \
|
||
-d @output/final_answer.json
|
||
```
|
||
|
||
Lub z formatowaniem odpowiedzi:
|
||
|
||
```bash
|
||
curl -X POST https://hub.ag3nts.org/verify \
|
||
-H "Content-Type: application/json" \
|
||
-d @output/final_answer.json | jq .
|
||
```
|
||
|
||
Format odpowiedzi (`output/final_answer.json`):
|
||
|
||
```json
|
||
{
|
||
"apikey": "your-api-key",
|
||
"task": "findhim",
|
||
"answer": {
|
||
"name": "Imię",
|
||
"surname": "Nazwisko",
|
||
"accessLevel": 7,
|
||
"powerPlant": "PWR1234PL"
|
||
}
|
||
}
|
||
```
|
||
|
||
## Wzór Haversine
|
||
|
||
Aplikacja używa wzoru Haversine do obliczania odległości między dwoma punktami na kuli ziemskiej:
|
||
|
||
```
|
||
a = sin²(Δlat/2) + cos(lat1) × cos(lat2) × sin²(Δlon/2)
|
||
c = 2 × atan2(√a, √(1−a))
|
||
distance = R × c
|
||
```
|
||
|
||
gdzie R = 6371 km (promień Ziemi)
|
||
|
||
## Narzędzia agenta LLM i lokalne funkcje
|
||
|
||
### 🤖 LLM Function Calling (2 wywołania na osobę):
|
||
|
||
#### get_location
|
||
Pobiera wszystkie możliwe lokalizacje osoby z API.
|
||
|
||
**Parametry:**
|
||
- `name` (string) - imię osoby
|
||
- `surname` (string) - nazwisko osoby
|
||
|
||
**Zwraca:** tablicę obiektów z `latitude` i `longitude`
|
||
|
||
**Logi:** `→ API call: get_location(Imię, Nazwisko)`
|
||
|
||
**Użycie:** LLM Call 1/2
|
||
|
||
#### get_access_level
|
||
Pobiera poziom dostępu osoby z API.
|
||
|
||
**Parametry:**
|
||
- `name` (string) - imię osoby
|
||
- `surname` (string) - nazwisko osoby
|
||
- `birth_year` (integer) - **tylko rok** urodzenia (np. 1987, NIE pełna data)
|
||
|
||
**Zwraca:** obiekt z `accessLevel` (integer)
|
||
|
||
**Logi:** `→ API call: get_access_level(Imię, Nazwisko, rok)`
|
||
|
||
**Użycie:** LLM Call 2/2
|
||
|
||
#### find_nearest_point
|
||
Znajduje najbliższy punkt z listy do punktu referencyjnego (np. elektrownia).
|
||
|
||
**Parametry:**
|
||
- `reference_lat` (float) - szerokość geograficzna punktu referencyjnego
|
||
- `reference_lon` (float) - długość geograficzna punktu referencyjnego
|
||
- `points` (array) - tablica punktów do sprawdzenia, każdy punkt: `[latitude, longitude]`
|
||
|
||
**Zwraca:** obiekt z `latitude`, `longitude`, `distance_km`, `index` (indeks najbliższego punktu)
|
||
|
||
**Przykład wywołania:**
|
||
```json
|
||
{
|
||
"reference_lat": 50.3086,
|
||
"reference_lon": 18.7864,
|
||
"points": [
|
||
[52.2297, 21.0122],
|
||
[51.1079, 17.0385],
|
||
[50.0647, 19.9450]
|
||
]
|
||
}
|
||
```
|
||
|
||
**Zwraca:**
|
||
```json
|
||
{
|
||
"latitude": 50.0647,
|
||
"longitude": 19.9450,
|
||
"distance_km": 45.23,
|
||
"index": 2
|
||
}
|
||
```
|
||
|
||
**Logi:** `→ Tool call: find_nearest_point(ref: 50.3086,18.7864, 3 points)`
|
||
|
||
**Użycie:** Opcjonalna - dostępna dla LLM jeśli zdecyduje się jej użyć
|
||
|
||
---
|
||
|
||
### ⚡ Lokalne funkcje (bez LLM - wielowątkowe):
|
||
|
||
#### GetPlantGeolocation (Geocoding API)
|
||
Pobiera dokładne współrzędne geograficzne elektrowni z OpenStreetMap Nominatim API.
|
||
|
||
**API:** `https://nominatim.openstreetmap.org/search`
|
||
|
||
**Parametry:**
|
||
- `city` - nazwa miasta
|
||
- `country` - Poland
|
||
- `format` - json
|
||
- `limit` - 1
|
||
|
||
**Zwraca:** Dokładne współrzędne `{lat, lon}` (6 miejsc po przecinku!)
|
||
|
||
**Użycie:** Jednokrotnie przy starcie aplikacji (cache w pamięci)
|
||
|
||
**Logi:**
|
||
```
|
||
→ Fetching accurate geolocations from geocoding API...
|
||
• Geocoding: Zabrze...
|
||
✓ Fetched: 50.308615°N, 18.786375°E
|
||
• Geocoding: Grudziądz...
|
||
✓ Fetched: 53.483624°N, 18.753536°E
|
||
```
|
||
|
||
#### CityCoordinates (baza fallback)
|
||
Hardcoded współrzędne wszystkich elektrowni w Polsce (fallback gdy API nie odpowiada).
|
||
|
||
**Zawartość:**
|
||
- Zabrze, Piotrków Trybunalski, Grudziądz, Tczew, Radom, Chełmno, Żarnowiec
|
||
- Każde miasto: `{Lat: float64, Lon: float64}`
|
||
|
||
**Użycie:** Fallback gdy geocoding API zawiedzie
|
||
|
||
#### Haversine (wzór odległości)
|
||
Oblicza odległość między dwoma punktami na kuli ziemskiej.
|
||
|
||
**Parametry:**
|
||
- `lat1, lon1` - współrzędne punktu 1
|
||
- `lat2, lon2` - współrzędne punktu 2
|
||
|
||
**Zwraca:** odległość w kilometrach (float64)
|
||
|
||
**Użycie:** Równoległe goroutines dla każdej elektrowni (bez LLM!)
|
||
|
||
**Logi:** `• Goroutine [Nazwa]: 123.45 km`
|
||
|
||
|
||
## Wymagania
|
||
|
||
- Go 1.21+
|
||
- LM Studio lub klucz OpenRouter API
|
||
- Model LLM wspierający function calling (zalecany: Bielik 11B)
|