| """ |
| app.py — Ana Uygulama Girişi (Gradio) |
| |
| Gradio ChatInterface ile sohbet arayüzü sunar. |
| Üst kısımda model seçici dropdown ile GPT/Claude/Gemini arası geçiş. |
| Alt sekmede audit/metrik dashboard. |
| |
| HuggingFace Spaces için: bu dosyayı root'ta tut, demo.launch() çağır. |
| """ |
| import uuid |
| import os |
| import sys |
| import io |
|
|
| |
| sys.path.insert(0, os.path.dirname(__file__)) |
|
|
| |
| if hasattr(sys.stdout, 'buffer'): |
| sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace', line_buffering=True) |
|
|
| |
| try: |
| from huggingface_hub import HfFolder |
| except ImportError: |
| import huggingface_hub as _hf_hub |
| class _HfFolder: |
| @staticmethod |
| def get_token(): return None |
| @staticmethod |
| def save_token(token): pass |
| @staticmethod |
| def delete_token(): pass |
| _hf_hub.HfFolder = _HfFolder |
|
|
| import gradio as gr |
|
|
| |
| |
| import gradio_client.utils as _gcu |
| _orig_get_type = _gcu.get_type |
| _orig_schema_to_type = _gcu._json_schema_to_python_type |
|
|
| def _safe_get_type(schema): |
| if not isinstance(schema, dict): |
| return "any" |
| return _orig_get_type(schema) |
|
|
| def _safe_schema_to_type(schema, defs=None): |
| if not isinstance(schema, dict): |
| return "any" |
| return _orig_schema_to_type(schema, defs) |
|
|
| _gcu.get_type = _safe_get_type |
| _gcu._json_schema_to_python_type = _safe_schema_to_type |
|
|
| from langchain_core.messages import HumanMessage |
|
|
| from config import GRADIO_TITLE, GRADIO_SHARE |
| from graph.state import create_initial_state |
| from graph.workflow import get_compiled_graph, get_workflow_mermaid |
| from memory.checkpointer import get_thread_config |
| from memory.vector_store import index_faq |
| from tools.db_tool import init_db |
| from observability.dashboard import build_dashboard_tab |
|
|
| |
| init_db() |
| try: |
| index_faq() |
| except Exception as e: |
| print(f"[Uyarı] FAQ indeksleme atlandı: {e}") |
|
|
| |
| _sessions: dict = {} |
|
|
|
|
| def chat(message: str, history: list, session_id: str) -> str: |
| """ |
| Gradio ChatInterface'in çağırdığı ana fonksiyon. |
| |
| Args: |
| message: Kullanıcının yazdığı metin |
| history: Önceki mesajlar (Gradio formatı) |
| session_id: Oturum UUID (Gradio state'te saklanır) |
| |
| Returns: |
| Chatbot'un cevabı (string) |
| """ |
| compiled = get_compiled_graph() |
| config = get_thread_config(session_id) |
|
|
| |
| if session_id not in _sessions: |
| _sessions[session_id] = create_initial_state(session_id) |
|
|
| state = _sessions[session_id] |
|
|
| |
| updated_messages = state["messages"] + [HumanMessage(content=message)] |
| state = {**state, "messages": updated_messages} |
| print(f"[DEBUG current state before invoke] {state}", flush=True) |
| import sys |
| print(f"[DEBUG chat] sid={session_id[:8]} msg={message!r} awaited={state.get('awaited_slot')} vtype={state.get('vehicle_type')} step={state.get('current_step')}", flush=True) |
| sys.stdout.flush() |
|
|
| |
| try: |
| result = compiled.invoke(state, config=config) |
| _sessions[session_id] = result |
| print(f"[DEBUG chat AFTER] awaited={result.get('awaited_slot')} vtype={result.get('vehicle_type')} step={result.get('current_step')} invoice={result.get('invoice_amount')}", flush=True) |
| sys.stdout.flush() |
|
|
| |
| ai_messages = [m for m in result["messages"] if hasattr(m, "type") and m.type == "ai"] |
| if ai_messages: |
| return ai_messages[-1].content |
| return "İşleniyor..." |
| except Exception as e: |
| print(f"[ERROR chat] sid={session_id[:8]} {type(e).__name__}: {e}", flush=True) |
| from litellm.exceptions import ServiceUnavailableError, RateLimitError, Timeout |
| if isinstance(e, (ServiceUnavailableError, RateLimitError, Timeout)): |
| return ("Sistemimiz şu anda yoğun. Lütfen birkaç saniye sonra " |
| "mesajınızı tekrar gönderir misiniz? 🙏") |
| return "Beklenmeyen bir sorun oluştu. Lütfen tekrar deneyin." |
|
|
|
|
| def new_session() -> tuple[str, list]: |
| """Yeni oturum başlat — session_id üret, history temizle.""" |
| session_id = str(uuid.uuid4()) |
| welcome = "Merhaba! 👋 Araç finansmanı ön başvurusu için yardımcı olabilirim.\nYeni araç mı, 2. el araç mı düşünüyorsunuz?" |
| return session_id, [(None, welcome)] |
|
|
|
|
| def show_workflow(): |
| """Workflow diyagramını PNG görsel olarak döndürür; başarısızsa mermaid kodu.""" |
| import io |
| from PIL import Image |
| try: |
| png_bytes = get_compiled_graph().get_graph().draw_mermaid_png() |
| return Image.open(io.BytesIO(png_bytes)), "" |
| except Exception as e: |
| |
| return None, f"```mermaid\n{get_workflow_mermaid()}\n```\n\n*(Görsel üretilemedi: {e})*" |
|
|
|
|
| |
|
|
| with gr.Blocks(title=GRADIO_TITLE) as demo: |
| gr.Markdown(f"# {GRADIO_TITLE}") |
|
|
| |
| session_state = gr.State(value=str(uuid.uuid4())) |
|
|
| with gr.Tab("💬 Chatbot"): |
| with gr.Row(): |
| import config as _cfg |
| active_model_label = gr.Textbox( |
| value=f"{_cfg.ACTIVE_MODEL} | router: {_cfg.ROUTER_MODEL}", |
| label="Aktif Model", |
| interactive=False, |
| scale=4, |
| ) |
| new_session_btn = gr.Button("🔄 Yeni Oturum", scale=1) |
|
|
| chatbot_ui = gr.Chatbot( |
| label="Arac Finansmani Chatbot", |
| height=500, |
| type="messages", |
| ) |
|
|
| with gr.Row(): |
| msg_input = gr.Textbox( |
| placeholder="Mesajınızı yazın... (örn: yeni araç finansmanı istiyorum)", |
| label="", |
| scale=5, |
| container=False, |
| ) |
| send_btn = gr.Button("Gönder", scale=1, variant="primary") |
|
|
| |
| def respond(message, history, session_id): |
| if not message.strip(): |
| return history, "" |
| response = chat(message, history, session_id) |
| history = history + [ |
| {"role": "user", "content": message}, |
| {"role": "assistant", "content": response}, |
| ] |
| return history, "" |
|
|
| send_btn.click( |
| fn=respond, |
| inputs=[msg_input, chatbot_ui, session_state], |
| outputs=[chatbot_ui, msg_input], |
| ) |
| msg_input.submit( |
| fn=respond, |
| inputs=[msg_input, chatbot_ui, session_state], |
| outputs=[chatbot_ui, msg_input], |
| ) |
|
|
| |
| def start_new(old_sid): |
| new_sid = str(uuid.uuid4()) |
| welcome = "Merhaba! 👋 Araç finansmanı ön başvurusu için yardımcı olabilirim.\nYeni araç mı, 2. el araç mı düşünüyorsunuz?" |
| return new_sid, [{"role": "assistant", "content": welcome}] |
|
|
| new_session_btn.click( |
| fn=start_new, |
| inputs=[session_state], |
| outputs=[session_state, chatbot_ui], |
| ) |
|
|
| with gr.Tab("🗺️ Workflow Diyagramı"): |
| gr.Markdown("LangGraph state machine görselleştirmesi:") |
| diagram_image = gr.Image(label="Workflow", show_label=False) |
| diagram_fallback = gr.Markdown() |
| show_btn = gr.Button("Diyagramı Göster") |
| show_btn.click(fn=show_workflow, outputs=[diagram_image, diagram_fallback]) |
|
|
| |
| build_dashboard_tab() |
|
|
| with gr.Tab("ℹ️ Agentic Pattern'ler"): |
| gr.Markdown(""" |
| ## Bu Projede Kullanılan Agentic Pattern'ler |
| |
| | Pattern | Dosya | Açıklama | |
| |---|---|---| |
| | **State Machine** | `graph/workflow.py` | LangGraph StateGraph — tüm akış | |
| | **Supervisor** | `agents/supervisor.py` | Intent sınıflandırma + dal yönlendirme | |
| | **Tool Use** | `tools/` | @tool ile iş kuralları kapsülleme | |
| | **Deterministik Doğrulama** | `agents/validation_agent.py` | İş kuralı kontrolü — LLM'siz, sabit akış | |
| | **RAG** | `agents/faq_agent.py` | Jina embed + ChromaDB retrieval | |
| | **Reflection** | `agents/reflection_agent.py` | Self-check onay öncesi | |
| | **Short-Term Memory** | `memory/checkpointer.py` | LangGraph MemorySaver | |
| | **Long-Term Memory** | `memory/vector_store.py` | ChromaDB persist | |
| | **Least-Privilege** | `agents/crosssell_agent.py` | Scoped tool access | |
| | **Input/Output Guard** | `guardrails/` | PII + injection filter | |
| | **Audit Log** | `observability/audit_logger.py` | KVKK/BDDK uyumlu log | |
| """) |
|
|
|
|
| if __name__ == "__main__": |
| demo.launch( |
| server_name="0.0.0.0", |
| server_port=int(os.environ.get("GRADIO_SERVER_PORT", 7860)), |
| share=GRADIO_SHARE, |
| ) |
|
|