Agente de KB
Un agente cuyo único tool es `search` sobre una KB pequeña de productos. Maneja su propio retrieval — decide qué buscar, lee resultados, busca de nuevo si hace falta — y responde solo con lo que encontró.
Showcase — Agente de KB
Un agente cuyo único tool es search sobre una KB pequeña de productos. Maneja su propio retrieval — decide qué buscar, lee resultados, busca de nuevo si hace falta — y responde solo con lo que encontró.
Córrelo
bash bootstrap-secrets.sh # reads ../../../../.env, writes secrets/
docker compose up --build # default: PROVIDER=openai
Abre http://localhost:3000. Gemini: PROVIDER=gemini docker compose up --build.
Qué hay aquí
backend/ai_openai.py/backend/ai_gemini.py— el tool de search + el agent loop.frontend/app/page.tsx— caja de pregunta, respuesta fundamentada.
Detenlo
docker compose down
Ejecútalo en tu máquina
Descarga el proyecto como ZIP y córrelo con Docker. Levanta un backend FastAPI y un frontend Next.js en localhost:3000.
unzip kb-agent.zip
cd kb-agent
bash bootstrap-secrets.sh # one-time: pulls API keys into ./secrets
docker compose up --build # default provider: openai
# or: PROVIDER=gemini docker compose up --build
Escribe algo, elige un proveedor y ejecuta el mismo código de Código contra la API real. Requiere iniciar sesión.
Los mismos módulos que ejecuta el botón Run. El proyecto completo (frontend, Dockerfile, compose) está en el ZIP, pestaña README.
backend/ai_openai.py
"""Showcase 2 (OpenAI): a knowledge-base agent.
An agent whose one tool is `search` over a small company KB. It decides what to
look up, reads the results, and answers from them — sometimes searching more than
once to piece an answer together. Tools plus grounding: the agent version of RAG,
where the model drives retrieval instead of a fixed pipeline.
"""
import json
from openai import OpenAI
_client = OpenAI()
_MODEL = "gpt-5.4-nano"
_KB = [
{"topic": "pricing", "text": "Pro is $20/user/month billed annually, or $24 monthly. Free tier allows 2 users."},
{"topic": "support", "text": "Pro includes 24/5 chat support; Enterprise adds a dedicated success manager."},
{"topic": "security", "text": "Data is encrypted at rest (AES-256) and in transit (TLS 1.3). SOC 2 Type II certified."},
{"topic": "integrations", "text": "Native integrations: Slack, GitHub, Jira, Google Drive. A REST API covers the rest."},
{"topic": "limits", "text": "Free tier: 100 API calls/day. Pro: 10,000/day. Enterprise: negotiable."},
{"topic": "onboarding", "text": "Enterprise plans include a guided onboarding and data migration from most competitors."},
]
def search(query: str) -> dict:
words = [w for w in query.lower().split() if len(w) > 2]
hits = [e for e in _KB if any(w in (e["text"] + " " + e["topic"]).lower() for w in words)]
return {"results": hits[:4], "note": "" if hits else "no matches — try different keywords"}
_IMPL = {"search": search}
_TOOLS = [{"type": "function", "name": "search",
"description": "Search the company knowledge base by keywords. Returns matching entries.",
"parameters": {"type": "object", "properties": {"query": {"type": "string"}}, "required": ["query"]}}]
_GOAL = ("You are a support agent for a SaaS product. Use `search` to find facts before "
"answering — search more than once if needed. Answer ONLY from search results; if "
"nothing relevant is found, say you don't have that information.")
def run(question: str) -> str:
q = question.strip()
if not q:
return "Ask a question about the product (pricing, support, security, integrations, limits)."
input_list = [{"role": "user", "content": q}]
response = None
for _ in range(6):
response = _client.responses.create(model=_MODEL, instructions=_GOAL, input=input_list, tools=_TOOLS)
calls = [i for i in response.output if i.type == "function_call"]
if not calls:
break
input_list += response.output
for c in calls:
result = _IMPL[c.name](**json.loads(c.arguments))
input_list.append({"type": "function_call_output", "call_id": c.call_id, "output": json.dumps(result)})
return response.output_text if response else ""
backend/ai_gemini.py
"""Showcase 2 (Gemini): a knowledge-base agent.
Same `search`-tool agent, Gemini's tool-calling.
"""
import os
from google import genai
from google.genai import types
_client = genai.Client(api_key=os.environ["GEMINI_API_KEY"])
_MODEL = "gemini-3.1-flash-lite"
_KB = [
{"topic": "pricing", "text": "Pro is $20/user/month billed annually, or $24 monthly. Free tier allows 2 users."},
{"topic": "support", "text": "Pro includes 24/5 chat support; Enterprise adds a dedicated success manager."},
{"topic": "security", "text": "Data is encrypted at rest (AES-256) and in transit (TLS 1.3). SOC 2 Type II certified."},
{"topic": "integrations", "text": "Native integrations: Slack, GitHub, Jira, Google Drive. A REST API covers the rest."},
{"topic": "limits", "text": "Free tier: 100 API calls/day. Pro: 10,000/day. Enterprise: negotiable."},
{"topic": "onboarding", "text": "Enterprise plans include a guided onboarding and data migration from most competitors."},
]
def search(query: str) -> dict:
words = [w for w in query.lower().split() if len(w) > 2]
hits = [e for e in _KB if any(w in (e["text"] + " " + e["topic"]).lower() for w in words)]
return {"results": hits[:4], "note": "" if hits else "no matches — try different keywords"}
_IMPL = {"search": search}
_CONFIG = types.GenerateContentConfig(
system_instruction=("You are a support agent for a SaaS product. Use `search` to find facts "
"before answering — search more than once if needed. Answer ONLY from search "
"results; if nothing relevant is found, say you don't have that information."),
tools=[types.Tool(function_declarations=[
types.FunctionDeclaration(name="search", description="Search the company knowledge base by keywords.",
parameters=types.Schema(type=types.Type.OBJECT,
properties={"query": types.Schema(type=types.Type.STRING)}, required=["query"]))])],
)
def run(question: str) -> str:
q = question.strip()
if not q:
return "Ask a question about the product (pricing, support, security, integrations, limits)."
contents = [types.Content(role="user", parts=[types.Part(text=q)])]
response = None
for _ in range(6):
response = _client.models.generate_content(model=_MODEL, contents=contents, config=_CONFIG)
if not response.function_calls:
break
contents.append(response.candidates[0].content)
parts = [types.Part.from_function_response(name=fc.name, response=_IMPL[fc.name](**dict(fc.args)))
for fc in response.function_calls]
contents.append(types.Content(role="user", parts=parts))
return (response.text or "") if response else ""
Archivos del proyecto
.gitignoreREADME.es.mdREADME.mdbackend/Dockerfilebackend/ai_gemini.pybackend/ai_openai.pybackend/main.pybackend/requirements.txtbootstrap-secrets.shdocker-compose.ymlfrontend/Dockerfilefrontend/app/layout.tsxfrontend/app/page.tsxfrontend/next.config.tsfrontend/package.jsonfrontend/tsconfig.json