# Tutoriel Complet : Créer et Utiliser des Agents LLM Dynamiques avec Ollama, Python, CLI, Streamlit et Cursor

---

## Objectif

- **Obsidian** (notes, organisation)
- **Cursor** (code, développement)
- **Streamlit** (interface utilisateur graphique)
- **CLI** (ligne de commande)

Chaque agent peut :

- Être instancié avec un **modèle différent** (mistral, codellama, etc.)
- Avoir un **rôle personnalisé** défini dynamiquement à l'appel
- Utiliser des **paramètres avancés** comme temperature, top_p, top_k
- Travailler en **mode synchrone ou asynchrone**

---

## I. Architecture des fichiers

```bash
llm_agents/
├── ollama_interface.py        # Interface générique avec Ollama
├── base_agent.py              # Classe d'agent configurable
├── agent_factory.py           # Création d'agents depuis JSON
├── agent_router.py            # Routage intelligent selon le contenu
├── cli.py                     # Interface terminale (CLI)
├── streamlit_app.py           # Interface Streamlit dynamique
├── main.py                    # Démo complète
├── profiles.json              # Profils agents (modèles, rôles)
└── agents/                    # Agents spécialisés (optionnel)
```

---

## II. Interface avec Ollama (ollama_interface.py)

```python
import requests

class OllamaInterface:
    def __init__(self, model, **params):
        self.model = model
        self.api_url = "http://localhost:11434/api/generate"
        self.params = params

    def generate(self, prompt):
        payload = {"model": self.model, "prompt": prompt, **self.params}
        try:
            response = requests.post(self.api_url, json=payload)
            response.raise_for_status()
            return response.json().get("response", "")
        except Exception as e:
            return f"[Erreur Ollama] {e}"
```

---

## III. Classe de base des agents (base_agent.py)

```python
import logging
from ollama_interface import OllamaInterface

class BaseAgent:
    def __init__(self, name, model, system_prompt="", async_mode=False, enable_logging=True, **params):
        self.name = name
        self.system_prompt = system_prompt.strip()
        self.async_mode = async_mode
        self.enable_logging = enable_logging
        self.llm = OllamaInterface(model=model, **params)
        self.history = []
        if enable_logging:
            logging.basicConfig(
                filename=f"{self.name}.log",
                level=logging.INFO,
                format="%(asctime)s [%(name)s] %(message)s",
                datefmt="%Y-%m-%d %H:%M:%S"
            )

    def set_role(self, prompt_system):
        self.system_prompt = prompt_system.strip()

    def build_prompt(self, user_input):
        return f"{self.system_prompt}\n\n{user_input}" if self.system_prompt else user_input

    def log(self, prompt, response):
        if self.enable_logging:
            logging.getLogger(self.name).info(f"Prompt:\n{prompt}\nRéponse:\n{response}\n")

    def process(self, user_input):
        prompt = self.build_prompt(user_input)
        response = self.llm.generate(prompt)
        self.log(prompt, response)
        self.history.append((prompt, response))
        return response
```

---

## IV. Création d'agents dynamiques avec des rôles

```python
from base_agent import BaseAgent

agent1 = BaseAgent("AnalyseurNote", model="mistral")
agent1.set_role("Tu analyses les notes markdown pour en extraire la structure.")

agent2 = BaseAgent("Correcteur", model="mistral")
agent2.set_role("Tu corriges l'orthographe et mets en page le texte Markdown.")

note = "ceci est une not mal structuré avec des errurs"
print(agent1.process(note))
print(agent2.process(note))
```

---

## V. AgentRouter — routage intelligent selon le contenu

```python
class AgentRouter:
    def __init__(self, agents):
        self.agents = {agent.name.lower(): agent for agent in agents}

    def route(self, prompt):
        if "code" in prompt.lower():
            return self.agents["codeoptimizer"].process(prompt)
        elif "note" in prompt.lower() or "obsidian" in prompt.lower():
            return self.agents["analyseurnote"].process(prompt)
        else:
            return self.agents["correcteur"].process(prompt)
```

---

## VI. Exemple avec plusieurs agents dynamiques (main.py)

```python
from base_agent import BaseAgent

analyse = BaseAgent("analyseur", model="mistral")
analyse.set_role("Tu es un expert Obsidian. Analyse les notes et suggère des améliorations.")

optimiseur = BaseAgent("codeoptimizer", model="codellama:13b-python")
optimiseur.set_role("Tu es un assistant Python. Améliore le code donné.")

texte = "Voici une note mal formée sur FastAPI"
code = "def app(): return None"

print(analyse.process(texte))
print(optimiseur.process(code))
```

---

## VII. Interface CLI (cli.py)

```python
from base_agent import BaseAgent

name = input("Nom de l'agent : ")
model = input("Modèle Ollama (ex: mistral) : ")
role = input("Décris le rôle de l'agent : ")

agent = BaseAgent(name=name, model=model)
agent.set_role(role)

while True:
    prompt = input("\nPrompt > ")
    if prompt in ["exit", "quit"]:
        break
    print(agent.process(prompt))
```

---

## VIII. Interface Streamlit (streamlit_app.py)

```python
import streamlit as st
from base_agent import BaseAgent

st.title("Créateur d'agents LLM dynamiques")

name = st.text_input("Nom de l'agent")
model = st.text_input("Modèle Ollama", value="mistral")
role = st.text_area("Prompt système (rôle)")
prompt = st.text_area("Message utilisateur")

if st.button("Créer et exécuter"):
    agent = BaseAgent(name=name, model=model)
    agent.set_role(role)
    st.write(agent.process(prompt))
```

---

## IX. Exemple de configuration JSON (profiles.json)

```json
{
  "agents": [
    {
      "name": "ObsidianNoteur",
      "model": "mistral",
      "system_prompt": "Tu aides à structurer des notes Markdown.",
      "temperature": 0.6
    },
    {
      "name": "PythonHelper",
      "model": "codellama:13b-python",
      "system_prompt": "Tu améliores du code Python.",
      "temperature": 0.4
    }
  ]
}
```

---

## Conclusion

Tu disposes maintenant :

- D'une **base agent dynamique** et paramétrable
- De **rôles personnalisables** via `set_role()`
- D'exemples concrets dans Obsidian, Cursor, CLI et Streamlit
