Skip to content

Commit f70f8d9

Browse files
committed
adds new function calling examples for Spanish
1 parent 8fd96bd commit f70f8d9

File tree

7 files changed

+476
-204
lines changed

7 files changed

+476
-204
lines changed

function_calling_fewshots.py

Lines changed: 38 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -93,31 +93,53 @@ def search_database(search_query: str, price_filter: dict | None = None) -> dict
9393
{"role": "system", "content": "You are a product search assistant."},
9494
{"role": "user", "content": "good options for climbing gear that can be used outside?"},
9595
{
96-
"id": "madeup",
97-
"call_id": "call_abc123",
96+
"role": "assistant",
97+
"content": None,
98+
"tool_calls": [
99+
{
100+
"id": "call_abc123",
101+
"type": "function",
102+
"function": {
103+
"name": "search_database",
104+
"arguments": '{"search_query":"climbing gear outside"}',
105+
},
106+
}
107+
],
108+
},
109+
{
110+
"role": "tool",
111+
"tool_call_id": "call_abc123",
98112
"name": "search_database",
99-
"arguments": '{"search_query":"climbing gear outside"}',
100-
"type": "function_call",
113+
"content": '{"results": [{"id": "1", "name": "Climbing Rope", "price": 45.99}, {"id": "2", "name": "Carabiners Set", "price": 25.50}]}',
101114
},
102115
{
103-
"id": "madeupoutput",
104-
"call_id": "call_abc123",
105-
"output": "Search results for climbing gear that can be used outside: ...",
106-
"type": "function_call_output",
116+
"role": "assistant",
117+
"content": "I found some great climbing gear options for outdoor use, including climbing ropes and carabiner sets.",
107118
},
108119
{"role": "user", "content": "are there any shoes less than $50?"},
109120
{
110-
"id": "madeup",
111-
"call_id": "call_abc456",
121+
"role": "assistant",
122+
"content": None,
123+
"tool_calls": [
124+
{
125+
"id": "call_abc456",
126+
"type": "function",
127+
"function": {
128+
"name": "search_database",
129+
"arguments": '{"search_query":"shoes","price_filter":{"comparison_operator":"<","value":50}}',
130+
},
131+
}
132+
],
133+
},
134+
{
135+
"role": "tool",
136+
"tool_call_id": "call_abc456",
112137
"name": "search_database",
113-
"arguments": '{"search_query":"shoes","price_filter":{"comparison_operator":"<","value":50}}',
114-
"type": "function_call",
138+
"content": '{"results": [{"id": "10", "name": "Trail Running Shoes", "price": 42.00}, {"id": "11", "name": "Approach Shoes", "price": 48.99}]}',
115139
},
116140
{
117-
"id": "madeupoutput",
118-
"call_id": "call_abc456",
119-
"output": "Search results for shoes cheaper than 50: ...",
120-
"type": "function_call_output",
141+
"role": "assistant",
142+
"content": "Yes! I found trail running shoes for $42 and approach shoes for $48.99, both under $50.",
121143
},
122144
{"role": "user", "content": "Find me a red shirt under $20."},
123145
]

spanish/README.md

Lines changed: 34 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,16 @@ Este repositorio contiene una colección de scripts en Python que demuestran có
2020
### Completados de chat de OpenAI
2121

2222
Estos scripts usan el paquete `openai` de Python para demostrar cómo utilizar la API de Chat Completions. En orden creciente de complejidad:
23-
24-
1. [`chat.py`](../chat.py): Script simple que muestra cómo generar un completado de chat.
25-
2. [`chat_stream.py`](../chat_stream.py): Añade `stream=True` para recibir el completado progresivamente.
26-
3. [`chat_history.py`](../chat_history.py): Añade un chat bidireccional que conserva el historial y lo reenvía en cada llamada.
27-
4. [`chat_history_stream.py`](../chat_history_stream.py): Igual que el anterior pero además con `stream=True`.
23+
1. [`chat.py`](chat.py): Script simple que muestra cómo generar un completado de chat.
24+
2. [`chat_stream.py`](chat_stream.py): Añade `stream=True` para recibir el completado progresivamente.
25+
3. [`chat_history.py`](chat_history.py): Añade un chat bidireccional que conserva el historial y lo reenvía en cada llamada.
26+
4. [`chat_history_stream.py`](chat_history_stream.py): Igual que el anterior pero además con `stream=True`.
2827

