Files
Effi/archive/Perplexity/CONTEXTE TECHNIQUE_Je cherche à créer un assistant (1).md
T
Folzo 97348e9a12 Add ComfyUI models documentation and download script
- Complete MODELS.md with detailed model descriptions
- Interactive download script for 5+ specialized SDXL models
- Updated README with model selection guide
- Fix docker-compose volumes path (/root/ComfyUI)
- API workflows for OpenWebUI integration

Models documented:
- RealVisXL (portraits)
- Juggernaut XL (versatile)
- DreamShaper (art/fantasy)
- SDXL Lightning (fast)
- Proteus (realism)
2026-04-06 01:05:18 +02:00

19 KiB

CONTEXTE TECHNIQUE

Je cherche à créer un assistant IA personnel qui utilise Open WebUI comme interface, Ollama pour l'inférence LLM, et AFFiNE comme base de connaissance/mémoire. Infrastructure actuelle :

  1. NAS Synology (192.168.1.25) Open WebUI (container Docker, port 3000) AFFiNE (container Docker, port 3010) avec PostgreSQL + Redis Compose AFFiNE complet fonctionnel (affine, migration, postgres, redis)
  2. PC Gamer (192.168.1.11) Ollama (port 11434) avec modèles 14B paramètres (Qwen3-VL, Ministral, etc.) Accessible via un WOL proxy (container sur NAS, port 3011) qui réveille le PC à la demande
  3. WOL Proxy fonctionnel Python/Flask sur NAS (192.168.1.25:3011) Réveille le PC gamer automatiquement via Wake-on-LAN Proxifie ensuite toutes les requêtes vers Ollama de manière transparente Open WebUI configuré avec OLLAMA_BASE_URL=http://wol-proxy:3010 CONFIGURATION AFFINE ACTUELLE text services: affine: image: ghcr.io/toeverything/affine:stable ports: ['3010:3010'] environment:
    • AFFINE_COPILOT_ENABLED=true
    • AFFINE_AI_PROVIDER=openai
    • AFFINE_OPENAI_BASE_URL=http://192.168.1.10:11434 # À modifier ?
    • AFFINE_OPENAI_API_KEY=ollama depends_on: [redis, postgres, affine_migration]

+ services redis, postgres, migration (fonctionnels)

OBJECTIF Je veux que : Open WebUI soit l'orchestrateur central (UI unique, gestion conversation, config assistant) Ollama sur PC gamer fasse l'inférence (via WOL proxy pour économie énergie) AFFiNE serve de base de connaissance persistante où l'assistant peut : Rechercher dans mes documents/notes Créer/modifier des notes automatiquement Récupérer du contexte pour les conversations Architecture souhaitée : text Utilisateur → Open WebUI (NAS) → WOL Proxy → Ollama (PC) ↓ [Tools] ↓ AFFiNE (NAS) ← GraphQL API

CONTRAINTES & PRÉFÉRENCES ✅ Tout en Docker/Docker Compose sur le NAS (sauf Ollama sur PC) ✅ Solution simple et maintenable (pas de dépendances npm complexes si possible) ✅ Pas de duplication d'historique (Open WebUI = source de vérité) ✅ AFFiNE doit rester utilisable indépendamment (pas couplé à l'IA) ⚠️ J'ai essayé d'installer le package npm @dawncr0w/affine-mcp-server mais il n'existe pas dans le registre npm QUESTIONS CONCRÈTES Quelle est l'approche la plus robuste : MCP server + proxy HTTP, ou outil Python custom dans Open WebUI qui appelle directement l'API GraphQL d'AFFiNE ? Comment configurer proprement l'intégration pour que le modèle LLM puisse faire du function calling vers AFFiNE (recherche, création docs, etc.) ? Dois-je modifier l'URL Ollama dans AFFiNE (AFFINE_OPENAI_BASE_URL) pour qu'elle passe aussi par le WOL proxy ? Actuellement elle pointe directement sur le PC. Peux-tu me fournir : Le docker-compose.yml complet modifié (si ajout de services) Le code de l'outil/intégration (Python, MCP config, etc.) Les étapes de configuration dans Open WebUI VERSION LOGICIELS Open WebUI : 0.8.2 AFFiNE : stable (ghcr.io/toeverything/affine:stable) Ollama : 0.16.1 IMPORTANT : Je préfère une solution qui minimise le nombre de conteneurs supplémentaires et qui soit facile à débugger. Si MCP est trop complexe, une intégration directe via GraphQL + Python dans Open WebUI me convient aussi.

