Lab 08: Python SDK - Tool Choice i Citations
Opis
W tym laboratorium nauczymy się kontrolowania użycia narzędzi przez agenta używając tool_choice parameter oraz ekstrakcji citations z odpowiedzi Bing Search. Będziemy budować kod małymi kawałkami testując różne scenariusze.
Ważne: Użyjemy istniejącego agenta z Bing Search tool (z Lab 06).
⚠️ Citations są OBOWIĄZKOWE dla Bing Grounding
Bing Search ma wymagania prawne (Display Requirements):
- Musisz wyświetlać źródła (citations) w odpowiedzi
- Link do źródła + tytuł strony
- Bing logo (w aplikacjach web)
Nieprzestrzeganie = naruszenie Terms of Service!
Pełna dokumentacja:
Problem: Kontrola narzędzi i citations
Wyzwanie 1: Nieprzewidywalne użycie narzędzi
Agent sam decyduje kiedy użyć Bing Search:
# Pytanie: "Jaka jest stolica Polski?"
# Agent może:
# 1. Odpowiedzieć z własnej wiedzy (bez Bing) → brak citations
# 2. Użyć Bing Search → z citations
Problem: Nie mamy kontroli. Czasem potrzebujemy:
- Zawsze świeże dane (wymuś Bing)
- Lub: nigdy nie używaj Bing (tylko wiedza modelu)
Wyzwanie 2: Citations są ukryte w strukturze
Bing zwraca citations, ale są “zagnieżdżone”:
message.content[0].text.annotations[0].url_citation.url
message.content[0].text.annotations[0].url_citation.title
Problem: Trzeba manual extraction. Różne formaty (markdown vs JSON) mają różne struktury.
Use cases
- Forced fresh data: Wiadomości, pogoda, kursy walut
- No Bing allowed: Pytania o prywatne dane (compliance)
- Citations tracking: Legal compliance, source attribution
- Batch processing: Zbieranie citations z wielu zapytań
Rozwiązanie: tool_choice parameter
Czym jest tool_choice?
tool_choice = kontrola nad wyborem narzędzi przez agenta.
run = project_client.agents.runs.create_and_process(
thread_id=thread.id,
agent_id=agent.id,
tool_choice={"type": "bing_grounding"} # Wymuś Bing!
)
Opcje:
| Wartość | Zachowanie |
|---|---|
| Brak (default) | Agent sam decyduje czy użyć narzędzia |
{"type": "bing_grounding"} |
MUSISZ użyć Bing (błąd jeśli nie da się) |
{"type": "none"} |
NIE MOŻESZ użyć żadnego narzędzia |
Trade-off:
auto(default) = elastic, czasem nieprzewidywalneforced= predictable, ale może być overkill (więcej tokenów)
Przygotowanie środowiska
Krok 1: Sprawdź Agent ID
Użyjemy istniejącego agenta z Bing Search (z Lab 06).
Przejdź do Azure AI Foundry Portal → Agents → znajdź agenta z Bing tool (powinien nazywać się HelloWorld) i skopiuj Agent ID.
Sprawdź: Agent ma Bing Grounding tool w sekcji Tools.
Krok 2: Dane z Lab 07
Będziemy używać tych samych danych:
PROJECT_ENDPOINT- z OverviewAGENT_ID- Agent z Bing- ChainedTokenCredential (Service Principal → Azure CLI)
Budowanie skryptu krok po kroku
Zbudujemy skrypt małymi kawałkami testując różne scenariusze tool_choice i citations extraction.
Krok 1: Utworzenie pliku i import
Otwórz Visual Studio Code wpisując code . w terminal będąc w katalogu roboczym z (venv)
Utwórz nowy plik bing_citations.py w katalogu roboczym:
#!/usr/bin/env python3
"""
Tool choice i Citations extraction z Bing Search
"""
from azure.ai.projects import AIProjectClient
from azure.identity import ChainedTokenCredential, EnvironmentCredential, AzureCliCredential
import json
print("✓ Importy załadowane")
Uruchom:
python bing_citations.py
Oczekiwany wynik:
✓ Importy załadowane
Krok 2: Utworzenie klienta i thread
Dodaj kod (zastąp wartości swoimi z Lab 07!):
# Konfiguracja (zastąp swoimi wartościami!)
PROJECT_ENDPOINT = "YOUR_PROJECT_ENDPOINT_HERE" # Z Lab 07
AGENT_ID = "YOUR_AGENT_WITH_BING_ID_HERE" # Agent z Bing tool
# 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.agents.threads.create()
print("✓ Klient i thread gotowe")
print(f" Thread ID: {thread.id}")
Zastąp wartości swoimi:
PROJECT_ENDPOINT- z Lab 07AGENT_ID- Agent z Bing tool (z Lab 06)
Uruchom:
python bing_citations.py
Oczekiwany wynik:
🔧 Tworzenie klienta...
🔧 Tworzenie thread...
✓ Klient i thread gotowe
Thread ID: thread_xyz789...
Krok 3: Test 1 - Auto mode (agent decyduje)
Dodaj kod:
# Test 1: Auto mode - agent sam decyduje
print("\n" + "=" * 60)
print("TEST 1: AUTO MODE (agent decyduje)")
print("=" * 60)
# Pytanie które agent MOŻE odpowiedzieć bez Bing
question1 = "Jaka jest stolica Polski?"
# Dodaj wiadomość
message1 = project_client.agents.messages.create(
thread_id=thread.id,
role="user",
content=question1
)
# Run BEZ tool_choice (agent sam decyduje)
print(f"\n▶️ Pytanie: {question1}")
print(" Tool choice: AUTO (agent decyduje)")
run1 = project_client.agents.runs.create_and_process(
thread_id=thread.id,
agent_id=AGENT_ID
# Brak tool_choice = auto mode
)
# Odczyt odpowiedzi
messages1 = project_client.agents.messages.list(thread_id=thread.id)
for msg in messages1:
if msg.role == "assistant" and msg.run_id == run1.id:
response1 = msg.content[0].text.value
break
print("\n✓ Odpowiedź agenta:")
print("-" * 60)
print(response1[:300]) # Pierwsze 300 znaków
print("-" * 60)
# Sprawdź czy użyto Bing
run_steps1 = project_client.agents.run_steps.list(
thread_id=thread.id,
run_id=run1.id
)
used_bing = False
for step in run_steps1:
if hasattr(step, 'step_details') and hasattr(step.step_details, 'tool_calls'):
for tool_call in step.step_details.tool_calls:
if tool_call.type == "bing_grounding":
used_bing = True
break
print(f"\n📊 Użyto Bing Search: {'✓ TAK' if used_bing else '✗ NIE (z własnej wiedzy)'}")
print(f"📊 Tokeny: {run1.usage.total_tokens if run1.usage else 'N/A'}")
Uruchom:
python bing_citations.py
Oczekiwany wynik (może się różnić!):
==========================================================
TEST 1: AUTO MODE (agent decyduje)
==========================================================
▶️ Pytanie: Jaka jest stolica Polski?
Tool choice: AUTO (agent decyduje)
✓ Odpowiedź agenta:
------------------------------------------------------------
Stolicą Polski jest Warszawa.
------------------------------------------------------------
📊 Użyto Bing Search: ✗ NIE (z własnej wiedzy)
📊 Tokeny: 145
Co się stało:
- Agent odpowiedział z własnej wiedzy (basic fact)
- NIE użył Bing Search (nie było potrzeby)
- Brak citations (odpowiedź z modelu)
- Niskie zużycie tokenów
Nieprzewidywalność: Czasem agent MOŻE użyć Bing, czasem nie. Zależy od pytania i wewnętrznej heurystyki.
Krok 4: Test 2 - Forced Bing (tool_choice wymusza)
Dodaj kod:
# Test 2: Forced Bing - wymuszenie użycia Bing
print("\n" + "=" * 60)
print("TEST 2: FORCED BING (tool_choice wymusza)")
print("=" * 60)
# TO SAMO pytanie!
question2 = "Jaka jest stolica Polski?"
# Dodaj wiadomość
message2 = project_client.agents.messages.create(
thread_id=thread.id,
role="user",
content=question2
)
# Run Z tool_choice - wymuszamy Bing!
print(f"\n▶️ Pytanie: {question2}")
print(" Tool choice: FORCED Bing")
run2 = project_client.agents.runs.create_and_process(
thread_id=thread.id,
agent_id=AGENT_ID,
tool_choice={"type": "bing_grounding"} # Wymuś Bing!
)
# Odczyt odpowiedzi
response2 = None
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
if response2 is None:
response2 = "Brak odpowiedzi od agenta."
print("Brak odpowiedzi od agenta.")
print("\n✓ Odpowiedź agenta:")
print("-" * 60)
print(response2[:400]) # Pierwsze 400 znaków
print("-" * 60)
# Sprawdź czy użyto Bing (powinno być TAK!)
run_steps2 = project_client.agents.run_steps.list(
thread_id=thread.id,
run_id=run2.id
)
used_bing2 = False
for step in run_steps2:
if hasattr(step, 'step_details') and hasattr(step.step_details, 'tool_calls'):
for tool_call in step.step_details.tool_calls:
if tool_call.type == "bing_grounding":
used_bing2 = True
break
print(f"\n📊 Użyto Bing Search: {'✓ TAK (wymuszono!)' if used_bing2 else '✗ NIE'}")
print(f"📊 Tokeny: {run2.usage.total_tokens if run2.usage else 'N/A'}")
Uruchom:
python bing_citations.py
Oczekiwany wynik:
==========================================================
TEST 2: FORCED BING (tool_choice wymusza)
==========================================================
▶️ Pytanie: Jaka jest stolica Polski?
Tool choice: FORCED Bing
✓ Odpowiedź agenta:
------------------------------------------------------------
Stolicą Polski jest Warszawa. Jest to największe miasto w Polsce, położone nad Wisłą [1][2].
[1] https://pl.wikipedia.org/wiki/Warszawa
[2] https://www.britannica.com/place/Warsaw
------------------------------------------------------------
📊 Użyto Bing Search: ✓ TAK (wymuszono!)
📊 Tokeny: 2847
Co się stało:
tool_choice={"type": "bing_grounding"}WYMUSIŁO użycie Bing- Agent MUSIAŁ wyszukać w internecie (nawet dla basic fact)
- Odpowiedź zawiera citations ([1][2])
- Znacznie więcej tokenów (Bing call + processing)
Porównanie:
- Test 1 (auto): 145 tokenów, brak citations, z własnej wiedzy
- Test 2 (forced): 2847 tokenów, z citations, z Bing
Krok 5: Citations extraction
Teraz nauczymy się wyciągać citations programmatically.
Dodaj kod:
# Ekstrakcja citations z Test 2
print("\n" + "=" * 60)
print("EKSTRAKCJA CITATIONS")
print("=" * 60)
# Pobierz message z Test 2
messages_with_citations = project_client.agents.messages.list(thread_id=thread.id)
for msg in messages_with_citations:
if msg.role == "assistant" and msg.run_id == run2.id:
# Sprawdź czy są annotations (citations)
text_content = msg.content[0]
if hasattr(text_content, 'text') and hasattr(text_content.text, 'annotations'):
annotations = text_content.text.annotations
if annotations and len(annotations) > 0:
print(f"\n✓ Znaleziono {len(annotations)} citations:")
print("-" * 60)
for i, annotation in enumerate(annotations, 1):
# Bing citations są typu url_citation (NIE uri_citation!)
if hasattr(annotation, 'url_citation'):
citation = annotation.url_citation
print(f"\n[{i}] {citation.title}")
print(f" URL: {citation.url}")
# Text który został zacytowany
if hasattr(annotation, 'text'):
print(f" Ref: {annotation.text}")
else:
print("\n✗ Brak citations (agent nie użył Bing)")
break
print("-" * 60)
Uruchom:
python bing_citations.py
Oczekiwany wynik:
==========================================================
EKSTRAKCJA CITATIONS
==========================================================
✓ Znaleziono 2 citations:
------------------------------------------------------------
[1] Warszawa – Wikipedia, wolna encyklopedia
URL: https://pl.wikipedia.org/wiki/Warszawa
Ref: [1]
[2] Warsaw | History, Population, Map, & Facts | Britannica
URL: https://www.britannica.com/place/Warsaw
Ref: [2]
------------------------------------------------------------
Co się stało:
- Citations są w
message.content[0].text.annotations - Każda citation ma
uri_citation.titleiuri_citation.uri - Możemy programmatically wyciągnąć źródła
- Display Requirements: Musisz pokazać te linki użytkownikowi!
Krok 6: Porównanie formatów odpowiedzi
Sprawdźmy jak citations wyglądają w formacie JSON.
Dodaj kod:
# Test 3: Citations w JSON format (schema bez format enforcement)
print("\n" + "=" * 60)
print("TEST 3: CITATIONS W JSON (schema only)")
print("=" * 60)
# Pytanie wymagające Bing
question3 = "Jaka jest dzisiejsza pogoda w Warszawie?"
# Dodaj wiadomość
message3 = project_client.agents.messages.create(
thread_id=thread.id,
role="user",
content=question3
)
# Run z Bing + JSON format (schema bez "no markdown")
print(f"\n▶️ Pytanie: {question3}")
print(" Tool choice: FORCED Bing")
print(" Response format: JSON (schema only)")
run3 = project_client.agents.runs.create_and_process(
thread_id=thread.id,
agent_id=AGENT_ID,
tool_choice={"type": "bing_grounding"},
response_format={"type": "json_object"},
additional_instructions="""Return ONLY raw JSON object with this exact schema:
{
"location": "city name",
"weather": "current weather description",
"sources": ["list of source URLs"]
}
No explanations, no markdown, no text before JSON. Pure JSON only."""
)
# Odczyt odpowiedzi
response3 = None
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
if response3 is None:
response3 = "Brak odpowiedzi od agenta."
print("Brak odpowiedzi od agenta.")
print("\n✓ Odpowiedź agenta (JSON):")
print("-" * 60)
print(response3[:400])
print("-" * 60)
# Próba parsowania
print("\n🔍 Próba parsowania JSON:")
try:
# Strip markdown if present
json_text = response3
if response3.strip().startswith("```"):
import re
json_match = re.search(r'```json\s*(\{.*\})\s*```', response3, re.DOTALL)
if json_match:
json_text = json_match.group(1)
data3 = json.loads(json_text)
print("✓ Parse SUCCESS")
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 bing_citations.py
Oczekiwany wynik:
==========================================================
TEST 3: CITATIONS W JSON (schema only)
==========================================================
▶️ Pytanie: Jaka jest dzisiejsza pogoda w Warszawie?
Tool choice: FORCED Bing
Response format: JSON (schema only)
✓ Odpowiedź agenta (JSON):
------------------------------------------------------------
```json
{
"location": "Warszawa",
"weather": "Pochmurno, 5°C, wiatr północny 15 km/h",
"sources": [
"https://weather.com/pl-PL/weather/today/l/Warszawa",
"https://www.meteo.pl/warszawa"
]
}
🔍 Próba parsowania JSON: ✓ Parse SUCCESS { “location”: “Warszawa”, “weather”: “Pochmurno, 5°C, wiatr północny 15 km/h”, “sources”: [ “https://weather.com/pl-PL/weather/today/l/Warszawa”, … }
📊 Tokeny: 3214
**Co się stało:**
- Schema w `additional_instructions` pomogła modelowi structured output
- Model sam dodał `sources` array z URL-ami
- Nadal ```json block (trzeba strip)
- Sources są w JSON, ale nie mamy title (tylko URL)
**Ograniczenie:** Sources w JSON to tylko URLs - brak title jak w native citations!
---
### Krok 7: Porównanie wszystkich testów
Dodaj kod na końcu:
```python
# Porównanie wszystkich testów
print("\n" + "=" * 60)
print("PORÓWNANIE TESTÓW")
print("=" * 60)
print(f"""
Test | Tool choice | Format | Citations | Tokeny
-------------------------|-------------|-----------|-----------|--------
Test 1: Auto mode | AUTO | Markdown | ✗ | {run1.usage.total_tokens if run1.usage else 'N/A':6}
Test 2: Forced Bing | FORCED | Markdown | ✓ | {run2.usage.total_tokens if run2.usage else 'N/A':6}
Test 3: Forced + JSON | FORCED | JSON | ✓ (URLs) | {run3.usage.total_tokens if run3.usage else 'N/A':6}
""")
print("\nKiedy używać:")
print(" ✓ AUTO: Agent decyduje, oszczędność tokenów, nieprzewidywalne")
print(" ✓ FORCED: Zawsze świeże dane, predictable, więcej tokenów")
print(" ✓ JSON format: Strukturalne dane, ale sources = tylko URLs")
print("\nKluczowa lekcja:")
print(" tool_choice kontroluje użycie narzędzi")
print(" Citations OBOWIĄZKOWE dla Bing (Display Requirements!)")
print(" Native annotations mają title + URL, JSON tylko URL")
# Cleanup
print("\n🧹 Usuwanie thread...")
project_client.agents.threads.delete(thread_id=thread.id)
print("✓ Thread usunięty")
Uruchom:
python bing_citations.py
Oczekiwany wynik:
==========================================================
PORÓWNANIE TESTÓW
==========================================================
Test | Tool choice | Format | Citations | Tokeny
-------------------------|-------------|-----------|-----------|--------
Test 1: Auto mode | AUTO | Markdown | ✗ | 145
Test 2: Forced Bing | FORCED | Markdown | ✓ | 2847
Test 3: Forced + JSON | FORCED | JSON | ✓ (URLs) | 3214
Kiedy używać:
✓ AUTO: Agent decyduje, oszczędność tokenów, nieprzewidywalne
✓ FORCED: Zawsze świeże dane, predictable, więcej tokenów
✓ JSON format: Strukturalne dane, ale sources = tylko URLs
Kluczowa lekcja:
tool_choice kontroluje użycie narzędzi
Citations OBOWIĄZKOWE dla Bing (Display Requirements!)
Native annotations mają title + URL, JSON tylko URL
🧹 Usuwanie thread...
✓ Thread usunięty
Co się stało:
- Porównaliśmy 3 podejścia do Bing usage
- AUTO = cheap, nieprzewidywalne
- FORCED = expensive, predictable, z citations
- JSON = strukturalne, ale citations gorsze (tylko URLs)
Trade-off:
- Koszt (tokeny) vs Predictability (zawsze świeże)
- Native citations (title+URL) vs JSON sources (tylko URL)
Citations Display Requirements
Wymagania prawne
Bing Grounding ma obowiązkowe wymagania prawne:
You MUST display the following for each Grounding with Bing Search result:
- Title of the source page
- URL (clickable link)
- Bing logo (for web applications)
Source: Bing Grounding Documentation
Jak wyświetlać citations
Przykład (web app):
<div class="citations">
<img src="bing-logo.png" alt="Powered by Bing" />
<h4>Sources:</h4>
<ul>
<li>
<a href="https://pl.wikipedia.org/wiki/Warszawa">
Warszawa – Wikipedia, wolna encyklopedia
</a>
</li>
<li>
<a href="https://www.britannica.com/place/Warsaw">
Warsaw | History, Population, Map, & Facts | Britannica
</a>
</li>
</ul>
</div>
Przykład (CLI):
Stolicą Polski jest Warszawa.
Sources (Powered by Bing):
[1] Warszawa – Wikipedia, wolna encyklopedia
https://pl.wikipedia.org/wiki/Warszawa
[2] Warsaw | History, Population, Map, & Facts | Britannica
https://www.britannica.com/place/Warsaw
Extraction pattern
def extract_citations(message):
"""Extract citations from agent message"""
citations = []
if hasattr(message.content[0], 'text') and hasattr(message.content[0].text, 'annotations'):
for annotation in message.content[0].text.annotations:
if hasattr(annotation, 'url_citation'):
citations.append({
'title': annotation.url_citation.title,
'url': annotation.url_citation.url,
'text': annotation.text if hasattr(annotation, 'text') else ''
})
return citations
# Usage
citations = extract_citations(message3)
for i, cite in enumerate(citations, 1):
print(f"[{i}] {cite['title']}")
print(f" {cite['url']}")
Troubleshooting
Problem: “Invalid tool_choice type”
Przyczyna: Nieprawidłowa wartość w tool_choice.
Rozwiązanie:
Użyj dokładnie {"type": "bing_grounding"}:
tool_choice={"type": "bing_grounding"} # Correct
# NIE: tool_choice="bing"
# NIE: tool_choice={"bing_grounding": True}
Problem: Run failed z tool_choice
Przyczyna: Agent nie ma Bing tool lub pytanie nie da się odpowiedzieć przez Bing.
Rozwiązanie 1: Sprawdź że agent ma Bing tool:
# W Azure AI Foundry Portal:
# Agents → Twój agent → Tools → sprawdź "Bing Grounding"
Rozwiązanie 2: Pytanie musi być możliwe do wyszukania:
# BAD: "Wymyśl nową bajkę" - nie da się wyszukać!
# GOOD: "Jaka jest pogoda w Warszawie?" - da się wyszukać
Problem: Brak citations mimo forced Bing
Przyczyna: Citations są w annotations, nie w text.
Rozwiązanie:
Sprawdź message.content[0].text.annotations:
annotations = message.content[0].text.annotations
if annotations:
for ann in annotations:
if hasattr(ann, 'url_citation'):
print(ann.url_citation.title)
else:
print("No annotations found")
Problem: JSON format nie ma citations
Przyczyna: JSON format powoduje że model sam formatuje sources (jako URLs w JSON), tracimy native annotations.
Rozwiązanie:
Dla pełnych citations (title + URL) używaj markdown format (bez response_format):
# Markdown = native citations z title + URL
run = project_client.agents.runs.create_and_process(
thread_id=thread.id,
agent_id=AGENT_ID,
tool_choice={"type": "bing_grounding"}
# BEZ response_format!
)
# Potem extract z annotations
Kluczowe koncepty
tool_choice
Co to jest:
- Parameter w
create_and_process() - Kontroluje wybór narzędzi przez agenta
Opcje:
# Auto (default) - agent decyduje
run = project_client.agents.runs.create_and_process(
thread_id=thread.id,
agent_id=agent.id
)
# Forced - MUSISZ użyć Bing
run = project_client.agents.runs.create_and_process(
thread_id=thread.id,
agent_id=agent.id,
tool_choice={"type": "bing_grounding"}
)
# None - NIE MOŻESZ użyć narzędzi
run = project_client.agents.runs.create_and_process(
thread_id=thread.id,
agent_id=agent.id,
tool_choice={"type": "none"}
)
Use cases:
- ✅ Forced: Wiadomości, pogoda, ceny, aktualne wydarzenia
- ✅ Auto: Oszczędność tokenów, mixed queries
- ✅ None: Compliance (prywatne dane, nie mogą opuścić Azure)
Citations structure
Native annotations (markdown format):
message.content[0].text.annotations[i].url_citation.title # "Wikipedia - Warszawa"
message.content[0].text.annotations[i].url_citation.url # "https://pl.wikipedia.org/..."
message.content[0].text.annotations[i].text # "[1]"
JSON format:
- Model sam dodaje sources do JSON
- Tylko URLs (brak title!)
- Schema w
additional_instructionspomaga
Best practice: Dla pełnych citations używaj markdown + native annotations.
Display Requirements
OBOWIĄZKOWE dla Bing:
- Title każdego źródła
- Clickable URL
- Bing logo (web apps)
Nieprzestrzeganie = naruszenie Terms of Service!
Podsumowanie
W tym labie nauczyliśmy się:
✅ tool_choice: Kontrola nad użyciem narzędzi (auto vs forced)
✅ Forced Bing: Zawsze świeże dane (tool_choice={"type": "bing_grounding"})
✅ Citations extraction: Programmatic access do źródeł
✅ Display Requirements: Prawny obowiązek wyświetlania citations
✅ Format comparison: Markdown (native citations) vs JSON (URLs only)
✅ Trade-offs: Cost vs freshness, native vs JSON citations
Kluczowe wnioski:
- tool_choice daje predictability (ale kosztuje tokeny)
- Citations są OBOWIĄZKOWE dla Bing (Display Requirements!)
- Native annotations (markdown) > JSON sources (tylko URLs)
- Trade-off: cost (tokeny) vs freshness (Bing)
Best practices:
- ✓ Używaj forced Bing dla time-sensitive queries
- ✓ Zawsze ekstraktuj i wyświetlaj citations
- ✓ Markdown format dla pełnych citations (title + URL)
- ✓ JSON format tylko jeśli potrzebujesz strukturalnych danych
Next step: Lab 10 - Connected Agents (Multi-agent orchestration)