145 lines
6.0 KiB
Python
145 lines
6.0 KiB
Python
"""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)
|