Basé sur votre infrastructure et vos contraintes, je recommande une intégration Python directe via GraphQL plutôt que MCP, car c'est plus simple, maintenable et parfaitement adapté à votre cas.1 2 3

Approche recommandée : Outil Python Custom dans Open WebUI

Open WebUI supporte nativement le function calling via des Tools Python qui permettent au LLM d'appeler des fonctions externes. Cette approche évite d'ajouter des conteneurs supplémentaires et reste facilement débuggable.2 3 1

Architecture finale

Utilisateur → Open WebUI (NAS:3000) → Ollama (via WOL Proxy)
                     ↓
                [AFFiNE Tool]
                     ↓
                AFFiNE GraphQL API (NAS:3010)

Solution complète

1. Docker Compose modifié

services:
  # Configuration AFFiNE existante
  affine:
    image: ghcr.io/toeverything/affine:stable
    ports: ['3010:3010']
    environment:
      - AFFINE_COPILOT_ENABLED=true
      - AFFINE_AI_PROVIDER=openai
      # Modifier pour passer par le WOL proxy
      - AFFINE_OPENAI_BASE_URL=http://wol-proxy:3011/v1
      - AFFINE_OPENAI_API_KEY=ollama
    depends_on: [redis, postgres, affine_migration]
    networks:
      - affine_network

  postgres:
    # Votre config existante
    networks:
      - affine_network

  redis:
    # Votre config existante
    networks:
      - affine_network

  affine_migration:
    # Votre config existante
    networks:
      - affine_network

  # Configuration Open WebUI
  open-webui:
    image: ghcr.io/open-webui/open-webui:latest
    ports: ['3000:8080']
    environment:
      - OLLAMA_BASE_URL=http://wol-proxy:3011
      - WEBUI_SECRET_KEY=${WEBUI_SECRET_KEY}
    volumes:
      - open-webui-data:/app/backend/data
    networks:
      - affine_network

  # WOL Proxy existant
  wol-proxy:
    # Votre config existante
    ports: ['3011:3011']
    networks:
      - affine_network

networks:
  affine_network:
    driver: bridge

volumes:
  open-webui-data:

2. Tool Python pour Open WebUI

Créez un fichier affine_tool.py à importer dans Open WebUI:3

"""
title: AFFiNE Knowledge Base Tool
author: Your Name
version: 1.0.0
description: Interact with AFFiNE workspace for document search, creation, and retrieval
required_open_webui_version: 0.8.0
"""

import os
import requests
from typing import Optional, Callable, Any
from pydantic import BaseModel, Field


