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:

  1. Markdown code block (częste):
    {
      "nazwa": "ORLEN S.A.",
      "krs": "0000028860"
    }
    
  2. 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 PortalAgentsKRS-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 Overview
  • AGENT_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 TODO na 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 JSON
  • additional_instructions zawiera 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:

  1. Schema definition - jakie pola zwrócić (→ consistent fields)
  2. 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_instructions z 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:

  1. Test 1: Ani schema, ani format → chaos
  2. Test 2: Schema, bez format enforcement → consistent fields, ale ```json block
  3. 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:

  1. Markdown block: ```json {…}` → strip before parse
  2. 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

results matching ""

    No results matching ""