"""AIVANOV server – Ollama (gpt-oss:120b-cloud) + PostgreSQL (Chinook).""" import os import sys # Ensure src is on path for editable install sys.path.insert(0, os.path.join(os.path.dirname(__file__), "src")) from vanna import Agent, AgentConfig from vanna.core.registry import ToolRegistry from vanna.core.user import User from vanna.core.user.resolver import UserResolver from vanna.core.user.request_context import RequestContext from vanna.integrations.ollama import OllamaLlmService from vanna.integrations.postgres import PostgresRunner from vanna.integrations.local.agent_memory import DemoAgentMemory from vanna.integrations.local import FileSystemConversationStore from vanna.tools import RunSqlTool, VisualizeDataTool, ExportPdfTool, LocalFileSystem from vanna.core.system_prompt import DefaultSystemPromptBuilder from vanna.servers.fastapi.app import VannaFastAPIServer SYSTEM_PROMPT = """\ Vous êtes l'assistant AIVANOV, un analyste de données IA. Vous répondez aux questions en écrivant et exécutant des requêtes SQL sur une base de données PostgreSQL. Répondez toujours en français. SCHÉMA DE LA BASE DE DONNÉES (Chinook - magasin de musique) : Tables et colonnes : - artist(artist_id, name) - album(album_id, title, artist_id) → FK artist - track(track_id, name, album_id, media_type_id, genre_id, composer, milliseconds, bytes, unit_price) → FK album, media_type, genre - genre(genre_id, name) - media_type(media_type_id, name) - playlist(playlist_id, name) - playlist_track(playlist_id, track_id) → FK playlist, track - customer(customer_id, first_name, last_name, company, address, city, state, country, postal_code, phone, fax, email, support_rep_id) → FK employee - employee(employee_id, last_name, first_name, title, reports_to, birth_date, hire_date, address, city, state, country, postal_code, phone, fax, email) - invoice(invoice_id, customer_id, invoice_date, billing_address, billing_city, billing_state, billing_country, billing_postal_code, total) → FK customer - invoice_line(invoice_line_id, invoice_id, track_id, unit_price, quantity) → FK invoice, track INSTRUCTIONS CRITIQUES — LISEZ ATTENTIVEMENT : 1. EXÉCUTEZ TOUJOURS les requêtes SQL avec l'outil run_sql. Ne montrez JAMAIS uniquement du code SQL sans l'exécuter. 2. INTERDIT : NE GÉNÉREZ JAMAIS de tableaux markdown (|---|---|). Les données sont affichées automatiquement par le frontend. Si vous affichez un tableau markdown, c'est une ERREUR. 3. GRAPHIQUES ET DIAGRAMMES — OBLIGATOIRE : Quand l'utilisateur demande un diagramme, graphique, camembert, histogramme, courbe, visualisation ou chart : ÉTAPE 1 : Appelez run_sql pour récupérer les données. ÉTAPE 2 : Lisez le nom du fichier CSV dans la réponse de run_sql (format: res_XXXXX.csv). ÉTAPE 3 : Appelez visualize_data en copiant le nom EXACT du fichier. Ne modifiez PAS le nom. ATTENTION AU NOM DE FICHIER : - Le fichier s'appelle "res_XXXXX.csv" (5 chiffres) - Copiez-le EXACTEMENT tel qu'il apparaît dans le résultat de run_sql - N'inventez PAS de nom. N'ajoutez PAS "..." ou de troncature. Types de graphiques (paramètre chart_type) : "pie" = camembert | "bar" = barres | "scatter" = nuage de points "histogram" = histogramme | "line" = courbe | "heatmap" = carte de chaleur Exemple : → run_sql(sql="SELECT genre.name, COUNT(*) as total FROM track JOIN genre USING(genre_id) GROUP BY 1 ORDER BY 2 DESC LIMIT 10") (résultat contient: FICHIER CSV SAUVEGARDÉ: res_42851.csv) → visualize_data(filename="res_42851.csv", title="Top 10 genres", chart_type="bar") 4. Ne générez JAMAIS de liens markdown d'images. Le graphique est rendu automatiquement. 5. Gardez vos commentaires textuels COURTS (2-3 phrases max). Les données sont déjà visibles. """ class DemoUserResolver(UserResolver): """Always returns a demo user - no auth required.""" async def resolve_user(self, request_context: RequestContext) -> User: return User( id="demo_user", email="demo@example.com", group_memberships=["user"], ) def create_agent() -> Agent: llm_service = OllamaLlmService( model="gpt-oss:120b-cloud", host="http://localhost:11434", ) postgres_runner = PostgresRunner( host="localhost", port=5432, database="chinook", user="dom", password="loli", ) file_system = LocalFileSystem() run_sql_tool = RunSqlTool(sql_runner=postgres_runner, file_system=file_system) visualize_tool = VisualizeDataTool(file_system=file_system) export_pdf_tool = ExportPdfTool(file_system=file_system) tool_registry = ToolRegistry() tool_registry.register_local_tool(run_sql_tool, access_groups=[]) tool_registry.register_local_tool(visualize_tool, access_groups=[]) tool_registry.register_local_tool(export_pdf_tool, access_groups=[]) agent_memory = DemoAgentMemory(max_items=1000) user_resolver = DemoUserResolver() conversation_store = FileSystemConversationStore( base_dir=os.path.join(os.path.dirname(__file__), "data", "conversations") ) return Agent( llm_service=llm_service, tool_registry=tool_registry, user_resolver=user_resolver, agent_memory=agent_memory, conversation_store=conversation_store, system_prompt_builder=DefaultSystemPromptBuilder(base_prompt=SYSTEM_PROMPT), config=AgentConfig( stream_responses=True, include_thinking_indicators=True, ), ) if __name__ == "__main__": agent = create_agent() static_dir = os.path.join(os.path.dirname(__file__), "frontends", "webcomponent", "dist") server = VannaFastAPIServer(agent, config={ "dev_mode": True, "static_folder": static_dir, }) print("Démarrage d'AIVANOV sur http://localhost:8084") print(" LLM : Ollama gpt-oss:120b-cloud") print(" Base : PostgreSQL chinook (localhost:5432)") print(" Frontend : build local (avec graphiques Plotly)") print(" API docs : http://localhost:8084/docs") server.run(host="0.0.0.0", port=8084)