class Tools:
    class Valves(BaseModel):
        AFFINE_BASE_URL: str = Field(
            default="http://affine:3010",
            description="AFFiNE server base URL"
        )
        AFFINE_WORKSPACE_ID: str = Field(
            default="",
            description="Default workspace ID"
        )
        AFFINE_API_TOKEN: str = Field(
            default="",
            description="AFFiNE personal access token (optional)"
        )

    def __init__(self):
        self.valves = self.Valves()
        self.graphql_endpoint = None

    def _get_endpoint(self):
        """Construire l'URL GraphQL"""
        if not self.graphql_endpoint:
            base = self.valves.AFFINE_BASE_URL.rstrip('/')
            self.graphql_endpoint = f"{base}/graphql"
        return self.graphql_endpoint

    def _execute_graphql(self, query: str, variables: dict = None) -> dict:
        """Exécuter une requête GraphQL"""
        headers = {"Content-Type": "application/json"}
        
        # Ajouter le token si disponible
        if self.valves.AFFINE_API_TOKEN:
            headers["Authorization"] = f"Bearer {self.valves.AFFINE_API_TOKEN}"
        
        payload = {"query": query}
        if variables:
            payload["variables"] = variables

        try:
            response = requests.post(
                self._get_endpoint(),
                json=payload,
                headers=headers,
                timeout=30
            )
            response.raise_for_status()
            return response.json()
        except Exception as e:
            return {"error": str(e)}

    def search_documents(
        self,
        search_query: str,
        workspace_id: Optional[str] = None,
        __event_emitter__: Callable[[dict], Any] = None
    ) -> str:
        """
        Search for documents in AFFiNE workspace.
        
        :param search_query: Text to search for in documents
        :param workspace_id: Workspace ID (uses default if not provided)
        :return: Search results with document titles and IDs
        """
        if __event_emitter__:
            __event_emitter__({
                "type": "status",
                "data": {"description": f"Searching AFFiNE for: {search_query}", "done": False}
            })

        ws_id = workspace_id or self.valves.AFFINE_WORKSPACE_ID
        
        query = """
        query SearchDocs($workspaceId: String!, $query: String!) {
          workspace(id: $workspaceId) {
            docs {
              id
              title
              createdAt
            }
          }
        }
        """
        
        result = self._execute_graphql(query, {
            "workspaceId": ws_id,
            "query": search_query
        })

        if __event_emitter__:
            __event_emitter__({
                "type": "status",
                "data": {"description": "Search completed", "done": True}
            })

        if "error" in result:
            return f"Error searching documents: {result['error']}"
        
        if "data" in result and result["data"].get("workspace"):
            docs = result["data"]["workspace"]["docs"]
            if docs:
                response = f"Found {len(docs)} documents:\n"
                for doc in docs[:10]:  # Limiter à 10 résultats
                    response += f"- {doc['title']} (ID: {doc['id']})\n"
                return response
            return "No documents found matching your query."
        
        return "Error retrieving search results."

    def get_document_content(
        self,
        doc_id: str,
        workspace_id: Optional[str] = None,
        __event_emitter__: Callable[[dict], Any] = None
    ) -> str:
        """
        Retrieve the content of a specific document.
        
        :param doc_id: Document ID to retrieve
        :param workspace_id: Workspace ID (uses default if not provided)
        :return: Document content
        """
        if __event_emitter__:
            __event_emitter__({
                "type": "status",
                "data": {"description": f"Retrieving document {doc_id}", "done": False}
            })

        ws_id = workspace_id or self.valves.AFFINE_WORKSPACE_ID
        
        query = """
        query GetDoc($workspaceId: String!, $docId: String!) {
          workspace(id: $workspaceId) {
            doc(id: $docId) {
              id
              title
              content
              updatedAt
            }
          }
        }
        """
        
        result = self._execute_graphql(query, {
            "workspaceId": ws_id,
            "docId": doc_id
        })

        if __event_emitter__:
            __event_emitter__({
                "type": "status",
                "data": {"description": "Document retrieved", "done": True}
            })

        if "error" in result:
            return f"Error retrieving document: {result['error']}"
        
        if "data" in result and result["data"].get("workspace", {}).get("doc"):
            doc = result["data"]["workspace"]["doc"]
            return f"# {doc['title']}\n\nLast updated: {doc['updatedAt']}\n\n{doc.get('content', 'No content available')}"
        
        return "Document not found or error retrieving content."

    def create_document(
        self,
        title: str,
        content: str,
        workspace_id: Optional[str] = None,
        __event_emitter__: Callable[[dict], Any] = None
    ) -> str:
        """
        Create a new document in AFFiNE workspace.
        
        :param title: Title of the new document
        :param content: Content of the document
        :param workspace_id: Workspace ID (uses default if not provided)
        :return: Created document ID and confirmation
        """
        if __event_emitter__:
            __event_emitter__({
                "type": "status",
                "data": {"description": f"Creating document: {title}", "done": False}
            })

        ws_id = workspace_id or self.valves.AFFINE_WORKSPACE_ID
        
        mutation = """
        mutation CreateDoc($workspaceId: String!, $title: String!, $content: String!) {
          createDoc(workspaceId: $workspaceId, title: $title, content: $content) {
            id
            title
          }
        }
        """
        
        result = self._execute_graphql(mutation, {
            "workspaceId": ws_id,
            "title": title,
            "content": content
        })

        if __event_emitter__:
            __event_emitter__({
                "type": "status",
                "data": {"description": "Document created successfully", "done": True}
            })

        if "error" in result:
            return f"Error creating document: {result['error']}"
        
        if "data" in result and result["data"].get("createDoc"):
            doc = result["data"]["createDoc"]
            return f"Successfully created document '{doc['title']}' with ID: {doc['id']}"
        
        return "Error creating document."

    def list_workspaces(
        self,
        __event_emitter__: Callable[[dict], Any] = None
    ) -> str:
        """
        List all available workspaces.
        
        :return: List of workspace names and IDs
        """
        if __event_emitter__:
            __event_emitter__({
                "type": "status",
                "data": {"description": "Fetching workspaces", "done": False}
            })

        query = """
        query {
          workspaces {
            id
            name
            createdAt
          }
        }
        """
        
        result = self._execute_graphql(query)

        if __event_emitter__:
            __event_emitter__({
                "type": "status",
                "data": {"description": "Workspaces retrieved", "done": True}
            })

        if "error" in result:
            return f"Error listing workspaces: {result['error']}"
        
        if "data" in result and result["data"].get("workspaces"):
            workspaces = result["data"]["workspaces"]
            if workspaces:
                response = f"Available workspaces ({len(workspaces)}):\n"
                for ws in workspaces:
                    response += f"- {ws['name']} (ID: {ws['id']})\n"
                return response
            return "No workspaces found."
        
        return "Error retrieving workspaces."