2928
Scripts adicionales de características:
3029

31-
* [`chat_safety.py`](../chat_safety.py): Manejo de excepciones para filtros de seguridad de contenido (Azure AI Content Safety).
32-
* [`chat_async.py`](../chat_async.py): Uso de clientes asíncronos y envío concurrente de múltiples solicitudes con `asyncio.gather`.
30+
* [`chat_safety.py`](chat_safety.py): Manejo de excepciones para filtros de seguridad de contenido (Azure AI Content Safety).
31+
* [`chat_async.py`](chat_async.py): Uso de clientes asíncronos y envío concurrente de múltiples solicitudes con `asyncio.gather`.
32+
* [`few_shot_examples.py`](few_shot_examples.py): Demuestra patrones de few‑shot (proporcionar ejemplos en el prompt) para guiar respuestas del modelo.
3333

3434
### Llamadas a funciones (Function calling)
3535

@@ -39,12 +39,13 @@ En todos los ejemplos se declara una lista de funciones en el parámetro `tools`
3939

4040
Scripts (en orden de capacidad):
4141

42-
1. [`function_calling_basic.py`](../function_calling_basic.py): Declara una sola función `lookup_weather` y muestra la llamada (si existe) o el contenido normal (no ejecuta la función).
43-
2. [`function_calling_call.py`](../function_calling_call.py): Ejecuta `lookup_weather` si el modelo la solicita, parseando los argumentos JSON.
44-
3. [`function_calling_extended.py`](../function_calling_extended.py): Hace el ciclo completo: tras ejecutar la función, añade un mensaje de rol `tool` con el resultado y vuelve a consultar al modelo para incorporar los datos reales.
45-
4. [`function_calling_extended_errors.py`](../function_calling_extended_errors.py): Igual que el ejemplo extendido pero con manejo robusto de errores (JSON malformado, herramienta inexistente, excepciones de ejecución, serialización JSON de respaldo).
46-
5. [`function_calling_parallel.py`](../function_calling_parallel.py): Demuestra el modelo devolviendo múltiples llamadas a herramientas en una sola respuesta
47-
6. [`function_calling_while_loop.py`](../function_calling_while_loop.py): Bucle conversacional iterativo que sigue ejecutando llamadas secuenciales (con manejo de errores) hasta que el modelo da una respuesta final en lenguaje natural.
42+
1. [`function_calling_basic.py`](function_calling_basic.py): Declara una sola función `lookup_weather` y muestra la llamada (si existe) o el contenido normal (no ejecuta la función).
43+
2. [`function_calling_call.py`](function_calling_call.py): Ejecuta `lookup_weather` si el modelo la solicita, parseando los argumentos JSON.
44+
3. [`function_calling_extended.py`](function_calling_extended.py): Hace el ciclo completo: tras ejecutar la función, añade un mensaje de rol `tool` con el resultado y vuelve a consultar al modelo para incorporar los datos reales.
45+
4. [`function_calling_errors.py`](function_calling_errors.py): Igual que el ejemplo extendido pero con manejo robusto de errores (JSON malformado, herramienta inexistente, excepciones de ejecución, serialización JSON de respaldo).
46+
5. [`function_calling_parallel.py`](function_calling_parallel.py): Demuestra el modelo devolviendo múltiples llamadas a herramientas en una sola respuesta.
47+
6. [`function_calling_while_loop.py`](function_calling_while_loop.py): Bucle conversacional iterativo que sigue ejecutando llamadas secuenciales (con manejo de errores) hasta que el modelo da una respuesta final en lenguaje natural.
48+
7. [`function_calling_fewshots.py`](function_calling_fewshots.py): Combina function calling con ejemplos few‑shot para reforzar esquemas y estilos de respuesta.
4849

4950
Debe usarse un modelo que soporte function calling (por ejemplo, `gpt-4o`, `gpt-4o-mini`, etc.). Algunos modelos locales o antiguos no soportan `tools`.
5051

@@ -60,22 +61,31 @@ python -m pip install -r requirements-rag.txt
6061

6162
Luego ejecuta (en orden de complejidad):
6263

