Lab 07: Python SDK - JSON Object
Opis
W tym laboratorium nauczymy się ekstrakcji strukturalnych danych z agenta używając JSON object mode. Będziemy budować kod małymi kawałkami testując różne formaty output i poznamy trick z override instructions.
Ważne: NIE tworzymy agenta w kodzie. Użyjemy istniejącego agenta z Lab 05 (KRS-Agent).
⚠️ Structured Output (Pydantic) nie jest wspierany
Azure AI Agents Service NIE wspiera natywnego Structured Output z Pydantic (
response_format=PydanticModel).Zamiast tego używamy JSON object mode (
response_format={"type": "json_object"}) z manual schema validation.Ta funkcja działa tylko z bezpośrednim OpenAI Chat Completions API, ale wtedy tracimy Agent Tools (KRS API, Bing Search).
Pełna dokumentacja:
Problem: Parsing free-form text
Typowe wyzwania
Agent zwraca odpowiedzi w różnych formatach:
Format 1: Markdown table
| Pole | Wartość |
|------|---------|
| Nazwa | ORLEN S.A. |
| KRS | 0000028860 |
Format 2: Plain text
Firma: ORLEN Spółka Akcyjna
Numer KRS: 0000028860
NIP: 7740001454
Format 3: Mixed format
Dane firmy KRS 0000028860:
**Nazwa:** ORLEN Spółka Akcyjna
**Adres:** Płock, ul. Chemików 7
Problem: Każdy format wymaga innego parsera! String splitting, manual extraction…
Use cases wymagające struktury
- ETL pipelines: Ekstrakcja danych z agenta → database/CSV
- Integracje: Agent → downstream system (API call z danymi)
- Batch processing: 100 firm z KRS → jedna tabela
- Automation: Przetwarzanie bez human review
Potrzeba: Gwarantowany JSON format (ale schema może się różnić).
Rozwiązanie: JSON object mode
Czym jest JSON object mode?
JSON object mode = MODEL zwraca poprawny JSON (ale NIE gwarantuje schema).
response_format={"type": "json_object"}
Co gwarantuje:
- ✅ Valid JSON (można parsować przez
json.loads()) - ✅ Działa z Agent Tools (KRS API, Bing Search)
- ❌ NIE gwarantuje schema (pola mogą się różnić)
Dwa warianty odpowiedzi:
- Markdown code block (częste):
{ "nazwa": "ORLEN S.A.", "krs": "0000028860" } - Goły JSON (jeśli wymusimy):
{"nazwa": "ORLEN S.A.", "krs": "0000028860"}
Dlaczego nie Structured Output (Pydantic)?
Azure AI Agents Service NIE wspiera response_format=PydanticModel:
- To wymaga bezpośredniego OpenAI Chat Completions API
- Ale wtedy tracimy Agent Tools (KRS API, Bing Search)
Trade-off: JSON object + manual validation = best compromise dla Agents.
Przygotowanie środowiska
Krok 1: Sprawdź Agent ID
Użyjemy istniejącego agenta z Lab 05 (KRS-Agent) lub Lab 07.
Przejdź do Azure AI Foundry Portal → Agents → KRS-Agent i skopiuj Agent ID.
Krok 2: Dane z Lab 07
Będziemy używać tych samych danych co w Lab 07:
PROJECT_ENDPOINT- z OverviewAGENT_ID- KRS-Agent- ChainedTokenCredential (Service Principal → Azure CLI)
Budowanie skryptu krok po kroku
Zbudujemy skrypt małymi kawałkami testując różne formaty output.
Krok 1: Utworzenie pliku i import
Otwórz Visual Studio Code (wpisz w terminalu w katalogu roboczym code .)
Utwórz nowy plik json_output.py w katalogu roboczym:
#!/usr/bin/env python3
"""
JSON object mode - porównanie formatów
"""
from azure.ai.projects import AIProjectClient
from azure.identity import ChainedTokenCredential, EnvironmentCredential, AzureCliCredential
import json
import re
print("✓ Importy załadowane")
Uruchom:
python json_output.py
Oczekiwany wynik:
✓ Importy załadowane
Co się stało: Sprawdziliśmy że pakiety są zainstalowane.
Krok 2: Utworzenie klienta i thread
Dodaj kod i zmodyfikuj kod:
- Zastąp wartości swoimi z Lab 07!
- Uzupełnij
TODOna bazie wiedzy z poprzedniego Labu
# Konfiguracja (zastąp swoimi wartościami!)
PROJECT_ENDPOINT = "YOUR_PROJECT_ENDPOINT_HERE" # Z Lab 07
AGENT_ID = "YOUR_AGENT_ID_HERE" # KRS-Agent ID
# Utworzenie klienta
print("\n🔧 Tworzenie klienta...")
credential = ChainedTokenCredential(
EnvironmentCredential(),
AzureCliCredential()
)
# (Opcjonalne) Jeżeli masz rozszyfrowywanie ruchu na proxy (SSL interception),
# możesz wyłączyć weryfikowanie certyfikatu:
# from azure.core.pipeline.transport import RequestsTransport
# transport = RequestsTransport(connection_verify=False)
project_client = AIProjectClient(
endpoint=PROJECT_ENDPOINT,
credential=credential
# transport=transport # Odkomentuj jeżeli używasz proxy
)
# Utworzenie thread
print("🔧 Tworzenie thread...")
thread = project_client. #TODO: Utwórz tu thread
print("✓ Klient i thread gotowe")
print(f" Thread ID: {thread.id}")
Zastąp wartości swoimi z Lab 07.
Uruchom:
python json_output.py
Oczekiwany wynik:
🔧 Tworzenie klienta...
🔧 Tworzenie thread...
✓ Klient i thread gotowe
Thread ID: thread_xyz789...
Krok 3: Test 1 - Markdown (baseline)
Dodaj kod:
# Test 1: Markdown (default format)
print("\n" + "=" * 60)
print("TEST 1: MARKDOWN (default)")
print("=" * 60)
# Dodaj wiadomość
message1 = project_client. # TODO: Stwórz wiadomość "Podaj dane firmy KRS 0000028860"
# Run bez response_format (default = markdown)
print("\n▶️ Uruchamianie agenta (markdown)...")
run1 = project_client.agents.runs. #TODO: Wyślij wiadomość do agenta. Jeżeli nie ustawisz property `response_format` odpowiedź będzie w Markdown. Na teraz nie ustawiamy nic.
# Odczyt odpowiedzi
messages1 = project_client.agents.messages. #TODO: Odczytaj odpowiedzi
for msg in messages1:
if msg.role == "assistant":
response1 = msg.content[0].text.value
break
print("\n✓ Odpowiedź agenta (markdown):")
print("-" * 60)
print(response1[:400]) # Pierwsze 400 znaków
print("-" * 60)
print(f"\n📊 Tokeny: {run1.usage.total_tokens if run1.usage else 'N/A'}")
Uruchom:
python json_output.py
Oczekiwany wynik:
==========================================================
TEST 1: MARKDOWN (default)
==========================================================
▶️ Uruchamianie agenta (markdown)...
✓ Odpowiedź agenta (markdown):
------------------------------------------------------------
Oto dane firmy z KRS 0000028860:
**Nazwa:** ORLEN Spółka Akcyjna
**Siedziba:** Płock, ul. Chemików 7, 09-411
**NIP:** 7740001454
...
------------------------------------------------------------
📊 Tokeny: 21417
Co się stało:
- Agent zwrócił naturalny format (markdown)
- To jest najłatwiejsze dla modelu
- Ale wymaga manual parsing (string splitting, regex)
Krok 4: Test 2 - JSON object (schema bez format enforcement)
Dodaj kod:
# Test 2: JSON object (schema bez format enforcement)
print("\n" + "=" * 60)
print("TEST 2: JSON OBJECT (schema, no format enforcement)")
print("=" * 60)
# Dodaj wiadomość
message2 = project_client.agents.messages.create(
thread_id=thread.id,
role="user",
content="Podaj dane firmy KRS 0000028860"
)
# Run z response_format + schema (ALE bez "no markdown"!)
print("\n▶️ Uruchamianie agenta (json + schema only)...")
run2 = project_client.agents.runs.create_and_process(
thread_id=thread.id,
agent_id=AGENT_ID,
# TODO:
# Ustaw pola/property:
# - response_format - na `json_object`. Pamiętaj, że format to `{"type": "WARTOŚĆ"}`
# - additional_instructions - Dodaj instrukcję (prompt) aby dane firmy były zwracane w formacie JSON z następującym schematem:
# {
# "nazwa": "company name",
# "krs": "KRS number",
# "nip": "tax ID",
# "regon": "REGON number",
# "adres": "full address as single string"
#}
# Pamiętaj o odpowiedzniej ilości `"` - Instrukcja powinna być w formacie """ INSTRUKCJA """
)
# Odczyt odpowiedzi
messages2 = project_client.agents.messages.list(thread_id=thread.id)
for msg in messages2:
if msg.role == "assistant" and msg.run_id == run2.id:
response2 = msg.content[0].text.value
break
print("\n✓ Odpowiedź agenta (json_object):")
print("-" * 60)
print("Raw response (pierwsze 300 znaków):")
print(response2[:300])
print("-" * 60)
# Parsing - problem: może być w markdown code block!
print("\n🔍 Próba parsowania:")
try:
# Próba 1: Direct parse
data2 = json.loads(response2)
print("✅ Direct parse SUCCESS")
print(json.dumps(data2, indent=2, ensure_ascii=False)[:300])
except json.JSONDecodeError as e:
print(f"❌ Direct parse FAILED: {e}")
# Próba 2: Strip markdown code block
print("\n🔧 Próba strip markdown...")
# Usuń ```json i ``` z początku/końca
json_match = re.search(r'```json\s*(\{.*\})\s*```', response2, re.DOTALL)
if json_match:
json_str = json_match.group(1)
try:
data2 = json.loads(json_str)
print("✓ After strip SUCCESS")
print(json.dumps(data2, indent=2, ensure_ascii=False)[:300])
except json.JSONDecodeError as e2:
print(f"✗ After strip FAILED: {e2}")
else:
print("✗ No JSON block found")
print(f"\n📊 Tokeny: {run2.usage.total_tokens if run2.usage else 'N/A'}")
Uruchom:
python json_output.py
Oczekiwany wynik:
==========================================================
TEST 2: JSON OBJECT (schema, no format enforcement)
==========================================================
▶️ Uruchamianie agenta (json + schema only)...
✓ Odpowiedź agenta (json_object):
------------------------------------------------------------
Raw response (pierwsze 300 znaków):
json
{
"nazwa": "ORLEN SPÓŁKA AKCYJNA",
"krs": "0000028860",
"nip": "7740001454",
"regon": "61018820100000",
"adres": "ul. Chemików 7, 09-411 Płock"
}
------------------------------------------------------------
🔍 Próba parsowania:
✗ Direct parse FAILED: Expecting value: line 1 column 1 (char 0)
🔧 Próba strip markdown...
✓ After strip SUCCESS
{
"nazwa": "ORLEN SPÓŁKA AKCYJNA",
"krs": "0000028860",
"nip": "7740001454",
"regon": "61018820100000",
"adres": "ul. Chemików 7, 09-411 Płock"
}
📊 Tokeny: 64758
Co się stało:
response_format={"type": "json_object"}wymusza JSONadditional_instructionszawiera schema definition (dokładne pola)- Problem: Model NADAL zwrócił JSON w markdown code block (```json)
- Direct
json.loads()FAILED (trzeba strip) - Ale: Schema jest spójny! Zawsze te same pola (nazwa, krs, nip, regon, adres)
Wnioski:
- Schema w instructions = spójne pole ✓
- Ale NIE wymusza czystego formatu (nadal ```json block)
- Potrzeba dodatkowego elementu: format enforcement!
Krok 5: Kluczowe odkrycie - potrzeba OBIE części!
Z Test 2 widzimy że sam schema nie wystarczy. Potrzebujemy dwóch elementów:
- Schema definition - jakie pola zwrócić (→ consistent fields)
- Format enforcement - jak je zwrócić (→ clean JSON bez markdown)
Kompletny wzorzec
Zobacz na kod poniżej i dodaj/zmień wywołanie które pisałeś w poprzednim kroku. Co się zmieniło?
run = project_client.agents.runs.create_and_process(
thread_id=thread.id,
agent_id=AGENT_ID,
additional_instructions="""Return ONLY raw JSON object with this exact schema:
{
"nazwa": "company name",
"krs": "KRS number",
"nip": "tax ID",
"adres": "full address"
}
No markdown, no code blocks, no explanations. Pure JSON only."""
)
Dwa elementy:
- ✅
with this exact schema: {...}- schema definition - ✅
No markdown, no code blocks, Pure JSON only- format enforcement
Co to jest additional_instructions?
additional_instructions = parameter w create_and_process() który:
- Nadpisuje/uzupełnia agent instructions tylko dla tego run
- Agent instructions (permanent) + additional_instructions (per-run)
Różnica: Agent instructions vs additional_instructions
| Agent instructions | additional_instructions | |
|---|---|---|
| Gdzie? | Portal: Edit agent → Instructions | Code: create_and_process() parameter |
| Scope | Permanent (wszystkie runs) | Per-run (tylko ten run) |
| Use case | Ogólne zachowanie agenta | Temporary overrides, format control |
Use cases dla additional_instructions:
- ✅ Temporary format changes (np. JSON vs markdown)
- ✅ Schema definition - dokładne pola do zwrócenia
- ✅ Różne schematy dla różnych zapytań
- ✅ A/B testing prompts
- ✅ Format enforcement (goły JSON vs markdown)
Tip: additional_instructions NIE zastępuje agent instructions - UZUPEŁNIA je. Agent widzi oba.
Best practice: Zawsze podawaj OBIE części w additional_instructions:
additional_instructions="""Return ONLY raw JSON with schema:
{
"field1": "description",
"field2": "description"
}
No markdown, no code blocks, no extra text. Pure JSON only."""
Dwa elementy = pseudo-structured output!
Krok 6: Test 3 - JSON object + schema + format enforcement
Dodaj kod:
# Test 3: JSON object + schema + format enforcement
print("\n" + "=" * 60)
print("TEST 3: JSON OBJECT + SCHEMA + FORMAT ENFORCEMENT")
print("=" * 60)
# Dodaj wiadomość
message3 = project_client.agents.messages.create(
thread_id=thread.id,
role="user",
content="Podaj dane firmy KRS 0000028860"
)
# Run z response_format + OBIE części: schema + format enforcement!
print("\n▶️ Uruchamianie agenta (json + schema + format)...")
run3 = project_client.agents.runs.create_and_process(
thread_id=thread.id,
agent_id=AGENT_ID,
response_format={"type": "json_object"},
additional_instructions="""Return ONLY raw JSON object with this exact schema:
{
"nazwa": "company name",
"krs": "KRS number",
"nip": "tax ID",
"regon": "REGON number",
"adres": "full address as single string"
}
No markdown, no code blocks, no explanations. Pure JSON only."""
)
# Odczyt odpowiedzi
messages3 = project_client.agents.messages.list(thread_id=thread.id)
for msg in messages3:
if msg.role == "assistant" and msg.run_id == run3.id:
response3 = msg.content[0].text.value
break
print("\n✓ Odpowiedź agenta (json + override):")
print("-" * 60)
print("Raw response (pierwsze 300 znaków):")
print(response3[:300])
print("-" * 60)
# Parsing - powinno działać od razu!
print("\n🔍 Parsowanie:")
try:
data3 = json.loads(response3)
print("✅ Direct parse SUCCESS (goły JSON!)")
print(json.dumps(data3, indent=2, ensure_ascii=False)[:300])
except json.JSONDecodeError as e:
print(f"❌ Parse FAILED: {e}")
print(f"\n📊 Tokeny: {run3.usage.total_tokens if run3.usage else 'N/A'}")
Uruchom:
python json_output.py
Oczekiwany wynik:
==========================================================
TEST 3: JSON OBJECT + SCHEMA + FORMAT ENFORCEMENT
==========================================================
▶️ Uruchamianie agenta (json + schema + format)...
✓ Odpowiedź agenta (json + schema + format):
------------------------------------------------------------
Raw response (pierwsze 300 znaków):
{"nazwa": "ORLEN SPÓŁKA AKCYJNA", "krs": "0000028860", "nip": "7740001454", "regon": "61018820100000", "adres": "ul. Chemików 7, 09-411 Płock"}
------------------------------------------------------------
🔍 Parsowanie:
✓ Direct parse SUCCESS (goły JSON z consistent schema!)
{
"nazwa": "ORLEN SPÓŁKA AKCYJNA",
"krs": "0000028860",
"nip": "7740001454",
"regon": "61018820100000",
"adres": "ul. Chemików 7, 09-411 Płock"
}
📊 Tokeny: 21650
Co się stało:
additional_instructionsz obiema częściami:- Schema definition → consistent fields (nazwa, krs, nip, regon, adres)
- Format enforcement → goły JSON (bez markdown code block)
- Direct
json.loads()działa od razu! - Zero strip logic potrzebnej
- Pseudo-structured output - prawie jak Pydantic!
Progresja testów:
- Test 1 (markdown): natural format, wymaga parsing
- Test 2 (schema only): consistent fields ✓, ale nadal ```json block ✗
- Test 3 (schema + format): consistent fields ✓, clean JSON ✓
Dlaczego to działa:
- Schema → model wie JAKIE pola zwrócić
- Format enforcement → model wie JAK je zwrócić (raw JSON)
- Obie części razem = pseudo-structured output!
Krok 7: Porównanie wszystkich testów
Dodaj kod na końcu:
# Porównanie wszystkich formatów
print("\n" + "=" * 60)
print("PORÓWNANIE FORMATÓW")
print("=" * 60)
print(f"""
Test | Tokeny | Direct parse | Schema consistent
-------------------------------|--------|--------------|-------------------
Test 1: Markdown | {run1.usage.total_tokens if run1.usage else 'N/A':6} | ✗ | ✗
Test 2: JSON (schema only) | {run2.usage.total_tokens if run2.usage else 'N/A':6} | ✗ | ✓
Test 3: JSON (schema + format) | {run3.usage.total_tokens if run3.usage else 'N/A':6} | ✓ | ✓
""")
print("\nKiedy używać:")
print(" ✓ Markdown: Human-readable output, exploratory")
print(" ✓ JSON (schema only): Prototyping, can handle markdown strip")
print(" ✓ JSON (schema + format): Production, batch processing, automation")
print("\nKluczowa lekcja:")
print(" Potrzebujesz OBIE części:")
print(" 1. Schema definition → consistent fields")
print(" 2. Format enforcement → clean JSON")
print(" Razem = pseudo-structured output!")
Uruchom:
python json_output.py
Oczekiwany wynik:
==========================================================
PORÓWNANIE FORMATÓW
==========================================================
Test | Tokeny | Direct parse | Schema consistent
-------------------------------|--------|--------------|-------------------
Test 1: Markdown | 21417 | ✗ | ✗
Test 2: JSON (schema only) | 64758 | ✗ | ✓
Test 3: JSON (schema + format) | 21650 | ✓ | ✓
Kiedy używać:
✓ Markdown: Human-readable output, exploratory
✓ JSON (schema only): Prototyping, can handle markdown strip
✓ JSON (schema + format): Production, batch processing, automation
Kluczowa lekcja:
Potrzebujesz OBIE części:
1. Schema definition → consistent fields
2. Format enforcement → clean JSON
Razem = pseudo-structured output!
Co się stało:
- Porównaliśmy 3 podejścia w progresji
- Test 2 pokazuje że sama schema daje consistent fields, ale NIE clean format
- Test 3 pokazuje że potrzeba OBIE części dla idealnego wyniku
- Kluczowe odkrycie: Schema + format enforcement = pseudo-structured output!
Progresja:
- Test 1: Ani schema, ani format → chaos
- Test 2: Schema, bez format enforcement → consistent fields, ale ```json block
- Test 3: Schema + format enforcement → consistent fields + clean JSON
Trade-off:
- Test 3 może używać więcej tokenów (model reasoning)
- Ale daje najlepszy developer experience (zero parsing tricks)
- Cost vs reliability
Manual schema validation
Problem: Schema nie jest gwarantowany
JSON object mode gwarantuje valid JSON ale NIE gwarantuje schema:
# Model może zwrócić różne pola:
# Run 1: {"nazwa": "...", "krs": "...", "nip": "..."}
# Run 2: {"company_name": "...", "krs_number": "..."} # Inne nazwy pól!
# Run 3: {"nazwa": "...", "krs": "...", "adres": {...}} # Nested object!
Trzeba manual validation:
try:
data = json.loads(response)
# Check required fields
# (jeśli podałeś schema w additional_instructions, pola powinny być consistent!)
if "nazwa" not in data:
print("Error: Missing 'nazwa' field")
return None
if "krs" not in data:
print("Error: Missing 'krs' field")
return None
# Safe access
nazwa = data["nazwa"]
krs = data["krs"]
nip = data.get("nip", "N/A") # Optional field - default "N/A"
return {"nazwa": nazwa, "krs": krs, "nip": nip}
except json.JSONDecodeError as e:
print(f"JSON parsing error: {e}")
return None
except KeyError as e:
print(f"Missing field: {e}")
return None
Best practice z schema w instructions:
Jeśli podałeś schema w additional_instructions, validation jest prostsza:
# Schema był: {"nazwa": "...", "krs": "...", "nip": "...", "adres": "..."}
data = json.loads(response)
# Teraz te pola POWINNY być zawsze (model dostał exact schema)
return {
"nazwa": data["nazwa"], # Mniej defensive - model wie co robić
"krs": data["krs"],
"nip": data.get("nip", "N/A"), # Still optional dla safety
"adres": data.get("adres", "N/A")
}
Best practices:
- ✅ Always check required fields
- ✅ Use
.get()for optional fields z default value - ✅ Try/except dla KeyError i JSONDecodeError
- ✅ Return structured dict or None (not raw data)
Praktyka: Batch processing z validation
Zadanie: Ekstrakcja danych z 3 firm
Zmodyfikuj skrypt aby pobrać dane z 3 różnych firm KRS z validation.
Cel:
- Loop przez listę numerów KRS
- Dla każdej firmy: message → run z JSON object + override → validate → collect
- Error handling per firma
Numery KRS do przetestowania:
0000028860- ORLEN S.A.0000033001- PKN ORLEN S.A.0000127767- PZU S.A.
Rozwiązanie (kliknij aby zobaczyć)
```python #!/usr/bin/env python3 """ Batch processing KRS z JSON object + validation """ from azure.ai.projects import AIProjectClient from azure.identity import ChainedTokenCredential, EnvironmentCredential, AzureCliCredential import json # Konfiguracja (dane z Lab 07) PROJECT_ENDPOINT = "YOUR_PROJECT_ENDPOINT_HERE" AGENT_ID = "YOUR_AGENT_ID_HERE" # Klient credential = ChainedTokenCredential( EnvironmentCredential(), AzureCliCredential() ) # (Opcjonalne) Jeżeli masz rozszyfrowywanie ruchu na proxy (SSL interception), # możesz wyłączyć weryfikowanie certyfikatu: # from azure.core.pipeline.transport import RequestsTransport # transport = RequestsTransport(connection_verify=False) project_client = AIProjectClient( endpoint=PROJECT_ENDPOINT, credential=credential # transport=transport # Odkomentuj jeżeli używasz proxy ) # Thread thread = project_client.agents.threads.create() # Validation function def validate_company_data(response: str) -> dict | None: """Validate and extract company data from JSON response""" try: data = json.loads(response) # Required fields if "nazwa" not in data: print(" ✗ Missing 'nazwa' field") return None if "krs" not in data: print(" ✗ Missing 'krs' field") return None # Extract data (optional fields with defaults) return { "nazwa": data["nazwa"], "krs": data["krs"], "nip": data.get("nip", "N/A"), "adres": data.get("adres", "N/A") } except json.JSONDecodeError as e: print(f" ✗ JSON parse error: {e}") return None # Lista firm do pobrania krs_numbers = ["0000028860", "0000033001", "0000127767"] companies = [] print("🔄 Batch processing...") print(f"Pobieram dane dla {len(krs_numbers)} firm\n") # Loop przez firmy for krs_num in krs_numbers: print(f"▶️ Pobieram KRS {krs_num}...") # Message message = project_client.agents.messages.create( thread_id=thread.id, role="user", content=f"Podaj dane firmy KRS {krs_num}" ) # Run z JSON object + override try: run = project_client.agents.runs.create_and_process( thread_id=thread.id, agent_id=AGENT_ID, response_format={"type": "json_object"}, additional_instructions="""Return ONLY raw JSON object with this exact schema: { "nazwa": "company name", "krs": "KRS number", "nip": "tax ID", "adres": "full address" } No markdown, no code blocks, no explanations. Pure JSON only.""" ) # Get response if run.status == "completed": messages = project_client.agents.messages.list(thread_id=thread.id) for msg in messages: if msg.role == "assistant" and msg.run_id == run.id: response = msg.content[0].text.value # Validate company = validate_company_data(response) if company: companies.append(company) print(f" ✓ {company['nazwa']}") break else: print(f" ✗ Run failed: {run.status}") except Exception as e: print(f" ✗ Error: {e}") # Wyświetl tabelę print("\n" + "=" * 80) print("WYNIKI BATCH PROCESSING") print("=" * 80) print(f"{'KRS':<15} {'Nazwa':<40} {'NIP':<15}") print("-" * 80) for company in companies: nazwa = company['nazwa'][:38] # Truncate long names print(f"{company['krs']:<15} {nazwa:<40} {company['nip']:<15}") print("-" * 80) print(f"\n✓ Pobrano {len(companies)}/{len(krs_numbers)} firm") # Cleanup project_client.agents.threads.delete(thread_id=thread.id) print("✓ Thread usunięty") ``` **Uruchom:** ```bash python batch_krs.py ``` **Oczekiwany wynik:** ``` 🔄 Batch processing... Pobieram dane dla 3 firm ▶️ Pobieram KRS 0000028860... ✓ ORLEN Spółka Akcyjna ▶️ Pobieram KRS 0000033001... ✓ PKN ORLEN Spółka Akcyjna ▶️ Pobieram KRS 0000127767... ✓ Powszechny Zakład Ubezpieczeń Spółka Akcyjna ================================================================================ WYNIKI BATCH PROCESSING ================================================================================ KRS Nazwa NIP -------------------------------------------------------------------------------- 0000028860 ORLEN Spółka Akcyjna 7740001454 0000033001 PKN ORLEN Spółka Akcyjna 7740001454 0000127767 Powszechny Zakład Ubezpieczeń Spółka 5250001351 -------------------------------------------------------------------------------- ✓ Pobrano 3/3 firm ✓ Thread usunięty ```Co osiągnęliśmy:
- JSON object + schema w instructions = pseudo-structured output
- Consistent schema między runs (dzięki exact schema w instructions)
- Manual validation prostsza (pola predictable)
- Error handling per firma (nie failuje cały batch)
- Łatwy export do CSV/DataFrame
Kluczowa lekcja:
Schema definition w additional_instructions = poor man’s Pydantic Structured Output!
Troubleshooting
Problem: JSON parsing error mimo response_format
Przyczyna: Model zwrócił JSON w markdown code block (```json).
Rozwiązanie:
Użyj additional_instructions z exact schema:
run = project_client.agents.runs.create_and_process(
thread_id=thread.id,
agent_id=AGENT_ID,
response_format={"type": "json_object"},
additional_instructions="""Return ONLY raw JSON object with exact schema:
{
"nazwa": "...",
"krs": "..."
}
No markdown, no code blocks."""
)
Albo strip markdown przed parsing:
json_match = re.search(r'```json\s*(\{.*\})\s*```', response, re.DOTALL)
if json_match:
json_str = json_match.group(1)
data = json.loads(json_str)
Problem: Missing field w JSON
Przyczyna: Schema się różni między runs (JSON object NIE gwarantuje schema - chyba że podasz schema w instructions!).
Rozwiązanie 1: Podaj exact schema w additional_instructions
additional_instructions="""Return JSON with exact schema:
{
"nazwa": "required",
"krs": "required",
"nip": "optional"
}"""
Rozwiązanie 2: Manual validation z try/except:
try:
nazwa = data["nazwa"]
except KeyError:
print("Missing 'nazwa' field")
nazwa = "N/A"
# Lub z .get()
nazwa = data.get("nazwa", "N/A")
Problem: Nested objects w JSON
Przyczyna: Model zwrócił nested structure:
{
"nazwa": "...",
"adres": {
"ulica": "...",
"miasto": "..."
}
}
Rozwiązanie: Flatten lub handle nested:
adres_obj = data.get("adres", {})
if isinstance(adres_obj, dict):
adres = f"{adres_obj.get('ulica', '')} {adres_obj.get('miasto', '')}"
else:
adres = adres_obj # String
Problem: Run failed z response_format
Przyczyna: Model nie może zwrócić valid JSON dla tego pytania.
Rozwiązanie:
Sprawdź run.status i run.last_error:
if run.status == "failed":
print(f"Error: {run.last_error.code}")
print(f"Message: {run.last_error.message}")
Możliwe przyczyny:
- Pytanie zbyt ogólne/niejasne
- Model nie ma danych (np. nieprawidłowy KRS)
- Timeout
Kluczowe koncepty
additional_instructions
Co to jest:
- Parameter w
create_and_process() - Nadpisuje/uzupełnia agent instructions tylko dla tego run
Różnica:
# Agent instructions (permanent - w portalu)
"You are KRS Agent. You help with company data from KRS."
# additional_instructions (per-run - w kodzie)
run = project_client.agents.runs.create_and_process(
...
additional_instructions="""Return ONLY raw JSON with schema:
{
"nazwa": "company name",
"krs": "KRS number"
}
No markdown."""
)
# Agent widzi: Agent instructions + additional_instructions
Use cases:
- ✅ Schema definition - exact fields to return (NAJWAŻNIEJSZE!)
- ✅ Format control (JSON vs markdown)
- ✅ Temporary overrides
- ✅ Different schemas per query
- ✅ A/B testing prompts
Best practice: Zawsze podawaj exact schema dla consistent output = pseudo-structured output!
JSON object mode
Co gwarantuje:
response_format={"type": "json_object"}
- ✅ Valid JSON (można parsować)
- ✅ Działa z Agent Tools
- ❌ NIE gwarantuje schema (pola mogą się różnić)
Dwa warianty output:
- Markdown block: ```json {…}` → strip before parse
- Goły JSON:
{...}→ direct parse (z override instructions)
Dlaczego nie Pydantic Structured Output?
Azure AI Agents Service ograniczenie:
- ❌ NIE wspiera:
response_format=PydanticModel - ✅ Wspiera:
response_format={"type": "json_object"}
Structured Output wymaga:
- Bezpośredniego OpenAI Chat Completions API
- Ale wtedy tracimy Agent Tools (KRS API, Bing Search)
Trade-off: JSON object + manual validation = best compromise.
Podsumowanie
W tym labie nauczyliśmy się:
✅ Problem: Free-form text wymaga parsowania
✅ JSON object mode: Valid JSON (ale nie schema - chyba że podasz!)
✅ Schema w instructions: additional_instructions z exact schema = pseudo-structured output
✅ Clean JSON: Goły JSON vs markdown block
✅ Consistent schema: Schema definition → predictable fields
✅ Manual validation: Prostsza z schema w instructions
✅ Batch processing: Loop + validation per item
Kluczowe wnioski:
- JSON object mode = valid JSON guarantee (nie schema)
- Schema w additional_instructions = poor man’s Pydantic Structured Output!
- Model dokładnie wie jakie pola zwrócić → consistent output
- Manual validation prostsza (pola predictable)
Best practices:
- ✓ ZAWSZE podawaj exact schema w additional_instructions
- ✓ Format:
{"field": "description"}w instructions - ✓ Używaj
.get()dla optional fields (safety) - ✓ Try/except dla JSONDecodeError
- ✓ Trade-off: może więcej tokenów, ale worth it (consistency!)
Next step: Lab 09 - Wymuszanie narzędzi i Citations