3. Configuration dans Open WebUI

Étape A : Importer le Tool

  1. Connectez-vous à Open WebUI (http://192.168.1.25:3000)
  2. Allez dans Workspace → Tools
  3. Cliquez sur + Create Tool
  4. Collez le code Python ci-dessus
  5. Cliquez sur Save

Étape B : Configurer les Valves3

  1. Cliquez sur l'icône d'engrenage du tool AFFiNE Knowledge Base
  2. Configurez les valeurs :
    • AFFINE_BASE_URL: http://affine:3010 (communication inter-conteneur)
    • AFFINE_WORKSPACE_ID: Votre ID de workspace (à récupérer)
    • AFFINE_API_TOKEN: Laissez vide pour l'instant

Étape C : Récupérer le Workspace ID4 5

Depuis le NAS, exécutez :

curl -X POST http://192.168.1.25:3010/graphql \
  -H "Content-Type: application/json" \
  -d '{"query": "{ workspaces { id name } }"}'

Copiez l'ID du workspace et mettez-le dans les Valves.

Étape D : Activer le Tool

  1. Dans Workspace → Models, sélectionnez votre modèle Ollama
  2. Cliquez sur l'icône crayon pour éditer
  3. Scrollez jusqu'à Functions
  4. Cochez AFFiNE Knowledge Base Tool
  5. Cliquez sur Save

Réponses aux questions

1. Approche la plus robuste

Python Tool directement dans Open WebUI est supérieure à MCP dans votre cas car:6 3

  • Pas de conteneur supplémentaire
  • Débugage simple (logs Python natifs)
  • Configuration unifiée dans Open WebUI
  • Le package npm @dawncr0w/affine-mcp-server existe mais nécessite Node.js + conteneur dédié7 5

2. Configuration function calling

Le code fourni utilise les Sphinx-style docstrings qu'Open WebUI convertit automatiquement en spécifications de tools pour le LLM. Les paramètres typés permettent au modèle de comprendre comment appeler les fonctions.2 3

3. URL Ollama dans AFFiNE

Oui, modifiez-la pour utiliser le WOL proxy. Changez :1

- AFFINE_OPENAI_BASE_URL=http://wol-proxy:3011/v1

Important : Ajoutez /v1 car AFFiNE attend une API compatible OpenAI. Votre WOL proxy doit donc exposer /v1/chat/completions (pattern standard Ollama).4

Avantages de cette solution

✅ Un seul conteneur supplémentaire : Open WebUI gère tout ✅ Debugging facile : Logs Python visibles dans Open WebUI ✅ Pas de duplication : Open WebUI reste la source de vérité ✅ AFFiNE indépendant : Fonctionne normalement sans l'IA ✅ Économie d'énergie : Le WOL proxy réveille le PC uniquement quand nécessaire

Limitations et évolutions

  • Les requêtes GraphQL ci-dessus sont des exemples : l'API réelle d'AFFiNE peut différer8 9
  • Testez les requêtes manuellement avec curl avant de les intégrer
  • Pour l'authentification avancée, générez un Personal Access Token via l'API GraphQL d'AFFiNE10 11
  • Si vous avez besoin de capacités MCP plus tard, le projet ivanuser/open-webui-mcp-connector existe6

Cette architecture vous permet de commencer simplement et d'étendre progressivement les fonctionnalités selon vos besoins.1 3 12 13 14 15 16 17 18 19 20 21 22 23 24 25

⁂

  1. https://github.com/open-webui/open-webui/discussions/11820 ↩︎

  2. https://github.com/open-webui/open-webui/discussions/3134 ↩︎

  3. https://docs.openwebui.com/tutorials/plugin/functions/ ↩︎

  4. https://lobehub.com/mcp/dawncr0w-affine-mcp-server ↩︎

  5. https://github.com/dawncr0w/affine-mcp-server ↩︎

  6. https://github.com/ivanuser/open-webui-mcp-connector ↩︎

  7. https://playbooks.com/mcp/dawncr0w/affine-mcp-server ↩︎

  8. https://deepwiki.com/toeverything/AFFiNE/3.1-server-and-api ↩︎

  9. https://opendeep.wiki/toeverything/AFFiNE/integrating-external-app ↩︎

  10. https://github.com/toeverything/AFFiNE/issues/13262 ↩︎

  11. https://glama.ai/mcp/servers/@DAWNCR0W/affine-mcp-server/tools/affine_generate_access_token ↩︎

  12. https://www.reddit.com/r/OpenWebUI/comments/1jeqj9u/is_function_calling_possible_in_open_webui/ ↩︎

  13. https://www.youtube.com/watch?v=04h9XlOkhCE ↩︎

  14. https://www.reddit.com/r/mcp/comments/1oqj7hp/affine_mcp_server_enables_interaction_with_affine/ ↩︎

  15. https://github.com/open-webui/open-webui/issues/2825 ↩︎

  16. https://dahvc-docs.colab.duke.edu/mcp/open-web-ui-and-mcpo.html ↩︎

  17. https://github.com/open-webui/open-webui/discussions/1629 ↩︎

  18. https://mcpmarket.com/server/affine ↩︎

  19. https://rainbowbreeze.it/openwebui-mcp-http/ ↩︎

  20. https://www.reddit.com/r/OpenWebUI/comments/1oqh3au/open_webui_now_supports_native_sequential_tool/ ↩︎

  21. https://docs.affine.pro/self-host-affine/install/upgrade ↩︎

  22. https://developers.cloudflare.com/analytics/graphql-api/getting-started/authentication/api-token-auth/ ↩︎

  23. https://developers.openai.com/api/docs/guides/function-calling/ ↩︎

  24. https://community.retool.com/t/graphql-authorization-with-url-token/4638 ↩︎

  25. https://shyft.ai/mcp/dawncr0w-affine-mcp-server-github ↩︎