63-
* [`rag_csv.py`](../rag_csv.py): Recupera filas coincidentes de un CSV y las usa para responder.
64-
* [`rag_multiturn.py`](../rag_multiturn.py): Igual, pero con chat multi‑turno y preservación de historial.
65-
* [`rag_queryrewrite.py`](../rag_queryrewrite.py): Añade reescritura de la consulta del usuario para mejorar la recuperación.
66-
* [`rag_documents_ingestion.py`](../rag_documents_ingestion.py): Ingeste de PDFs: convierte a Markdown (pymupdf), divide en fragmentos (LangChain), genera embeddings (OpenAI) y guarda en un JSON local.
67-
* [`rag_documents_flow.py`](../rag_documents_flow.py): Flujo RAG que consulta el JSON creado anteriormente.
68-
* [`rag_documents_hybrid.py`](../rag_documents_hybrid.py): Recuperación híbrida (vector + keywords), fusión con RRF y re‑ranking semántico con un modelo cross‑encoder.
64+
* [`rag_csv.py`](rag_csv.py): Recupera filas coincidentes de un CSV y las usa para responder.
65+
* [`rag_multiturn.py`](rag_multiturn.py): Igual, pero con chat multi‑turno y preservación de historial.
66+
* [`rag_queryrewrite.py`](rag_queryrewrite.py): Añade reescritura de la consulta del usuario para mejorar la recuperación.
67+
* [`rag_documents_ingestion.py`](rag_documents_ingestion.py): Ingeste de PDFs: convierte a Markdown (pymupdf), divide en fragmentos (LangChain), genera embeddings (OpenAI) y guarda en un JSON local.
68+
* [`rag_documents_flow.py`](rag_documents_flow.py): Flujo RAG que consulta el JSON creado anteriormente.
69+
* [`rag_documents_hybrid.py`](rag_documents_hybrid.py): Recuperación híbrida (vector + keywords), fusión con RRF y re‑ranking semántico con un modelo cross‑encoder.
70+
* [`retrieval_augmented_generation.py`](retrieval_augmented_generation.py): Variante alternativa de RAG con un flujo simplificado de recuperación + generación.
6971

7072
### Salidas estructuradas
7173

7274
Estos scripts muestran cómo generar respuestas estructuradas usando modelos Pydantic:
7375

74-
* [`structured_outputs_basic.py`](../structured_outputs_basic.py): Extrae información simple de un evento.
75-
* [`structured_outputs_description.py`](../structured_outputs_description.py): Añade descripciones en campos para guiar el formato.
76-
* [`structured_outputs_enum.py`](../structured_outputs_enum.py): Usa enumeraciones para restringir valores.
77-
* [`structured_outputs_function_calling.py`](../structured_outputs_function_calling.py): Usa funciones definidas con Pydantic para llamadas automáticas.
78-
* [`structured_outputs_nested.py`](../structured_outputs_nested.py): Modelos anidados para estructuras más complejas (por ejemplo, eventos con participantes detallados).
76+
* [`structured_outputs_basic.py`](structured_outputs_basic.py): Extrae información simple de un evento.
77+
* [`structured_outputs_description.py`](structured_outputs_description.py): Añade descripciones en campos para guiar el formato.
78+
* [`structured_outputs_enum.py`](structured_outputs_enum.py): Usa enumeraciones para restringir valores.
79+
* [`structured_outputs_function_calling.py`](structured_outputs_function_calling.py): Usa funciones definidas con Pydantic para llamadas automáticas.
80+
* [`structured_outputs_nested.py`](structured_outputs_nested.py): Modelos anidados para estructuras más complejas (por ejemplo, eventos con participantes detallados).
81+
82+
### Ingeniería de prompts y otros
83+
84+
Scripts adicionales fuera de las categorías anteriores:
85+
86+
* [`prompt_engineering.py`](prompt_engineering.py): Técnicas de ingeniería de prompts (roles, instrucciones, delimitadores, control de formato).
87+
* [`chained_calls.py`](chained_calls.py): Llamadas encadenadas; salida de una respuesta alimenta la siguiente (pipeline de pasos).
88+
* [`retrieval_augmented_generation.py`](retrieval_augmented_generation.py): (Listado también en RAG) Alternativa minimalista de flujo RAG.
7989

8090
## Configuración del entorno de Python
8191

