diff --git a/python-backend/api.py b/python-backend/api.py index f1c1334..a5cd53b 100644 --- a/python-backend/api.py +++ b/python-backend/api.py @@ -5,6 +5,12 @@ from uuid import uuid4 import time import logging +from dotenv import load_dotenv + +# Import do logger personalizado +from llm_logger import llm_logger + +load_dotenv() from main import ( triage_agent, @@ -91,12 +97,22 @@ def save(self, conversation_id: str, state: Dict[str, Any]): class InMemoryConversationStore(ConversationStore): _conversations: Dict[str, Dict[str, Any]] = {} + _conversation_start_times: Dict[str, float] = {} def get(self, conversation_id: str) -> Optional[Dict[str, Any]]: return self._conversations.get(conversation_id) def save(self, conversation_id: str, state: Dict[str, Any]): self._conversations[conversation_id] = state + # Registra o tempo de início se for uma nova conversa + if conversation_id not in self._conversation_start_times: + self._conversation_start_times[conversation_id] = time.time() + + def get_conversation_duration(self, conversation_id: str) -> Optional[float]: + """Retorna a duração da conversa em segundos.""" + if conversation_id in self._conversation_start_times: + return time.time() - self._conversation_start_times[conversation_id] + return None # TODO: when deploying this app in scale, switch to your own production-ready implementation conversation_store = InMemoryConversationStore() @@ -168,6 +184,14 @@ async def chat_endpoint(req: ChatRequest): "context": ctx, "current_agent": current_agent_name, } + + # Log início da conversa + llm_logger.log_conversation_start( + conversation_id=conversation_id, + initial_agent=current_agent_name, + context=ctx.model_dump() if ctx else {} + ) + if req.message.strip() == "": conversation_store.save(conversation_id, state) return ChatResponse( @@ -175,7 +199,7 @@ async def chat_endpoint(req: ChatRequest): current_agent=current_agent_name, messages=[], events=[], - context=ctx.model_dump(), + context=ctx.model_dump() if ctx else {}, agents=_build_agents_list(), guardrails=[], ) @@ -185,17 +209,46 @@ async def chat_endpoint(req: ChatRequest): current_agent = _get_agent_by_name(state["current_agent"]) state["input_items"].append({"content": req.message, "role": "user"}) - old_context = state["context"].model_dump().copy() + old_context = (state["context"].model_dump() if state["context"] else {}).copy() guardrail_checks: List[GuardrailCheck] = [] + # Log da requisição para LLM + llm_logger.log_llm_request( + agent_name=current_agent.name, + model=getattr(current_agent, "model", "unknown"), + input_data=req.message, + conversation_id=conversation_id, + context=state["context"].model_dump() if state["context"] else {} + ) + try: result = await Runner.run(current_agent, state["input_items"], context=state["context"]) + + # Log da resposta da LLM + llm_logger.log_llm_response( + agent_name=current_agent.name, + model=getattr(current_agent, "model", "unknown"), + response=result.new_items, + conversation_id=conversation_id, + metadata={"final_output": str(result.final_output) if hasattr(result, 'final_output') else None} + ) + except InputGuardrailTripwireTriggered as e: failed = e.guardrail_result.guardrail gr_output = e.guardrail_result.output.output_info gr_reasoning = getattr(gr_output, "reasoning", "") gr_input = req.message gr_timestamp = time.time() * 1000 + + # Log do erro de guardrail + llm_logger.log_error( + agent_name=current_agent.name, + error_type="guardrail_tripwire", + error_message=f"Guardrail '{_get_guardrail_name(failed)}' triggered", + conversation_id=conversation_id, + context={"reasoning": gr_reasoning, "input": gr_input} + ) + for g in current_agent.input_guardrails: guardrail_checks.append(GuardrailCheck( id=uuid4().hex, @@ -205,6 +258,17 @@ async def chat_endpoint(req: ChatRequest): passed=(g != failed), timestamp=gr_timestamp, )) + + # Log individual de cada guardrail + llm_logger.log_guardrail_check( + agent_name=current_agent.name, + guardrail_name=_get_guardrail_name(g), + input_text=gr_input, + passed=(g != failed), + reasoning=(gr_reasoning if g == failed else ""), + conversation_id=conversation_id + ) + refusal = "Sorry, I can only answer questions related to airline travel." state["input_items"].append({"role": "assistant", "content": refusal}) return ChatResponse( @@ -212,7 +276,7 @@ async def chat_endpoint(req: ChatRequest): current_agent=current_agent.name, messages=[MessageResponse(content=refusal, agent=current_agent.name)], events=[], - context=state["context"].model_dump(), + context=state["context"].model_dump() if state["context"] else {}, agents=_build_agents_list(), guardrails=guardrail_checks, ) @@ -225,8 +289,18 @@ async def chat_endpoint(req: ChatRequest): text = ItemHelpers.text_message_output(item) messages.append(MessageResponse(content=text, agent=item.agent.name)) events.append(AgentEvent(id=uuid4().hex, type="message", agent=item.agent.name, content=text)) + # Handle handoff output and agent switching elif isinstance(item, HandoffOutputItem): + # Log da transição de agente + llm_logger.log_agent_transition( + from_agent=item.source_agent.name, + to_agent=item.target_agent.name, + reason="handoff_triggered", + conversation_id=conversation_id, + context=state["context"].model_dump() if state["context"] else {} + ) + # Record the handoff event events.append( AgentEvent( @@ -264,6 +338,7 @@ async def chat_endpoint(req: ChatRequest): ) ) current_agent = item.target_agent + elif isinstance(item, ToolCallItem): tool_name = getattr(item.raw_item, "name", None) raw_args = getattr(item.raw_item, "arguments", None) @@ -274,6 +349,15 @@ async def chat_endpoint(req: ChatRequest): tool_args = json.loads(raw_args) except Exception: pass + + # Log da chamada de ferramenta + llm_logger.log_tool_call( + agent_name=item.agent.name, + tool_name=tool_name or "unknown", + tool_args=tool_args, + conversation_id=conversation_id + ) + events.append( AgentEvent( id=uuid4().hex, @@ -291,7 +375,16 @@ async def chat_endpoint(req: ChatRequest): agent=item.agent.name, ) ) + elif isinstance(item, ToolCallOutputItem): + # Log do resultado da ferramenta + llm_logger.log_tool_result( + agent_name=item.agent.name, + tool_name="unknown", # Não temos acesso direto ao nome da ferramenta aqui + result=item.output, + conversation_id=conversation_id + ) + events.append( AgentEvent( id=uuid4().hex, @@ -302,7 +395,7 @@ async def chat_endpoint(req: ChatRequest): ) ) - new_context = state["context"].dict() + new_context = state["context"].dict() if state["context"] else {} changes = {k: new_context[k] for k in new_context if old_context.get(k) != new_context[k]} if changes: events.append( @@ -327,6 +420,16 @@ async def chat_endpoint(req: ChatRequest): if failed: final_guardrails.append(failed) else: + # Log de guardrails que passaram + llm_logger.log_guardrail_check( + agent_name=current_agent.name, + guardrail_name=name, + input_text=req.message, + passed=True, + reasoning="", + conversation_id=conversation_id + ) + final_guardrails.append(GuardrailCheck( id=uuid4().hex, name=name, @@ -341,7 +444,60 @@ async def chat_endpoint(req: ChatRequest): current_agent=current_agent.name, messages=messages, events=events, - context=state["context"].dict(), + context=state["context"].dict() if state["context"] else {}, agents=_build_agents_list(), guardrails=final_guardrails, ) + +@app.get("/logs") +async def get_logs(limit: int = 100): + """ + Endpoint para visualizar os logs da aplicação. + Retorna as últimas entradas do arquivo llm.log + """ + try: + with open("llm.log", "r", encoding="utf-8") as f: + lines = f.readlines() + # Retorna as últimas 'limit' linhas + recent_logs = lines[-limit:] if len(lines) > limit else lines + return { + "logs": recent_logs, + "total_lines": len(lines), + "showing_last": len(recent_logs) + } + except FileNotFoundError: + return { + "logs": [], + "total_lines": 0, + "showing_last": 0, + "message": "Arquivo de log não encontrado" + } + except Exception as e: + return { + "logs": [], + "total_lines": 0, + "showing_last": 0, + "error": str(e) + } + +@app.get("/logs/download") +async def download_logs(): + """ + Endpoint para baixar o arquivo de log completo + """ + try: + with open("llm.log", "r", encoding="utf-8") as f: + content = f.read() + return { + "content": content, + "filename": "llm.log", + "size": len(content) + } + except FileNotFoundError: + return { + "error": "Arquivo de log não encontrado" + } + except Exception as e: + return { + "error": str(e) + } diff --git a/python-backend/llm.log.example b/python-backend/llm.log.example new file mode 100644 index 0000000..cacbd7c --- /dev/null +++ b/python-backend/llm.log.example @@ -0,0 +1,54 @@ +2025-06-25 18:03:19 | INFO | CONVERSATION START: {"timestamp": "2025-06-25T18:03:19.741730", "type": "conversation_start", "conversation_id": "37c58194a04d4f5f824753a96182d173", "initial_agent": "Triage Agent", "context": {"passenger_name": null, "confirmation_number": null, "seat_number": null, "flight_number": null, "account_number": "32723827"}} +2025-06-25 18:03:21 | INFO | CONVERSATION START: {"timestamp": "2025-06-25T18:03:21.233488", "type": "conversation_start", "conversation_id": "1eec97a8eb3249e29d0d7196b3b92dfb", "initial_agent": "Triage Agent", "context": {"passenger_name": null, "confirmation_number": null, "seat_number": null, "flight_number": null, "account_number": "97212009"}} +2025-06-25 18:03:27 | INFO | LLM REQUEST: {"timestamp": "2025-06-25T18:03:27.066866", "type": "llm_request", "agent": "Triage Agent", "model": "gpt-4.1", "conversation_id": "37c58194a04d4f5f824753a96182d173", "input": "oi bom dia", "context": {"passenger_name": null, "confirmation_number": null, "seat_number": null, "flight_number": null, "account_number": "32723827"}} +2025-06-25 18:03:37 | INFO | LLM RESPONSE: {"timestamp": "2025-06-25T18:03:37.532295", "type": "llm_response", "agent": "Triage Agent", "model": "gpt-4.1", "conversation_id": "37c58194a04d4f5f824753a96182d173", "response": ["MessageOutputItem(agent=Agent(name='Triage Agent', instructions='# System context\\nYou are part of a multi-agent system called the Agents SDK, designed to make agent coordination and execution easy. Agents uses two primary abstraction: **Agents** and **Handoffs**. An agent encompasses instructions and tools and can hand off a conversation to another agent when appropriate. Handoffs are achieved by calling a handoff function, generally named `transfer_to_`. Transfers between agents ar"], "metadata": {"final_output": "Oi, bom dia! Como posso te ajudar hoje?"}} +2025-06-25 18:03:37 | INFO | GUARDRAIL CHECK: {"timestamp": "2025-06-25T18:03:37.533469", "type": "guardrail_check", "agent": "Triage Agent", "guardrail": "Relevance Guardrail", "conversation_id": "37c58194a04d4f5f824753a96182d173", "input": "oi bom dia", "passed": true, "reasoning": ""} +2025-06-25 18:03:37 | INFO | GUARDRAIL CHECK: {"timestamp": "2025-06-25T18:03:37.533665", "type": "guardrail_check", "agent": "Triage Agent", "guardrail": "Jailbreak Guardrail", "conversation_id": "37c58194a04d4f5f824753a96182d173", "input": "oi bom dia", "passed": true, "reasoning": ""} +2025-06-25 18:04:22 | INFO | LLM REQUEST: {"timestamp": "2025-06-25T18:04:22.464296", "type": "llm_request", "agent": "Triage Agent", "model": "gpt-4.1", "conversation_id": "37c58194a04d4f5f824753a96182d173", "input": "queria fazer mudar o assento", "context": {"passenger_name": null, "confirmation_number": null, "seat_number": null, "flight_number": null, "account_number": "32723827"}} +2025-06-25 18:04:24 | INFO | AGENT TRANSITION: {"timestamp": "2025-06-25T18:04:24.283320", "type": "agent_transition", "from_agent": "Triage Agent", "to_agent": "Seat Booking Agent", "conversation_id": null, "reason": "seat_booking_handoff_hook", "context": {"passenger_name": null, "confirmation_number": null, "seat_number": null, "flight_number": null, "account_number": "32723827"}} +2025-06-25 18:04:27 | INFO | LLM RESPONSE: {"timestamp": "2025-06-25T18:04:27.684075", "type": "llm_response", "agent": "Triage Agent", "model": "gpt-4.1", "conversation_id": "37c58194a04d4f5f824753a96182d173", "response": ["HandoffCallItem(agent=Agent(name='Triage Agent', instructions='# System context\\nYou are part of a multi-agent system called the Agents SDK, designed to make agent coordination and execution easy. Agents uses two primary abstraction: **Agents** and **Handoffs**. An agent encompasses instructions and tools and can hand off a conversation to another agent when appropriate. Handoffs are achieved by calling a handoff function, generally named `transfer_to_`. Transfers between agents are ", "HandoffOutputItem(agent=Agent(name='Triage Agent', instructions='# System context\\nYou are part of a multi-agent system called the Agents SDK, designed to make agent coordination and execution easy. Agents uses two primary abstraction: **Agents** and **Handoffs**. An agent encompasses instructions and tools and can hand off a conversation to another agent when appropriate. Handoffs are achieved by calling a handoff function, generally named `transfer_to_`. Transfers between agents ar", "MessageOutputItem(agent=Agent(name='Seat Booking Agent', instructions=, prompt=None, handoff_description='A helpful agent that can update a seat on a flight.', handoffs=[Agent(name='Triage Agent', instructions='# System context\\nYou are part of a multi-agent system called the Agents SDK, designed to make agent coordination and execution easy. Agents uses two primary abstraction: **Agents** and **Handoffs**. An agent encompasses instructions a"], "metadata": {"final_output": "Claro! Seu número de confirmação é 144A3P, correto?\n\nVocê já tem um assento em mente ou prefere que eu mostre o mapa de assentos para você escolher?"}} +2025-06-25 18:04:27 | INFO | AGENT TRANSITION: {"timestamp": "2025-06-25T18:04:27.684807", "type": "agent_transition", "from_agent": "Triage Agent", "to_agent": "Seat Booking Agent", "conversation_id": "37c58194a04d4f5f824753a96182d173", "reason": "handoff_triggered", "context": {"passenger_name": null, "confirmation_number": "144A3P", "seat_number": null, "flight_number": "FLT-341", "account_number": "32723827"}} +2025-06-25 18:04:27 | INFO | GUARDRAIL CHECK: {"timestamp": "2025-06-25T18:04:27.685032", "type": "guardrail_check", "agent": "Seat Booking Agent", "guardrail": "Relevance Guardrail", "conversation_id": "37c58194a04d4f5f824753a96182d173", "input": "queria fazer mudar o assento", "passed": true, "reasoning": ""} +2025-06-25 18:04:27 | INFO | GUARDRAIL CHECK: {"timestamp": "2025-06-25T18:04:27.685112", "type": "guardrail_check", "agent": "Seat Booking Agent", "guardrail": "Jailbreak Guardrail", "conversation_id": "37c58194a04d4f5f824753a96182d173", "input": "queria fazer mudar o assento", "passed": true, "reasoning": ""} +2025-06-25 18:04:54 | INFO | LLM REQUEST: {"timestamp": "2025-06-25T18:04:54.793918", "type": "llm_request", "agent": "Seat Booking Agent", "model": "gpt-4.1", "conversation_id": "37c58194a04d4f5f824753a96182d173", "input": "primeira fila primeira cadeira", "context": {"passenger_name": null, "confirmation_number": "144A3P", "seat_number": null, "flight_number": "FLT-341", "account_number": "32723827"}} +2025-06-25 18:04:56 | INFO | TOOL CALL: {"timestamp": "2025-06-25T18:04:56.562740", "type": "tool_call", "agent": "Seat Booking Agent", "tool": "update_seat", "conversation_id": null, "arguments": {"confirmation_number": "144A3P", "new_seat": "1A"}} +2025-06-25 18:04:56 | INFO | TOOL RESULT: {"timestamp": "2025-06-25T18:04:56.562904", "type": "tool_result", "agent": "Seat Booking Agent", "tool": "update_seat", "conversation_id": null, "result": "Updated seat to 1A for confirmation number 144A3P"} +2025-06-25 18:04:57 | INFO | LLM RESPONSE: {"timestamp": "2025-06-25T18:04:57.690586", "type": "llm_response", "agent": "Seat Booking Agent", "model": "gpt-4.1", "conversation_id": "37c58194a04d4f5f824753a96182d173", "response": ["ToolCallItem(agent=Agent(name='Seat Booking Agent', instructions=, prompt=None, handoff_description='A helpful agent that can update a seat on a flight.', handoffs=[Agent(name='Triage Agent', instructions='# System context\\nYou are part of a multi-agent system called the Agents SDK, designed to make agent coordination and execution easy. Agents uses two primary abstraction: **Agents** and **Handoffs**. An agent encompasses instructions and to", "ToolCallOutputItem(agent=Agent(name='Seat Booking Agent', instructions=, prompt=None, handoff_description='A helpful agent that can update a seat on a flight.', handoffs=[Agent(name='Triage Agent', instructions='# System context\\nYou are part of a multi-agent system called the Agents SDK, designed to make agent coordination and execution easy. Agents uses two primary abstraction: **Agents** and **Handoffs**. An agent encompasses instructions ", "MessageOutputItem(agent=Agent(name='Seat Booking Agent', instructions=, prompt=None, handoff_description='A helpful agent that can update a seat on a flight.', handoffs=[Agent(name='Triage Agent', instructions='# System context\\nYou are part of a multi-agent system called the Agents SDK, designed to make agent coordination and execution easy. Agents uses two primary abstraction: **Agents** and **Handoffs**. An agent encompasses instructions a"], "metadata": {"final_output": "Pronto! O seu assento foi alterado para a primeira fila, cadeira 1A.\n\nSe precisar de mais alguma coisa, estou à disposição!"}} +2025-06-25 18:04:57 | INFO | TOOL CALL: {"timestamp": "2025-06-25T18:04:57.691176", "type": "tool_call", "agent": "Seat Booking Agent", "tool": "update_seat", "conversation_id": "37c58194a04d4f5f824753a96182d173", "arguments": {"confirmation_number": "144A3P", "new_seat": "1A"}} +2025-06-25 18:04:57 | INFO | TOOL RESULT: {"timestamp": "2025-06-25T18:04:57.691297", "type": "tool_result", "agent": "Seat Booking Agent", "tool": "unknown", "conversation_id": "37c58194a04d4f5f824753a96182d173", "result": "Updated seat to 1A for confirmation number 144A3P"} +2025-06-25 18:04:57 | INFO | GUARDRAIL CHECK: {"timestamp": "2025-06-25T18:04:57.691516", "type": "guardrail_check", "agent": "Seat Booking Agent", "guardrail": "Relevance Guardrail", "conversation_id": "37c58194a04d4f5f824753a96182d173", "input": "primeira fila primeira cadeira", "passed": true, "reasoning": ""} +2025-06-25 18:04:57 | INFO | GUARDRAIL CHECK: {"timestamp": "2025-06-25T18:04:57.691607", "type": "guardrail_check", "agent": "Seat Booking Agent", "guardrail": "Jailbreak Guardrail", "conversation_id": "37c58194a04d4f5f824753a96182d173", "input": "primeira fila primeira cadeira", "passed": true, "reasoning": ""} +2025-06-25 18:05:15 | INFO | LLM REQUEST: {"timestamp": "2025-06-25T18:05:15.742166", "type": "llm_request", "agent": "Seat Booking Agent", "model": "gpt-4.1", "conversation_id": "37c58194a04d4f5f824753a96182d173", "input": "queria saber qual o tamanho da mala de bordo que posso levar", "context": {"passenger_name": null, "confirmation_number": "144A3P", "seat_number": "1A", "flight_number": "FLT-341", "account_number": "32723827"}} +2025-06-25 18:05:24 | INFO | TOOL CALL: {"timestamp": "2025-06-25T18:05:24.767439", "type": "tool_call", "agent": "FAQ Agent", "tool": "faq_lookup_tool", "conversation_id": null, "arguments": {"question": "qual o tamanho da mala de bordo que posso levar"}} +2025-06-25 18:05:24 | INFO | TOOL RESULT: {"timestamp": "2025-06-25T18:05:24.767541", "type": "tool_result", "agent": "FAQ Agent", "tool": "faq_lookup_tool", "conversation_id": null, "result": "I'm sorry, I don't know the answer to that question."} +2025-06-25 18:05:26 | INFO | LLM RESPONSE: {"timestamp": "2025-06-25T18:05:26.077493", "type": "llm_response", "agent": "Seat Booking Agent", "model": "gpt-4.1", "conversation_id": "37c58194a04d4f5f824753a96182d173", "response": ["HandoffCallItem(agent=Agent(name='Seat Booking Agent', instructions=, prompt=None, handoff_description='A helpful agent that can update a seat on a flight.', handoffs=[Agent(name='Triage Agent', instructions='# System context\\nYou are part of a multi-agent system called the Agents SDK, designed to make agent coordination and execution easy. Agents uses two primary abstraction: **Agents** and **Handoffs**. An agent encompasses instructions and", "HandoffOutputItem(agent=Agent(name='Seat Booking Agent', instructions=, prompt=None, handoff_description='A helpful agent that can update a seat on a flight.', handoffs=[Agent(name='Triage Agent', instructions='# System context\\nYou are part of a multi-agent system called the Agents SDK, designed to make agent coordination and execution easy. Agents uses two primary abstraction: **Agents** and **Handoffs**. An agent encompasses instructions a", "HandoffCallItem(agent=Agent(name='Triage Agent', instructions='# System context\\nYou are part of a multi-agent system called the Agents SDK, designed to make agent coordination and execution easy. Agents uses two primary abstraction: **Agents** and **Handoffs**. An agent encompasses instructions and tools and can hand off a conversation to another agent when appropriate. Handoffs are achieved by calling a handoff function, generally named `transfer_to_`. Transfers between agents are ", "HandoffOutputItem(agent=Agent(name='Triage Agent', instructions='# System context\\nYou are part of a multi-agent system called the Agents SDK, designed to make agent coordination and execution easy. Agents uses two primary abstraction: **Agents** and **Handoffs**. An agent encompasses instructions and tools and can hand off a conversation to another agent when appropriate. Handoffs are achieved by calling a handoff function, generally named `transfer_to_`. Transfers between agents ar", "ToolCallItem(agent=Agent(name='FAQ Agent', instructions='# System context\\nYou are part of a multi-agent system called the Agents SDK, designed to make agent coordination and execution easy. Agents uses two primary abstraction: **Agents** and **Handoffs**. An agent encompasses instructions and tools and can hand off a conversation to another agent when appropriate. Handoffs are achieved by calling a handoff function, generally named `transfer_to_`. Transfers between agents are handle", "ToolCallOutputItem(agent=Agent(name='FAQ Agent', instructions='# System context\\nYou are part of a multi-agent system called the Agents SDK, designed to make agent coordination and execution easy. Agents uses two primary abstraction: **Agents** and **Handoffs**. An agent encompasses instructions and tools and can hand off a conversation to another agent when appropriate. Handoffs are achieved by calling a handoff function, generally named `transfer_to_`. Transfers between agents are ", "MessageOutputItem(agent=Agent(name='FAQ Agent', instructions='# System context\\nYou are part of a multi-agent system called the Agents SDK, designed to make agent coordination and execution easy. Agents uses two primary abstraction: **Agents** and **Handoffs**. An agent encompasses instructions and tools and can hand off a conversation to another agent when appropriate. Handoffs are achieved by calling a handoff function, generally named `transfer_to_`. Transfers between agents are h"], "metadata": {"final_output": "Desculpe, não encontrei a resposta para o tamanho permitido da mala de bordo. Recomendo verificar diretamente com a companhia aérea ou no seu bilhete para obter as informações corretas. Posso ajudar em mais alguma coisa?"}} +2025-06-25 18:05:26 | INFO | AGENT TRANSITION: {"timestamp": "2025-06-25T18:05:26.078768", "type": "agent_transition", "from_agent": "Seat Booking Agent", "to_agent": "Triage Agent", "conversation_id": "37c58194a04d4f5f824753a96182d173", "reason": "handoff_triggered", "context": {"passenger_name": null, "confirmation_number": "144A3P", "seat_number": "1A", "flight_number": "FLT-341", "account_number": "32723827"}} +2025-06-25 18:05:26 | INFO | AGENT TRANSITION: {"timestamp": "2025-06-25T18:05:26.078891", "type": "agent_transition", "from_agent": "Triage Agent", "to_agent": "FAQ Agent", "conversation_id": "37c58194a04d4f5f824753a96182d173", "reason": "handoff_triggered", "context": {"passenger_name": null, "confirmation_number": "144A3P", "seat_number": "1A", "flight_number": "FLT-341", "account_number": "32723827"}} +2025-06-25 18:05:26 | INFO | TOOL CALL: {"timestamp": "2025-06-25T18:05:26.079006", "type": "tool_call", "agent": "FAQ Agent", "tool": "faq_lookup_tool", "conversation_id": "37c58194a04d4f5f824753a96182d173", "arguments": {"question": "qual o tamanho da mala de bordo que posso levar"}} +2025-06-25 18:05:26 | INFO | TOOL RESULT: {"timestamp": "2025-06-25T18:05:26.079062", "type": "tool_result", "agent": "FAQ Agent", "tool": "unknown", "conversation_id": "37c58194a04d4f5f824753a96182d173", "result": "I'm sorry, I don't know the answer to that question."} +2025-06-25 18:05:26 | INFO | GUARDRAIL CHECK: {"timestamp": "2025-06-25T18:05:26.079333", "type": "guardrail_check", "agent": "FAQ Agent", "guardrail": "Relevance Guardrail", "conversation_id": "37c58194a04d4f5f824753a96182d173", "input": "queria saber qual o tamanho da mala de bordo que posso levar", "passed": true, "reasoning": ""} +2025-06-25 18:05:26 | INFO | GUARDRAIL CHECK: {"timestamp": "2025-06-25T18:05:26.079492", "type": "guardrail_check", "agent": "FAQ Agent", "guardrail": "Jailbreak Guardrail", "conversation_id": "37c58194a04d4f5f824753a96182d173", "input": "queria saber qual o tamanho da mala de bordo que posso levar", "passed": true, "reasoning": ""} +2025-06-25 18:05:44 | INFO | LLM REQUEST: {"timestamp": "2025-06-25T18:05:44.891897", "type": "llm_request", "agent": "FAQ Agent", "model": "gpt-4.1", "conversation_id": "37c58194a04d4f5f824753a96182d173", "input": "o que posso levar na viagem", "context": {"passenger_name": null, "confirmation_number": "144A3P", "seat_number": "1A", "flight_number": "FLT-341", "account_number": "32723827"}} +2025-06-25 18:05:46 | INFO | TOOL CALL: {"timestamp": "2025-06-25T18:05:46.023238", "type": "tool_call", "agent": "FAQ Agent", "tool": "faq_lookup_tool", "conversation_id": null, "arguments": {"question": "o que posso levar na viagem"}} +2025-06-25 18:05:46 | INFO | TOOL RESULT: {"timestamp": "2025-06-25T18:05:46.023392", "type": "tool_result", "agent": "FAQ Agent", "tool": "faq_lookup_tool", "conversation_id": null, "result": "I'm sorry, I don't know the answer to that question."} +2025-06-25 18:05:48 | INFO | LLM RESPONSE: {"timestamp": "2025-06-25T18:05:48.284701", "type": "llm_response", "agent": "FAQ Agent", "model": "gpt-4.1", "conversation_id": "37c58194a04d4f5f824753a96182d173", "response": ["ToolCallItem(agent=Agent(name='FAQ Agent', instructions='# System context\\nYou are part of a multi-agent system called the Agents SDK, designed to make agent coordination and execution easy. Agents uses two primary abstraction: **Agents** and **Handoffs**. An agent encompasses instructions and tools and can hand off a conversation to another agent when appropriate. Handoffs are achieved by calling a handoff function, generally named `transfer_to_`. Transfers between agents are handle", "ToolCallOutputItem(agent=Agent(name='FAQ Agent', instructions='# System context\\nYou are part of a multi-agent system called the Agents SDK, designed to make agent coordination and execution easy. Agents uses two primary abstraction: **Agents** and **Handoffs**. An agent encompasses instructions and tools and can hand off a conversation to another agent when appropriate. Handoffs are achieved by calling a handoff function, generally named `transfer_to_`. Transfers between agents are ", "MessageOutputItem(agent=Agent(name='FAQ Agent', instructions='# System context\\nYou are part of a multi-agent system called the Agents SDK, designed to make agent coordination and execution easy. Agents uses two primary abstraction: **Agents** and **Handoffs**. An agent encompasses instructions and tools and can hand off a conversation to another agent when appropriate. Handoffs are achieved by calling a handoff function, generally named `transfer_to_`. Transfers between agents are h"], "metadata": {"final_output": "Desculpe, não encontrei a resposta sobre o que pode ser levado na viagem. Recomendo verificar as regras da companhia aérea ou consultar o site oficial da empresa para detalhes sobre itens permitidos. Se precisar de alguma outra informação, por favor, me avise!"}} +2025-06-25 18:05:48 | INFO | TOOL CALL: {"timestamp": "2025-06-25T18:05:48.285124", "type": "tool_call", "agent": "FAQ Agent", "tool": "faq_lookup_tool", "conversation_id": "37c58194a04d4f5f824753a96182d173", "arguments": {"question": "o que posso levar na viagem"}} +2025-06-25 18:05:48 | INFO | TOOL RESULT: {"timestamp": "2025-06-25T18:05:48.285189", "type": "tool_result", "agent": "FAQ Agent", "tool": "unknown", "conversation_id": "37c58194a04d4f5f824753a96182d173", "result": "I'm sorry, I don't know the answer to that question."} +2025-06-25 18:05:48 | INFO | GUARDRAIL CHECK: {"timestamp": "2025-06-25T18:05:48.285393", "type": "guardrail_check", "agent": "FAQ Agent", "guardrail": "Relevance Guardrail", "conversation_id": "37c58194a04d4f5f824753a96182d173", "input": "o que posso levar na viagem", "passed": true, "reasoning": ""} +2025-06-25 18:05:48 | INFO | GUARDRAIL CHECK: {"timestamp": "2025-06-25T18:05:48.285452", "type": "guardrail_check", "agent": "FAQ Agent", "guardrail": "Jailbreak Guardrail", "conversation_id": "37c58194a04d4f5f824753a96182d173", "input": "o que posso levar na viagem", "passed": true, "reasoning": ""} +2025-06-25 18:06:04 | INFO | LLM REQUEST: {"timestamp": "2025-06-25T18:06:04.510773", "type": "llm_request", "agent": "FAQ Agent", "model": "gpt-4.1", "conversation_id": "37c58194a04d4f5f824753a96182d173", "input": "qual o numero do meu assento?", "context": {"passenger_name": null, "confirmation_number": "144A3P", "seat_number": "1A", "flight_number": "FLT-341", "account_number": "32723827"}} +2025-06-25 18:06:05 | INFO | TOOL CALL: {"timestamp": "2025-06-25T18:06:05.799376", "type": "tool_call", "agent": "FAQ Agent", "tool": "faq_lookup_tool", "conversation_id": null, "arguments": {"question": "qual o número do meu assento"}} +2025-06-25 18:06:05 | INFO | TOOL RESULT: {"timestamp": "2025-06-25T18:06:05.799496", "type": "tool_result", "agent": "FAQ Agent", "tool": "faq_lookup_tool", "conversation_id": null, "result": "I'm sorry, I don't know the answer to that question."} +2025-06-25 18:06:08 | INFO | LLM RESPONSE: {"timestamp": "2025-06-25T18:06:08.462053", "type": "llm_response", "agent": "FAQ Agent", "model": "gpt-4.1", "conversation_id": "37c58194a04d4f5f824753a96182d173", "response": ["ToolCallItem(agent=Agent(name='FAQ Agent', instructions='# System context\\nYou are part of a multi-agent system called the Agents SDK, designed to make agent coordination and execution easy. Agents uses two primary abstraction: **Agents** and **Handoffs**. An agent encompasses instructions and tools and can hand off a conversation to another agent when appropriate. Handoffs are achieved by calling a handoff function, generally named `transfer_to_`. Transfers between agents are handle", "ToolCallOutputItem(agent=Agent(name='FAQ Agent', instructions='# System context\\nYou are part of a multi-agent system called the Agents SDK, designed to make agent coordination and execution easy. Agents uses two primary abstraction: **Agents** and **Handoffs**. An agent encompasses instructions and tools and can hand off a conversation to another agent when appropriate. Handoffs are achieved by calling a handoff function, generally named `transfer_to_`. Transfers between agents are ", "MessageOutputItem(agent=Agent(name='FAQ Agent', instructions='# System context\\nYou are part of a multi-agent system called the Agents SDK, designed to make agent coordination and execution easy. Agents uses two primary abstraction: **Agents** and **Handoffs**. An agent encompasses instructions and tools and can hand off a conversation to another agent when appropriate. Handoffs are achieved by calling a handoff function, generally named `transfer_to_`. Transfers between agents are h"], "metadata": {"final_output": "Desculpe, não tenho acesso ao número do seu assento no momento. Você pode conferir essa informação no seu cartão de embarque ou na confirmação da sua reserva. Precisa de mais alguma ajuda?"}} +2025-06-25 18:06:08 | INFO | TOOL CALL: {"timestamp": "2025-06-25T18:06:08.462677", "type": "tool_call", "agent": "FAQ Agent", "tool": "faq_lookup_tool", "conversation_id": "37c58194a04d4f5f824753a96182d173", "arguments": {"question": "qual o número do meu assento"}} +2025-06-25 18:06:08 | INFO | TOOL RESULT: {"timestamp": "2025-06-25T18:06:08.462809", "type": "tool_result", "agent": "FAQ Agent", "tool": "unknown", "conversation_id": "37c58194a04d4f5f824753a96182d173", "result": "I'm sorry, I don't know the answer to that question."} +2025-06-25 18:06:08 | INFO | GUARDRAIL CHECK: {"timestamp": "2025-06-25T18:06:08.463336", "type": "guardrail_check", "agent": "FAQ Agent", "guardrail": "Relevance Guardrail", "conversation_id": "37c58194a04d4f5f824753a96182d173", "input": "qual o numero do meu assento?", "passed": true, "reasoning": ""} +2025-06-25 18:06:08 | INFO | GUARDRAIL CHECK: {"timestamp": "2025-06-25T18:06:08.463458", "type": "guardrail_check", "agent": "FAQ Agent", "guardrail": "Jailbreak Guardrail", "conversation_id": "37c58194a04d4f5f824753a96182d173", "input": "qual o numero do meu assento?", "passed": true, "reasoning": ""} +2025-06-25 18:06:26 | INFO | LLM REQUEST: {"timestamp": "2025-06-25T18:06:26.668922", "type": "llm_request", "agent": "FAQ Agent", "model": "gpt-4.1", "conversation_id": "37c58194a04d4f5f824753a96182d173", "input": "numero de confirmação? qual é?", "context": {"passenger_name": null, "confirmation_number": "144A3P", "seat_number": "1A", "flight_number": "FLT-341", "account_number": "32723827"}} +2025-06-25 18:06:27 | INFO | TOOL CALL: {"timestamp": "2025-06-25T18:06:27.914474", "type": "tool_call", "agent": "FAQ Agent", "tool": "faq_lookup_tool", "conversation_id": null, "arguments": {"question": "qual é o número de confirmação"}} +2025-06-25 18:06:27 | INFO | TOOL RESULT: {"timestamp": "2025-06-25T18:06:27.914590", "type": "tool_result", "agent": "FAQ Agent", "tool": "faq_lookup_tool", "conversation_id": null, "result": "I'm sorry, I don't know the answer to that question."} +2025-06-25 18:06:29 | INFO | LLM RESPONSE: {"timestamp": "2025-06-25T18:06:29.900772", "type": "llm_response", "agent": "FAQ Agent", "model": "gpt-4.1", "conversation_id": "37c58194a04d4f5f824753a96182d173", "response": ["ToolCallItem(agent=Agent(name='FAQ Agent', instructions='# System context\\nYou are part of a multi-agent system called the Agents SDK, designed to make agent coordination and execution easy. Agents uses two primary abstraction: **Agents** and **Handoffs**. An agent encompasses instructions and tools and can hand off a conversation to another agent when appropriate. Handoffs are achieved by calling a handoff function, generally named `transfer_to_`. Transfers between agents are handle", "ToolCallOutputItem(agent=Agent(name='FAQ Agent', instructions='# System context\\nYou are part of a multi-agent system called the Agents SDK, designed to make agent coordination and execution easy. Agents uses two primary abstraction: **Agents** and **Handoffs**. An agent encompasses instructions and tools and can hand off a conversation to another agent when appropriate. Handoffs are achieved by calling a handoff function, generally named `transfer_to_`. Transfers between agents are ", "MessageOutputItem(agent=Agent(name='FAQ Agent', instructions='# System context\\nYou are part of a multi-agent system called the Agents SDK, designed to make agent coordination and execution easy. Agents uses two primary abstraction: **Agents** and **Handoffs**. An agent encompasses instructions and tools and can hand off a conversation to another agent when appropriate. Handoffs are achieved by calling a handoff function, generally named `transfer_to_`. Transfers between agents are h"], "metadata": {"final_output": "Desculpe, não consegui localizar o número de confirmação para você. Essa informação geralmente está disponível no seu e-mail de confirmação da reserva ou no aplicativo da companhia aérea. Se precisar de ajuda para encontrar, posso orientar você sobre onde procurar!"}} +2025-06-25 18:06:29 | INFO | TOOL CALL: {"timestamp": "2025-06-25T18:06:29.901517", "type": "tool_call", "agent": "FAQ Agent", "tool": "faq_lookup_tool", "conversation_id": "37c58194a04d4f5f824753a96182d173", "arguments": {"question": "qual é o número de confirmação"}} +2025-06-25 18:06:29 | INFO | TOOL RESULT: {"timestamp": "2025-06-25T18:06:29.901630", "type": "tool_result", "agent": "FAQ Agent", "tool": "unknown", "conversation_id": "37c58194a04d4f5f824753a96182d173", "result": "I'm sorry, I don't know the answer to that question."} +2025-06-25 18:06:29 | INFO | GUARDRAIL CHECK: {"timestamp": "2025-06-25T18:06:29.902026", "type": "guardrail_check", "agent": "FAQ Agent", "guardrail": "Relevance Guardrail", "conversation_id": "37c58194a04d4f5f824753a96182d173", "input": "numero de confirmação? qual é?", "passed": true, "reasoning": ""} +2025-06-25 18:06:29 | INFO | GUARDRAIL CHECK: {"timestamp": "2025-06-25T18:06:29.902122", "type": "guardrail_check", "agent": "FAQ Agent", "guardrail": "Jailbreak Guardrail", "conversation_id": "37c58194a04d4f5f824753a96182d173", "input": "numero de confirmação? qual é?", "passed": true, "reasoning": ""} diff --git a/python-backend/llm_logger.py b/python-backend/llm_logger.py new file mode 100644 index 0000000..cb879b7 --- /dev/null +++ b/python-backend/llm_logger.py @@ -0,0 +1,206 @@ +import logging +import json +import time +from datetime import datetime +from typing import Any, Dict, List, Optional +from pathlib import Path + +class LLMLogger: + """Logger especializado para capturar interações com LLMs e transições de agentes.""" + + def __init__(self, log_file: str = "llm.log"): + self.log_file = Path(log_file) + self.logger = self._setup_logger() + + def _setup_logger(self) -> logging.Logger: + """Configura o logger com formatação específica para LLM.""" + logger = logging.getLogger("llm_logger") + logger.setLevel(logging.INFO) + + # Evita duplicação de handlers + if logger.handlers: + return logger + + # Handler para arquivo + file_handler = logging.FileHandler(self.log_file, encoding='utf-8') + file_handler.setLevel(logging.INFO) + + # Formato personalizado para logs de LLM + formatter = logging.Formatter( + '%(asctime)s | %(levelname)s | %(message)s', + datefmt='%Y-%m-%d %H:%M:%S' + ) + file_handler.setFormatter(formatter) + + logger.addHandler(file_handler) + return logger + + def log_llm_request(self, + agent_name: str, + model: str, + input_data: Any, + conversation_id: Optional[str] = None, + context: Optional[Dict] = None) -> None: + """Log de entrada para LLM.""" + log_entry = { + "timestamp": datetime.now().isoformat(), + "type": "llm_request", + "agent": agent_name, + "model": model, + "conversation_id": conversation_id, + "input": self._sanitize_input(input_data), + "context": context + } + self.logger.info(f"LLM REQUEST: {json.dumps(log_entry, ensure_ascii=False)}") + + def log_llm_response(self, + agent_name: str, + model: str, + response: Any, + conversation_id: Optional[str] = None, + metadata: Optional[Dict] = None) -> None: + """Log de saída da LLM.""" + log_entry = { + "timestamp": datetime.now().isoformat(), + "type": "llm_response", + "agent": agent_name, + "model": model, + "conversation_id": conversation_id, + "response": self._sanitize_response(response), + "metadata": metadata + } + self.logger.info(f"LLM RESPONSE: {json.dumps(log_entry, ensure_ascii=False)}") + + def log_agent_transition(self, + from_agent: str, + to_agent: str, + reason: Optional[str] = None, + conversation_id: Optional[str] = None, + context: Optional[Dict] = None) -> None: + """Log de transição entre agentes.""" + log_entry = { + "timestamp": datetime.now().isoformat(), + "type": "agent_transition", + "from_agent": from_agent, + "to_agent": to_agent, + "conversation_id": conversation_id, + "reason": reason, + "context": context + } + self.logger.info(f"AGENT TRANSITION: {json.dumps(log_entry, ensure_ascii=False)}") + + def log_tool_call(self, + agent_name: str, + tool_name: str, + tool_args: Any, + conversation_id: Optional[str] = None) -> None: + """Log de chamada de ferramenta.""" + log_entry = { + "timestamp": datetime.now().isoformat(), + "type": "tool_call", + "agent": agent_name, + "tool": tool_name, + "conversation_id": conversation_id, + "arguments": self._sanitize_input(tool_args) + } + self.logger.info(f"TOOL CALL: {json.dumps(log_entry, ensure_ascii=False)}") + + def log_tool_result(self, + agent_name: str, + tool_name: str, + result: Any, + conversation_id: Optional[str] = None) -> None: + """Log de resultado de ferramenta.""" + log_entry = { + "timestamp": datetime.now().isoformat(), + "type": "tool_result", + "agent": agent_name, + "tool": tool_name, + "conversation_id": conversation_id, + "result": self._sanitize_response(result) + } + self.logger.info(f"TOOL RESULT: {json.dumps(log_entry, ensure_ascii=False)}") + + def log_guardrail_check(self, + agent_name: str, + guardrail_name: str, + input_text: str, + passed: bool, + reasoning: Optional[str] = None, + conversation_id: Optional[str] = None) -> None: + """Log de verificação de guardrail.""" + log_entry = { + "timestamp": datetime.now().isoformat(), + "type": "guardrail_check", + "agent": agent_name, + "guardrail": guardrail_name, + "conversation_id": conversation_id, + "input": input_text, + "passed": passed, + "reasoning": reasoning + } + self.logger.info(f"GUARDRAIL CHECK: {json.dumps(log_entry, ensure_ascii=False)}") + + def log_error(self, + agent_name: str, + error_type: str, + error_message: str, + conversation_id: Optional[str] = None, + context: Optional[Dict] = None) -> None: + """Log de erros.""" + log_entry = { + "timestamp": datetime.now().isoformat(), + "type": "error", + "agent": agent_name, + "error_type": error_type, + "conversation_id": conversation_id, + "message": error_message, + "context": context + } + self.logger.error(f"ERROR: {json.dumps(log_entry, ensure_ascii=False)}") + + def log_conversation_start(self, + conversation_id: str, + initial_agent: str, + context: Optional[Dict] = None) -> None: + """Log de início de conversa.""" + log_entry = { + "timestamp": datetime.now().isoformat(), + "type": "conversation_start", + "conversation_id": conversation_id, + "initial_agent": initial_agent, + "context": context + } + self.logger.info(f"CONVERSATION START: {json.dumps(log_entry, ensure_ascii=False)}") + + def log_conversation_end(self, + conversation_id: str, + final_agent: str, + duration_seconds: Optional[float] = None) -> None: + """Log de fim de conversa.""" + log_entry = { + "timestamp": datetime.now().isoformat(), + "type": "conversation_end", + "conversation_id": conversation_id, + "final_agent": final_agent, + "duration_seconds": duration_seconds + } + self.logger.info(f"CONVERSATION END: {json.dumps(log_entry, ensure_ascii=False)}") + + def _sanitize_input(self, data: Any) -> Any: + """Sanitiza dados de entrada para logging seguro.""" + if isinstance(data, str): + # Limita o tamanho de strings muito longas + return data[:1000] + "..." if len(data) > 1000 else data + elif isinstance(data, list): + return [self._sanitize_input(item) for item in data[:10]] # Limita a 10 itens + elif isinstance(data, dict): + return {k: self._sanitize_input(v) for k, v in list(data.items())[:20]} # Limita a 20 chaves + return str(data)[:500] if len(str(data)) > 500 else data + + def _sanitize_response(self, data: Any) -> Any: + """Sanitiza dados de resposta para logging seguro.""" + return self._sanitize_input(data) + +# Instância global do logger +llm_logger = LLMLogger() \ No newline at end of file diff --git a/python-backend/main.py b/python-backend/main.py index 1a34002..e9d4a4a 100644 --- a/python-backend/main.py +++ b/python-backend/main.py @@ -4,6 +4,9 @@ from pydantic import BaseModel import string +# Import do logger personalizado +from llm_logger import llm_logger + from agents import ( Agent, RunContextWrapper, @@ -47,32 +50,69 @@ def create_initial_context() -> AirlineAgentContext: ) async def faq_lookup_tool(question: str) -> str: """Lookup answers to frequently asked questions.""" + # Log da chamada da ferramenta + llm_logger.log_tool_call( + agent_name="FAQ Agent", + tool_name="faq_lookup_tool", + tool_args={"question": question}, + conversation_id=None # Será preenchido pelo contexto da conversa + ) + q = question.lower() if "bag" in q or "baggage" in q: - return ( + result = ( "You are allowed to bring one bag on the plane. " "It must be under 50 pounds and 22 inches x 14 inches x 9 inches." ) elif "seats" in q or "plane" in q: - return ( + result = ( "There are 120 seats on the plane. " "There are 22 business class seats and 98 economy seats. " "Exit rows are rows 4 and 16. " "Rows 5-8 are Economy Plus, with extra legroom." ) elif "wifi" in q: - return "We have free wifi on the plane, join Airline-Wifi" - return "I'm sorry, I don't know the answer to that question." + result = "We have free wifi on the plane, join Airline-Wifi" + else: + result = "I'm sorry, I don't know the answer to that question." + + # Log do resultado da ferramenta + llm_logger.log_tool_result( + agent_name="FAQ Agent", + tool_name="faq_lookup_tool", + result=result, + conversation_id=None + ) + + return result @function_tool async def update_seat( context: RunContextWrapper[AirlineAgentContext], confirmation_number: str, new_seat: str ) -> str: """Update the seat for a given confirmation number.""" + # Log da chamada da ferramenta + llm_logger.log_tool_call( + agent_name="Seat Booking Agent", + tool_name="update_seat", + tool_args={"confirmation_number": confirmation_number, "new_seat": new_seat}, + conversation_id=None + ) + context.context.confirmation_number = confirmation_number context.context.seat_number = new_seat assert context.context.flight_number is not None, "Flight number is required" - return f"Updated seat to {new_seat} for confirmation number {confirmation_number}" + result = f"Updated seat to {new_seat} for confirmation number {confirmation_number}" + + # Log do resultado da ferramenta + llm_logger.log_tool_result( + agent_name="Seat Booking Agent", + tool_name="update_seat", + result=result, + conversation_id=None + ) + + return result @function_tool( name_override="flight_status_tool", @@ -80,7 +120,25 @@ async def update_seat( ) async def flight_status_tool(flight_number: str) -> str: """Lookup the status for a flight.""" - return f"Flight {flight_number} is on time and scheduled to depart at gate A10." + # Log da chamada da ferramenta + llm_logger.log_tool_call( + agent_name="Flight Status Agent", + tool_name="flight_status_tool", + tool_args={"flight_number": flight_number}, + conversation_id=None + ) + + result = f"Flight {flight_number} is on time and scheduled to depart at gate A10." + + # Log do resultado da ferramenta + llm_logger.log_tool_result( + agent_name="Flight Status Agent", + tool_name="flight_status_tool", + result=result, + conversation_id=None + ) + + return result @function_tool( name_override="baggage_tool", @@ -88,12 +146,31 @@ async def flight_status_tool(flight_number: str) -> str: ) async def baggage_tool(query: str) -> str: """Lookup baggage allowance and fees.""" + # Log da chamada da ferramenta + llm_logger.log_tool_call( + agent_name="Triage Agent", + tool_name="baggage_tool", + tool_args={"query": query}, + conversation_id=None + ) + q = query.lower() if "fee" in q: - return "Overweight bag fee is $75." - if "allowance" in q: - return "One carry-on and one checked bag (up to 50 lbs) are included." - return "Please provide details about your baggage inquiry." + result = "Overweight bag fee is $75." + elif "allowance" in q: + result = "One carry-on and one checked bag (up to 50 lbs) are included." + else: + result = "Please provide details about your baggage inquiry." + + # Log do resultado da ferramenta + llm_logger.log_tool_result( + agent_name="Triage Agent", + tool_name="baggage_tool", + result=result, + conversation_id=None + ) + + return result @function_tool( name_override="display_seat_map", @@ -103,8 +180,26 @@ async def display_seat_map( context: RunContextWrapper[AirlineAgentContext] ) -> str: """Trigger the UI to show an interactive seat map to the customer.""" + # Log da chamada da ferramenta + llm_logger.log_tool_call( + agent_name="Seat Booking Agent", + tool_name="display_seat_map", + tool_args={}, + conversation_id=None + ) + # The returned string will be interpreted by the UI to open the seat selector. - return "DISPLAY_SEAT_MAP" + result = "DISPLAY_SEAT_MAP" + + # Log do resultado da ferramenta + llm_logger.log_tool_result( + agent_name="Seat Booking Agent", + tool_name="display_seat_map", + result=result, + conversation_id=None + ) + + return result # ========================= # HOOKS @@ -112,6 +207,15 @@ async def display_seat_map( async def on_seat_booking_handoff(context: RunContextWrapper[AirlineAgentContext]) -> None: """Set a random flight number when handed off to the seat booking agent.""" + # Log do hook de handoff + llm_logger.log_agent_transition( + from_agent="Triage Agent", + to_agent="Seat Booking Agent", + reason="seat_booking_handoff_hook", + conversation_id=None, + context=context.context.model_dump() if context.context else {} + ) + context.context.flight_number = f"FLT-{random.randint(100, 999)}" context.context.confirmation_number = "".join(random.choices(string.ascii_uppercase + string.digits, k=6)) @@ -239,14 +343,41 @@ async def cancel_flight( context: RunContextWrapper[AirlineAgentContext] ) -> str: """Cancel the flight in the context.""" + # Log da chamada da ferramenta + llm_logger.log_tool_call( + agent_name="Cancellation Agent", + tool_name="cancel_flight", + tool_args={}, + conversation_id=None + ) + fn = context.context.flight_number assert fn is not None, "Flight number is required" - return f"Flight {fn} successfully cancelled" + result = f"Flight {fn} successfully cancelled" + + # Log do resultado da ferramenta + llm_logger.log_tool_result( + agent_name="Cancellation Agent", + tool_name="cancel_flight", + result=result, + conversation_id=None + ) + + return result async def on_cancellation_handoff( context: RunContextWrapper[AirlineAgentContext] ) -> None: """Ensure context has a confirmation and flight number when handing off to cancellation.""" + # Log do hook de handoff + llm_logger.log_agent_transition( + from_agent="Triage Agent", + to_agent="Cancellation Agent", + reason="cancellation_handoff_hook", + conversation_id=None, + context=context.context.model_dump() if context.context else {} + ) + if context.context.confirmation_number is None: context.context.confirmation_number = "".join( random.choices(string.ascii_uppercase + string.digits, k=6) diff --git a/python-backend/test_logging.py b/python-backend/test_logging.py new file mode 100644 index 0000000..17b19f6 --- /dev/null +++ b/python-backend/test_logging.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python3 +""" +Script de teste para verificar o sistema de logging +""" + +import asyncio +import json +from llm_logger import llm_logger + +async def test_logging(): + """Testa todas as funcionalidades de logging""" + + print("🧪 Testando sistema de logging...") + + # Teste de início de conversa + llm_logger.log_conversation_start( + conversation_id="test-123", + initial_agent="Triage Agent", + context={"test": "data"} + ) + + # Teste de requisição LLM + llm_logger.log_llm_request( + agent_name="Triage Agent", + model="gpt-4.1", + input_data="Olá, preciso de ajuda com meu voo", + conversation_id="test-123", + context={"passenger_name": "João Silva"} + ) + + # Teste de resposta LLM + llm_logger.log_llm_response( + agent_name="Triage Agent", + model="gpt-4.1", + response="Como posso ajudá-lo com seu voo?", + conversation_id="test-123", + metadata={"tokens_used": 150} + ) + + # Teste de transição de agente + llm_logger.log_agent_transition( + from_agent="Triage Agent", + to_agent="Seat Booking Agent", + reason="user_requested_seat_change", + conversation_id="test-123", + context={"confirmation_number": "ABC123"} + ) + + # Teste de chamada de ferramenta + llm_logger.log_tool_call( + agent_name="Seat Booking Agent", + tool_name="update_seat", + tool_args={"confirmation_number": "ABC123", "new_seat": "12A"}, + conversation_id="test-123" + ) + + # Teste de resultado de ferramenta + llm_logger.log_tool_result( + agent_name="Seat Booking Agent", + tool_name="update_seat", + result="Assento atualizado com sucesso para 12A", + conversation_id="test-123" + ) + + # Teste de guardrail + llm_logger.log_guardrail_check( + agent_name="Triage Agent", + guardrail_name="Relevance Guardrail", + input_text="Olá, preciso de ajuda com meu voo", + passed=True, + reasoning="Mensagem relevante para serviço de companhia aérea", + conversation_id="test-123" + ) + + # Teste de erro + llm_logger.log_error( + agent_name="Triage Agent", + error_type="guardrail_tripwire", + error_message="Guardrail de relevância acionado", + conversation_id="test-123", + context={"input": "Mensagem irrelevante"} + ) + + # Teste de fim de conversa + llm_logger.log_conversation_end( + conversation_id="test-123", + final_agent="Seat Booking Agent", + duration_seconds=45.5 + ) + + print("✅ Testes de logging concluídos!") + print("📝 Verifique o arquivo llm.log para ver os resultados") + +if __name__ == "__main__": + asyncio.run(test_logging()) \ No newline at end of file diff --git a/ui/.nvmrc b/ui/.nvmrc new file mode 100644 index 0000000..58172ab --- /dev/null +++ b/ui/.nvmrc @@ -0,0 +1 @@ +20 \ No newline at end of file diff --git a/ui/components/Chat.tsx b/ui/components/chat.tsx similarity index 100% rename from ui/components/Chat.tsx rename to ui/components/chat.tsx