spanish/function_calling_basic.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
"type": "function",
3737
"function": {
3838
"name": "lookup_weather",
39-
"description": "Buscar el clima para un nombre de ciudad o código postal dado.",
39+
"description": "Busca el clima para un nombre de ciudad o código postal dado.",
4040
"parameters": {
4141
"type": "object",
4242
"properties": {

spanish/function_calling_call.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333

3434

3535
def lookup_weather(city_name=None, zip_code=None):
36-
"""Buscar el clima para un nombre de ciudad o código postal dado."""
36+
"""Busca el clima para un nombre de ciudad o código postal dado."""
3737
print(f"Buscando el clima para {city_name or zip_code}...")
3838
return "¡Está soleado!"
3939

@@ -43,7 +43,7 @@ def lookup_weather(city_name=None, zip_code=None):
4343
"type": "function",
4444
"function": {
4545
"name": "lookup_weather",
46-
"description": "Buscar el clima para un nombre de ciudad o código postal dado.",
46+
"description": "Busca el clima para un nombre de ciudad o código postal dado.",
4747
"parameters": {
4848
"type": "object",
4949
"properties": {
@@ -67,7 +67,7 @@ def lookup_weather(city_name=None, zip_code=None):
6767
model=MODEL_NAME,
6868
messages=[
6969
{"role": "system", "content": "Eres un chatbot del clima."},
70-
{"role": "user", "content": "¿está soleado en esa pequeña ciudad cerca de Sydney donde vive Anthony?"},
70+
{"role": "user", "content": "¿está soleado en Berkeley, California?"},
7171
],
7272
tools=tools,
7373
tool_choice="auto",
@@ -81,3 +81,5 @@ def lookup_weather(city_name=None, zip_code=None):
8181
arguments = json.loads(tool_call.function.arguments)
8282
if function_name == "lookup_weather":
8383
lookup_weather(**arguments)
84+
else:
85+
print(response.choices[0].message.content)

spanish/function_calling_errors.py

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
import json
2+
import os
3+
from collections.abc import Callable
4+
from typing import Any
5+
6+
import azure.identity
7+
import openai
8+
from dotenv import load_dotenv
9+
10+
# Setup del cliente OpenAI para usar Azure, OpenAI.com, Ollama o GitHub Models (según variables de entorno)
11+
load_dotenv(override=True)
12+
API_HOST = os.getenv("API_HOST", "github")
13+
14+
if API_HOST == "azure":
15+
token_provider = azure.identity.get_bearer_token_provider(
16+
azure.identity.DefaultAzureCredential(), "https://cognitiveservices.azure.com/.default"
17+
)
18+
client = openai.OpenAI(
19+
base_url=os.environ["AZURE_OPENAI_ENDPOINT"],
20+
api_key=token_provider,
21+
)
22+
MODEL_NAME = os.environ["AZURE_OPENAI_CHAT_DEPLOYMENT"]
23+
24+
elif API_HOST == "ollama":
25+
client = openai.OpenAI(base_url=os.environ["OLLAMA_ENDPOINT"], api_key="nokeyneeded")
26+
MODEL_NAME = os.environ["OLLAMA_MODEL"]
27+
28+
elif API_HOST == "github":
29+
client = openai.OpenAI(base_url="https://models.github.ai/inference", api_key=os.environ["GITHUB_TOKEN"])
30+
MODEL_NAME = os.getenv("GITHUB_MODEL", "openai/gpt-4o")
31+
32+
else:
33+
client = openai.OpenAI(api_key=os.environ["OPENAI_KEY"])
34+
MODEL_NAME = os.environ["OPENAI_MODEL"]
35+
36+
37+
# ---------------------------------------------------------------------------
38+
# Implementación de la tool(s)
39+
# ---------------------------------------------------------------------------
40+
def search_database(search_query: str, price_filter: dict | None = None) -> dict[str, str]:
41+
"""Busca productos relevantes en la base de datos usando el query del usuario.
42+
43+
search_query: texto que quieres buscar (por ejemplo "playera roja").
44+
price_filter: objeto opcional con filtros de precio. Debe incluir:
45+
- comparison_operator: uno de ">", "<", ">=", "<=", "="
46+
- value: número límite para comparar.
47+
48+
Regresa una lista con productos dummy (ejemplo) para mostrar el flujo de function calling.
49+
"""
50+
if not search_query:
51+
raise ValueError("search_query es requerido")
52+
if price_filter:
53+
if "comparison_operator" not in price_filter or "value" not in price_filter:
54+
raise ValueError("Se requieren comparison_operator y value en price_filter")
55+
if price_filter["comparison_operator"] not in {">", "<", ">=", "<=", "="}:
56+
raise ValueError("comparison_operator inválido en price_filter")
57+
if not isinstance(price_filter["value"], int | float):
58+
raise ValueError("value en price_filter debe ser numérico")
59+
return [{"id": "123", "name": "Producto Ejemplo", "price": 19.99}]
60+
61+
62+
tool_mapping: dict[str, Callable[..., Any]] = {
63+
"search_database": search_database,
64+
}
65+
66+
tools = [
67+
{
68+
"type": "function",
69+
"function": {
70+
"name": "search_database",
71+
"description": "Busca en la base de datos productos relevantes según el query del usuario",
72+
"parameters": {
73+
"type": "object",
74+
"properties": {
75+
"search_query": {
76+
"type": "string",
77+
"description": "Texto (query) para búsqueda full text, ej: 'tenis rojos'",
78+
},
79+
"price_filter": {
80+
"type": "object",
81+
"description": "Filtra resultados según el precio del producto",
82+
"properties": {
83+
"comparison_operator": {
84+
"type": "string",
85+
"description": "Operador para comparar el valor de la columna: '>', '<', '>=', '<=', '='", # noqa
86+
},
87+
"value": {
88+
"type": "number",
89+
"description": "Valor límite para comparar, ej: 30",
90+
},
91+
},
92+
},
93+
},
94+
"required": ["search_query"],
95+
},
96+
},
97+
}
98+
]
99+
100+
messages: list[dict[str, Any]] = [
101+
{"role": "system", "content": "Eres un assistant que ayuda a buscar productos."},
102+
{"role": "user", "content": "Búscame una camiseta roja que cueste menos de $20."},
103+
]
104+
105+
print(f"Modelo: {MODEL_NAME} en Host: {API_HOST}\n")
106+
107+
# Primera respuesta del model (puede incluir una tool call)
108+
response = client.chat.completions.create(
109+
model=MODEL_NAME,
110+
messages=messages,
111+
tools=tools,
112+
tool_choice="auto",
113+
parallel_tool_calls=False,
114+
)
115+
116+
assistant_msg = response.choices[0].message
117+
118+
# Si el model no pidió ninguna tool call, solo imprime la respuesta.
119+
if not assistant_msg.tool_calls:
120+
print("Assistant:")
121+
print(assistant_msg.content)
122+
else:
123+
# Agrega el mensaje del assistant incluyendo metadata de la tool call
124+
messages.append(
125+
{
126+
"role": "assistant",
127+
"content": assistant_msg.content or "",
128+
"tool_calls": [tc.model_dump() for tc in assistant_msg.tool_calls],
129+
}
130+
)
131+
132+
# Procesa cada tool pedida de forma secuencial (normalmente solo una aquí)
133+
for tool_call in assistant_msg.tool_calls:
134+
fn_name = tool_call.function.name
135+
raw_args = tool_call.function.arguments or "{}"
136+
print(f"Tool request: {fn_name}({raw_args})")
137+
138+
target = tool_mapping.get(fn_name)
139+
if not target:
140+
tool_result: Any = f"ERROR: No hay implementación registrada para la tool '{fn_name}'"
141+
else:
142+
# Parseo seguro de argumentos JSON
143+
try:
144+
parsed_args = json.loads(raw_args) if raw_args.strip() else {}
145+
except json.JSONDecodeError:
146+
parsed_args = {}
147+
tool_result = "Warning: JSON arguments malformados; sigo con args vacíos"
148+
else:
149+
try:
150+
tool_result = target(**parsed_args)
151+
except Exception as e: # safeguard tool execution
152+
tool_result = f"Error ejecutando la tool {fn_name}: {e}"
153+
154+
# Serializa el output de la tool (dict o str) como JSON string para el model
155+
try:
156+
tool_content = json.dumps(tool_result)
157+
except Exception:
158+
# Fallback a string si algo no es serializable a JSON
159+
tool_content = json.dumps({"result": str(tool_result)})
160+
161+
messages.append(
162+
{
163+
"role": "tool",
164+
"tool_call_id": tool_call.id,
165+
"name": fn_name,
166+
"content": tool_content,
167+
}
168+
)
169+
170+
# Segunda respuesta del model después de darle los tool outputs
171+
followup = client.chat.completions.create(
172+
model=MODEL_NAME,
173+
messages=messages,
174+
tools=tools,
175+
)
176+
final_msg = followup.choices[0].message
177+
print("Assistant (final):")
178+
print(final_msg.content)

0 commit comments

Comments
 (0)