Cyril Dupland commited on
Commit
c392583
·
1 Parent(s): 6c4dfd3

Refactoring gestion Agent

Browse files
README.md CHANGED
@@ -157,7 +157,7 @@ curl -X POST "http://localhost:7860/completion" \
157
  -d '{
158
  "message": "Bonjour, comment vas-tu?",
159
  "model": "gpt-4o",
160
- "agent_type": "simple",
161
  "stream": false,
162
  "temperature": 0.7
163
  }'
@@ -333,13 +333,13 @@ def create_custom_graph(llm):
333
  from graphs.custom_graph import create_custom_graph
334
 
335
  agent_registry.register_agent(
336
- AgentType.CUSTOM,
337
  create_custom_graph,
338
  "Description de votre agent"
339
  )
340
  ```
341
 
342
- 3. Utilisez-le via l'API sans changement de code!
343
 
344
  ## 🧪 Tests
345
 
@@ -370,6 +370,7 @@ LANGCHAIN_PROJECT=routeur-ia
370
 
371
  ## 📝 TODO / Roadmap
372
 
 
373
  - [ ] Tests unitaires et d'intégration
374
  - [ ] Implémentation complète WebRTC avec aiortc
375
  - [ ] Agent RAG avec base vectorielle
 
157
  -d '{
158
  "message": "Bonjour, comment vas-tu?",
159
  "model": "gpt-4o",
160
+ "agent": "V2",
161
  "stream": false,
162
  "temperature": 0.7
163
  }'
 
333
  from graphs.custom_graph import create_custom_graph
334
 
335
  agent_registry.register_agent(
336
+ "my_agent",
337
  create_custom_graph,
338
  "Description de votre agent"
339
  )
340
  ```
341
 
342
+ 3. Utilisez-le via l'API avec `"agent": "my_agent"` dans la requête `POST /completion`.
343
 
344
  ## 🧪 Tests
345
 
 
370
 
371
  ## 📝 TODO / Roadmap
372
 
373
+ - [ ] Harmoniser la nomenclature du pipeline voix: le champ `agent_type` est encore utilisé dans `services/voice/voice_pipeline.py` pour des événements internes et devra être renommé en `agent` lors d'un prochain refactoring.
374
  - [ ] Tests unitaires et d'intégration
375
  - [ ] Implémentation complète WebRTC avec aiortc
376
  - [ ] Agent RAG avec base vectorielle
api/routes/completion.py CHANGED
@@ -61,7 +61,7 @@ async def complete(
61
  - Content-Type: `text/event-stream`
62
 
63
  Args:
64
- request: Completion request with message, model, agent type, and streaming flag
65
  current_user: Authenticated user (JWT required)
66
 
67
  Returns:
@@ -86,7 +86,7 @@ async def complete(
86
  return await _complete(request)
87
 
88
  except ValueError as e:
89
- # Agent type not available or validation error
90
  raise HTTPException(
91
  status_code=status.HTTP_400_BAD_REQUEST,
92
  detail=str(e)
@@ -112,7 +112,7 @@ async def _complete(request: CompletionRequest) -> CompletionResponse:
112
  result = await agent_service.invoke(
113
  message=request.message,
114
  model_name=request.model,
115
- agent_type=request.agent_type,
116
  temperature=request.temperature,
117
  max_tokens=request.max_tokens,
118
  conversation_history=request.conversation_history,
@@ -139,7 +139,7 @@ async def _stream_completion(request: CompletionRequest) -> StreamingResponse:
139
  async for chunk in agent_service.stream(
140
  message=request.message,
141
  model_name=request.model,
142
- agent_type=request.agent_type,
143
  temperature=request.temperature,
144
  max_tokens=request.max_tokens,
145
  conversation_history=request.conversation_history,
 
61
  - Content-Type: `text/event-stream`
62
 
63
  Args:
64
+ request: Completion request with message, model, agent and streaming flag
65
  current_user: Authenticated user (JWT required)
66
 
67
  Returns:
 
86
  return await _complete(request)
87
 
88
  except ValueError as e:
89
+ # Agent not available or validation error
90
  raise HTTPException(
91
  status_code=status.HTTP_400_BAD_REQUEST,
92
  detail=str(e)
 
112
  result = await agent_service.invoke(
113
  message=request.message,
114
  model_name=request.model,
115
+ agent=request.agent,
116
  temperature=request.temperature,
117
  max_tokens=request.max_tokens,
118
  conversation_history=request.conversation_history,
 
139
  async for chunk in agent_service.stream(
140
  message=request.message,
141
  model_name=request.model,
142
+ agent=request.agent,
143
  temperature=request.temperature,
144
  max_tokens=request.max_tokens,
145
  conversation_history=request.conversation_history,
docs/API_EXAMPLES.md CHANGED
@@ -9,6 +9,11 @@
9
  5. [WebSocket](#websocket)
10
  6. [Exemples avancés](#exemples-avancés)
11
 
 
 
 
 
 
12
  ## Authentification
13
 
14
  ### Obtenir un token JWT
@@ -60,7 +65,7 @@ curl -X POST http://localhost:7860/completion \
60
  -d '{
61
  "message": "Explique-moi la théorie de la relativité en 2 phrases",
62
  "model": "gpt-4o",
63
- "agent_type": "simple",
64
  "stream": false,
65
  "temperature": 0.7
66
  }'
@@ -71,7 +76,7 @@ curl -X POST http://localhost:7860/completion \
71
  {
72
  "response": "La théorie de la relativité d'Einstein comprend deux parties: la relativité restreinte (1905) qui établit que la vitesse de la lumière est constante et que le temps et l'espace sont relatifs, et la relativité générale (1915) qui décrit la gravitation comme une courbure de l'espace-temps causée par la masse et l'énergie. Ces théories ont révolutionné notre compréhension de l'univers et sont confirmées par de nombreuses expériences.",
73
  "model": "gpt-4o",
74
- "agent_type": "simple",
75
  "usage": {
76
  "prompt_tokens": 25,
77
  "completion_tokens": 98,
@@ -99,15 +104,15 @@ curl -N -X POST http://localhost:7860/completion \
99
 
100
  **Réponse (Server-Sent Events):**
101
  ```
102
- data: {"content": "Il", "done": false, "metadata": {"model": "gpt-3.5-turbo", "agent_type": "simple"}}
103
 
104
- data: {"content": " était", "done": false, "metadata": {"model": "gpt-3.5-turbo", "agent_type": "simple"}}
105
 
106
- data: {"content": " une", "done": false, "metadata": {"model": "gpt-3.5-turbo", "agent_type": "simple"}}
107
 
108
  ...
109
 
110
- data: {"content": "", "done": true, "metadata": {"model": "gpt-3.5-turbo", "agent_type": "simple"}}
111
  ```
112
 
113
  #### Champs d'empreinte carbone, latence, pricing et équivalences
@@ -146,7 +151,7 @@ Les réponses incluent désormais des métriques d'impact carbone calculées ave
146
  "done": true,
147
  "metadata": {
148
  "model": "mistral-large-latest",
149
- "agent_type": "simple",
150
  "usage": {"input_tokens":123, "output_tokens":456, "total_tokens":579},
151
  "usage_by_model": {
152
  "mistral-large-latest": {"input_tokens":123, "output_tokens":456, "total_tokens":579}
@@ -324,19 +329,19 @@ curl -X GET http://localhost:7860/agents \
324
  {
325
  "agents": [
326
  {
327
- "type": "simple",
328
- "name": "Simple",
329
- "description": "Simple conversational agent without tools or memory",
330
  "available": true
331
  },
332
  {
333
- "type": "rag",
334
- "name": "Rag",
335
- "description": "Agent with Retrieval Augmented Generation (not yet implemented)",
336
- "available": false
337
  }
338
  ],
339
- "total": 4
340
  }
341
  ```
342
 
 
9
  5. [WebSocket](#websocket)
10
  6. [Exemples avancés](#exemples-avancés)
11
 
12
+ ## Point d'attention
13
+
14
+ - La completion HTTP utilise désormais `agent` (ex: `V1`, `V2`).
15
+ - Le pipeline voix conserve temporairement un champ interne nommé `agent_type` dans les événements (`services/voice/voice_pipeline.py`), pour compatibilité. Ce point est prévu pour un alignement ultérieur vers `agent`.
16
+
17
  ## Authentification
18
 
19
  ### Obtenir un token JWT
 
65
  -d '{
66
  "message": "Explique-moi la théorie de la relativité en 2 phrases",
67
  "model": "gpt-4o",
68
+ "agent": "V2",
69
  "stream": false,
70
  "temperature": 0.7
71
  }'
 
76
  {
77
  "response": "La théorie de la relativité d'Einstein comprend deux parties: la relativité restreinte (1905) qui établit que la vitesse de la lumière est constante et que le temps et l'espace sont relatifs, et la relativité générale (1915) qui décrit la gravitation comme une courbure de l'espace-temps causée par la masse et l'énergie. Ces théories ont révolutionné notre compréhension de l'univers et sont confirmées par de nombreuses expériences.",
78
  "model": "gpt-4o",
79
+ "agent": "V2",
80
  "usage": {
81
  "prompt_tokens": 25,
82
  "completion_tokens": 98,
 
104
 
105
  **Réponse (Server-Sent Events):**
106
  ```
107
+ data: {"content": "Il", "done": false, "metadata": {"model": "gpt-3.5-turbo", "agent": "V2"}}
108
 
109
+ data: {"content": " était", "done": false, "metadata": {"model": "gpt-3.5-turbo", "agent": "V2"}}
110
 
111
+ data: {"content": " une", "done": false, "metadata": {"model": "gpt-3.5-turbo", "agent": "V2"}}
112
 
113
  ...
114
 
115
+ data: {"content": "", "done": true, "metadata": {"model": "gpt-3.5-turbo", "agent": "V2"}}
116
  ```
117
 
118
  #### Champs d'empreinte carbone, latence, pricing et équivalences
 
151
  "done": true,
152
  "metadata": {
153
  "model": "mistral-large-latest",
154
+ "agent": "V2",
155
  "usage": {"input_tokens":123, "output_tokens":456, "total_tokens":579},
156
  "usage_by_model": {
157
  "mistral-large-latest": {"input_tokens":123, "output_tokens":456, "total_tokens":579}
 
329
  {
330
  "agents": [
331
  {
332
+ "type": "V1",
333
+ "name": "V1",
334
+ "description": "Current production orchestrated workflow",
335
  "available": true
336
  },
337
  {
338
+ "type": "V2",
339
+ "name": "V2",
340
+ "description": "Isolated V2 workflow (default)",
341
+ "available": true
342
  }
343
  ],
344
+ "total": 2
345
  }
346
  ```
347
 
domain/models.py CHANGED
@@ -2,7 +2,7 @@
2
  from pydantic import BaseModel, Field
3
  from typing import Optional, List, Dict, Any, Literal
4
  from datetime import datetime, timezone
5
- from .enums import ModelName, AgentType
6
 
7
 
8
  # ============ Auth Models ============
@@ -27,7 +27,10 @@ class CompletionRequest(BaseModel):
27
  """Request for text completion."""
28
  message: str = Field(..., description="User message to complete")
29
  model: ModelName = Field(default=ModelName.MISTRAL_LARGE, description="LLM model to use")
30
- agent_type: AgentType = Field(default=AgentType.SIMPLE, description="Agent type to use")
 
 
 
31
  stream: bool = Field(default=False, description="Enable streaming response")
32
  temperature: float = Field(default=0.7, ge=0.0, le=2.0, description="Sampling temperature")
33
  max_tokens: Optional[int] = Field(default=None, description="Maximum tokens to generate")
@@ -48,7 +51,7 @@ class CompletionResponse(BaseModel):
48
  """Response for text completion (non-streaming)."""
49
  response: str
50
  model: str
51
- agent_type: str
52
  usage: Optional[Dict[str, Any]] = None
53
  metadata: Optional[Dict[str, Any]] = None
54
  conversation_id: Optional[str] = Field(
@@ -114,7 +117,7 @@ class ModelsListResponse(BaseModel):
114
 
115
  class AgentInfo(BaseModel):
116
  """Information about an available agent."""
117
- type: AgentType
118
  name: str
119
  description: str
120
  available: bool = True
 
2
  from pydantic import BaseModel, Field
3
  from typing import Optional, List, Dict, Any, Literal
4
  from datetime import datetime, timezone
5
+ from .enums import ModelName
6
 
7
 
8
  # ============ Auth Models ============
 
27
  """Request for text completion."""
28
  message: str = Field(..., description="User message to complete")
29
  model: ModelName = Field(default=ModelName.MISTRAL_LARGE, description="LLM model to use")
30
+ agent: Optional[str] = Field(
31
+ default=None,
32
+ description="Agent identifier to use (ex: 'V1' or 'V2'). If omitted, defaults to 'V2'."
33
+ )
34
  stream: bool = Field(default=False, description="Enable streaming response")
35
  temperature: float = Field(default=0.7, ge=0.0, le=2.0, description="Sampling temperature")
36
  max_tokens: Optional[int] = Field(default=None, description="Maximum tokens to generate")
 
51
  """Response for text completion (non-streaming)."""
52
  response: str
53
  model: str
54
+ agent: Optional[str] = None
55
  usage: Optional[Dict[str, Any]] = None
56
  metadata: Optional[Dict[str, Any]] = None
57
  conversation_id: Optional[str] = Field(
 
117
 
118
  class AgentInfo(BaseModel):
119
  """Information about an available agent."""
120
+ type: str
121
  name: str
122
  description: str
123
  available: bool = True
graphs/README.md DELETED
@@ -1,63 +0,0 @@
1
- # LangGraph Graphs
2
-
3
- Ce dossier contient les différents graphes LangGraph utilisés par l'API.
4
-
5
- ## Structure
6
-
7
- - `base_graph.py`: Graphe conversationnel simple par défaut
8
- - Vous pouvez ajouter d'autres graphes personnalisés ici
9
-
10
- ## Comment créer un nouveau graphe
11
-
12
- 1. Créez un nouveau fichier Python dans ce dossier (ex: `custom_graph.py`)
13
- 2. Définissez votre `State` avec TypedDict
14
- 3. Créez vos fonctions de nœuds
15
- 4. Construisez le graphe avec `StateGraph`
16
- 5. Compilez le graphe avec `.compile()`
17
- 6. Enregistrez votre graphe dans `services/agent_registry.py`
18
-
19
- ## Exemple de graphe personnalisé
20
-
21
- ```python
22
- from typing import TypedDict, Annotated, Sequence
23
- from langchain_core.messages import BaseMessage
24
- from langgraph.graph import StateGraph, END
25
- from langgraph.graph.message import add_messages
26
-
27
- class CustomState(TypedDict):
28
- messages: Annotated[Sequence[BaseMessage], add_messages]
29
- custom_field: str
30
-
31
- def create_custom_graph(llm):
32
- def custom_node(state: CustomState):
33
- # Votre logique personnalisée
34
- messages = state["messages"]
35
- response = llm.invoke(messages)
36
- return {"messages": [response]}
37
-
38
- workflow = StateGraph(CustomState)
39
- workflow.add_node("custom", custom_node)
40
- workflow.set_entry_point("custom")
41
- workflow.add_edge("custom", END)
42
-
43
- return workflow.compile()
44
- ```
45
-
46
- ## Graphes disponibles
47
-
48
- ### Simple Graph (`base_graph.py`)
49
- - Graphe conversationnel basique
50
- - Prend un message, l'envoie au LLM, retourne la réponse
51
- - Pas de mémoire persistante
52
-
53
- ### Simple Graph with History (`base_graph.py`)
54
- - Graphe conversationnel avec support de l'historique
55
- - Utilise l'historique fourni dans la requête
56
- - Pas de mémoire persistante (stateless)
57
-
58
- ## Notes
59
-
60
- - Tous les graphes sont stateless par défaut
61
- - L'historique de conversation doit être fourni par le client dans chaque requête
62
- - Pour ajouter des outils (RAG, recherche web, etc.), créez un nouveau graphe personnalisé
63
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
graphs/base_graph.py DELETED
@@ -1,193 +0,0 @@
1
- """Simple base LangGraph for conversational agent."""
2
- from typing import TypedDict, Annotated, Sequence, List, Optional
3
- from langchain_core.messages import BaseMessage, HumanMessage, AIMessage, SystemMessage
4
- from langchain_core.language_models.chat_models import BaseChatModel
5
- from langchain_core.documents import Document
6
- from langgraph.graph import StateGraph, END
7
- from langgraph.graph.message import add_messages
8
- from .prompts import SYSTEM_PROMPT_TEMPLATE
9
-
10
- # RAG imports (reuse setup from knowledge/ocr.ipynb)
11
- import os
12
- from functools import lru_cache
13
- from supabase import create_client, Client
14
- from langchain_openai import OpenAIEmbeddings
15
- from langchain_community.vectorstores import SupabaseVectorStore
16
-
17
-
18
- class AgentState(TypedDict, total=False):
19
- """State for the conversational agent with RAG."""
20
- messages: Annotated[Sequence[BaseMessage], add_messages]
21
- query: Optional[str]
22
- formation_docs: List[Document]
23
- prestation_docs: List[Document]
24
- formation_context: str
25
- prestation_context: str
26
- project_docs: List[Document]
27
- project_context: str
28
-
29
-
30
- def create_simple_graph(llm: BaseChatModel):
31
- """
32
- Create a simple conversational graph with LangGraph.
33
-
34
- This is a basic graph that takes a message, sends it to the LLM,
35
- and returns the response. It can be easily replaced with more complex graphs.
36
-
37
- Args:
38
- llm: Language model to use for generation
39
-
40
- Returns:
41
- Compiled LangGraph
42
- """
43
-
44
- def call_model(state: AgentState) -> AgentState:
45
- """Call the LLM with the current messages."""
46
- print(f"Calling model with messages: {state['messages']}")
47
-
48
- messages = state["messages"]
49
- response = llm.invoke(messages)
50
- return {"messages": messages + [AIMessage(content=response.content)] }
51
-
52
- # Build the graph
53
- workflow = StateGraph(AgentState)
54
-
55
- # Add nodes
56
- workflow.add_node("agent", call_model)
57
-
58
- # Set entry point
59
- workflow.set_entry_point("agent")
60
-
61
- # Add edge to end
62
- workflow.add_edge("agent", END)
63
-
64
- # Compile and return
65
- return workflow.compile()
66
-
67
-
68
- def create_simple_graph_with_history(llm: BaseChatModel):
69
- """
70
- Create a conversational graph with history + RAG retrieval from Supabase.
71
- Entry -> retrieve (RAG) -> agent (generate) -> END
72
- """
73
-
74
- @lru_cache(maxsize=2)
75
- def _get_retriever(doc_type: str, k: int = int(os.getenv("RAG_TOP_K", "5"))):
76
- """Get retriever for specific document type (formation or prestation)."""
77
- url = os.getenv("SUPABASE_URL")
78
- key = (
79
- os.getenv("SUPABASE_KEY")
80
- or os.getenv("SUPABASE_SERVICE_ROLE_KEY")
81
- or os.getenv("SUPABASE_ANON_KEY")
82
- or os.getenv("NEXT_PUBLIC_SUPABASE_ANON_KEY")
83
- )
84
- if not url or not key:
85
- raise ValueError("SUPABASE_URL and a SUPABASE_*KEY env var are required.")
86
- client: Client = create_client(url, key)
87
- vector_store = SupabaseVectorStore(
88
- embedding=OpenAIEmbeddings(api_key=os.getenv("OPENAI_API_KEY")),
89
- client=client,
90
- table_name=os.getenv("SUPABASE_TABLE", "documents"),
91
- query_name=os.getenv("SUPABASE_MATCH_FN", "match_documents"),
92
- )
93
- return vector_store.as_retriever(search_kwargs={"k": k, "filter": {"type": doc_type}})
94
-
95
-
96
- def _format_docs(docs: List[Document], doc_type: str, max_chars_per_doc: int = 1200) -> str:
97
- """Format documents with type-specific formatting."""
98
- blocks = []
99
- for i, doc in enumerate(docs, 1):
100
- text = (doc.page_content or "")[:max_chars_per_doc]
101
- meta = doc.metadata or {}
102
- src = meta.get("source", "N/A")
103
- page = meta.get("page_number", "N/A")
104
- kind = meta.get("type", "N/A")
105
- contact = meta.get("contact", None)
106
- header = f"[{i}] source={src} page={page} type={kind}"
107
- if contact:
108
- header += f" contact={contact}"
109
- blocks.append(f"<document>\n{header}\n{text}</document>".strip())
110
- return "\n\n---\n\n".join(blocks)
111
-
112
-
113
- def retrieve(state: AgentState) -> AgentState:
114
- """Separate retriever node: builds query, fetches docs for both types, formats context."""
115
- # Get query from state or last human message
116
- q = state.get("query")
117
- if not q:
118
- q = ""
119
- for msg in reversed(list(state.get("messages", []))):
120
- if getattr(msg, "type", "") == "human":
121
- q = (msg.content or "").strip()
122
- break
123
-
124
- # Get retrievers for both types
125
- formation_retriever = _get_retriever("formation", k=8)
126
- prestation_retriever = _get_retriever("prestation", k=8)
127
-
128
- # Retrieve documents for both types
129
- formation_docs = formation_retriever.invoke(q or "")
130
- prestation_docs = prestation_retriever.invoke(q or "")
131
-
132
- # Format contexts for both types
133
- formation_context = _format_docs(formation_docs, "formation")
134
- prestation_context = _format_docs(prestation_docs, "prestation")
135
-
136
- return {
137
- "formation_docs": formation_docs,
138
- "prestation_docs": prestation_docs,
139
- "formation_context": formation_context,
140
- "prestation_context": prestation_context
141
- }
142
-
143
-
144
- def call_model_with_history(state: AgentState) -> AgentState:
145
- """Generation node: SYSTEM + RAG context + conversation."""
146
- messages = list(state.get("messages", []))
147
- sys_msgs: List[BaseMessage] = [SystemMessage(content=SYSTEM_PROMPT_TEMPLATE)]
148
-
149
- # Get both contexts
150
- formation_context = state.get("formation_context", "")
151
- prestation_context = state.get("prestation_context", "")
152
-
153
- # Add formation context if available
154
- if formation_context:
155
- sys_msgs.append(SystemMessage(content=(
156
- "CONTEXTE FORMATIONS (extraits du catalogue formations; n'utilise rien d'autre):\n\n"
157
- f"{formation_context}\n\n"
158
- "Consignes formations: Utilise exclusivement ce contexte pour recommander les formations. "
159
- "Cite la page et la source pour chaque recommandation. "
160
- "Une formation = un document."
161
- )))
162
-
163
- # Add prestation context if available
164
- if prestation_context:
165
- sys_msgs.append(SystemMessage(content=(
166
- "CONTEXTE PRESTATIONS (extraits du catalogue services; n'utilise rien d'autre):\n\n"
167
- f"{prestation_context}\n\n"
168
- "Consignes prestations: Utilise exclusivement ce contexte pour recommander les prestations. "
169
- "Cite la page et la source pour chaque recommandation. "
170
- "Un document peut contenir plusieurs prestations."
171
- )))
172
-
173
- response = llm.invoke(sys_msgs + messages)
174
- return {"messages": messages + [AIMessage(content=response.content)]}
175
-
176
-
177
- # Build the graph
178
- workflow = StateGraph(AgentState)
179
-
180
- # Add nodes
181
- workflow.add_node("retrieve", retrieve)
182
- workflow.add_node("agent", call_model_with_history)
183
-
184
- # Set entry point
185
- workflow.set_entry_point("retrieve")
186
-
187
- # Add edges
188
- workflow.add_edge("retrieve", "agent")
189
- workflow.add_edge("agent", END)
190
-
191
- # Compile and return
192
- return workflow.compile()
193
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
graphs/workflows/conversation.py DELETED
@@ -1,24 +0,0 @@
1
- """Conversation workflow: retrieve -> chat_agent -> END."""
2
- from langgraph.graph import StateGraph, END
3
- from langchain_core.language_models.chat_models import BaseChatModel
4
-
5
- from graphs.state import AgentState
6
- from graphs.nodes.retrieval import retrieve_both_types
7
- from graphs.agents.chat_agent import chat_node
8
-
9
-
10
- def create_conversation_graph(llm: BaseChatModel):
11
- workflow = StateGraph(AgentState)
12
-
13
- # Nodes
14
- workflow.add_node("retrieve", retrieve_both_types)
15
- workflow.add_node("agent", chat_node(llm))
16
-
17
- # Entry and edges
18
- workflow.set_entry_point("retrieve")
19
- workflow.add_edge("retrieve", "agent")
20
- workflow.add_edge("agent", END)
21
-
22
- return workflow.compile()
23
-
24
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
graphs/workflows/{conversation_with_summary.py → orchestrated_v2.py} RENAMED
@@ -1,20 +1,32 @@
1
- """Workflow: retrieve -> chat_agent -> summarizer -> END."""
 
 
 
 
2
  from langgraph.graph import StateGraph, END
3
  from langchain_core.language_models.chat_models import BaseChatModel
4
 
5
  from graphs.state import AgentState
6
- from graphs.nodes.retrieval import retrieve_both_types
 
7
  from graphs.agents.chat_agent import chat_node
8
  from graphs.agents.summarizer_agent import summarizer_llm_node, summarizer_export_node
9
  from tools.pdf import markdown_to_pdf
10
  from tools.storage import upload_pdf_to_supabase
11
 
12
 
13
- def create_conversation_with_summary_graph(llm: BaseChatModel):
14
  workflow = StateGraph(AgentState)
15
 
16
- # Nodes
17
- workflow.add_node("retrieve", retrieve_both_types)
 
 
 
 
 
 
 
18
  workflow.add_node("agent", chat_node(llm))
19
  workflow.add_node("summarizer_llm", summarizer_llm_node(llm))
20
  workflow.add_node(
@@ -25,13 +37,33 @@ def create_conversation_with_summary_graph(llm: BaseChatModel):
25
  ),
26
  )
27
 
28
- # Entry and edges
29
- workflow.set_entry_point("retrieve")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  workflow.add_edge("retrieve", "agent")
31
- workflow.add_edge("agent", "summarizer_llm")
 
32
  workflow.add_edge("summarizer_llm", "summarizer_export")
33
  workflow.add_edge("summarizer_export", END)
34
 
35
- return workflow.compile()
36
-
37
 
 
1
+ """Orchestrated V2 workflow.
2
+
3
+ V2 is intentionally isolated from V1 for safe incremental rollout.
4
+ Current behavior mirrors V1 and can evolve independently.
5
+ """
6
  from langgraph.graph import StateGraph, END
7
  from langchain_core.language_models.chat_models import BaseChatModel
8
 
9
  from graphs.state import AgentState
10
+ from graphs.agents.classifier_agent import classifier_node
11
+ from graphs.nodes.retrieval import retrieve_catalogue, retrieve_projects
12
  from graphs.agents.chat_agent import chat_node
13
  from graphs.agents.summarizer_agent import summarizer_llm_node, summarizer_export_node
14
  from tools.pdf import markdown_to_pdf
15
  from tools.storage import upload_pdf_to_supabase
16
 
17
 
18
+ def create_orchestrated_graph_v2(llm: BaseChatModel, checkpointer=None):
19
  workflow = StateGraph(AgentState)
20
 
21
+ workflow.add_node("classify", classifier_node(llm))
22
+ workflow.add_node("retrieve", retrieve_catalogue)
23
+
24
+ def _router_passthrough(state: AgentState) -> AgentState:
25
+ q = state.get("query") or ""
26
+ return {"query": q}
27
+
28
+ workflow.add_node("retrieve_router", _router_passthrough)
29
+ workflow.add_node("retrieve_project", retrieve_projects)
30
  workflow.add_node("agent", chat_node(llm))
31
  workflow.add_node("summarizer_llm", summarizer_llm_node(llm))
32
  workflow.add_node(
 
37
  ),
38
  )
39
 
40
+ workflow.set_entry_point("classify")
41
+
42
+ workflow.add_conditional_edges(
43
+ "classify",
44
+ lambda s: getattr(s.get("classification"), "classification", "CLASSIC"),
45
+ {
46
+ "CLASSIC": "retrieve_router",
47
+ "SUMMARIZE": "summarizer_llm",
48
+ "UNKNOWN": "retrieve_router",
49
+ },
50
+ )
51
+
52
+ workflow.add_conditional_edges(
53
+ "retrieve_router",
54
+ lambda s: "PROJECT" if s.get("project_id") else "CLASSIC",
55
+ {
56
+ "PROJECT": "retrieve_project",
57
+ "CLASSIC": "retrieve",
58
+ },
59
+ )
60
+
61
+ workflow.add_edge("retrieve_project", "retrieve")
62
  workflow.add_edge("retrieve", "agent")
63
+ workflow.add_edge("agent", END)
64
+
65
  workflow.add_edge("summarizer_llm", "summarizer_export")
66
  workflow.add_edge("summarizer_export", END)
67
 
68
+ return workflow.compile(checkpointer=checkpointer)
 
69
 
postman_collection.json CHANGED
@@ -214,7 +214,7 @@
214
  ],
215
  "body": {
216
  "mode": "raw",
217
- "raw": "{\n \"message\": \"Bonjour, comment vas-tu?\",\n \"model\": \"gpt-4o\",\n \"agent_type\": \"simple\",\n \"stream\": false,\n \"temperature\": 0.7\n}"
218
  },
219
  "url": {
220
  "raw": "{{base_url}}/completion",
@@ -251,7 +251,7 @@
251
  ],
252
  "body": {
253
  "mode": "raw",
254
- "raw": "{\n \"message\": \"Explique-moi la théorie de la relativité en 2 phrases\",\n \"model\": \"mistral-large-latest\",\n \"agent_type\": \"simple\",\n \"stream\": false,\n \"temperature\": 0.7\n}"
255
  },
256
  "url": {
257
  "raw": "{{base_url}}/completion",
@@ -288,7 +288,7 @@
288
  ],
289
  "body": {
290
  "mode": "raw",
291
- "raw": "{\n \"message\": \"Raconte-moi une courte histoire\",\n \"model\": \"gpt-3.5-turbo\",\n \"agent_type\": \"simple\",\n \"stream\": true,\n \"temperature\": 0.9\n}"
292
  },
293
  "url": {
294
  "raw": "{{base_url}}/completion",
@@ -325,7 +325,7 @@
325
  ],
326
  "body": {
327
  "mode": "raw",
328
- "raw": "{\n \"message\": \"Et en Python?\",\n \"model\": \"gpt-4o\",\n \"stream\": false,\n \"conversation_history\": [\n {\n \"role\": \"user\",\n \"content\": \"Comment faire une boucle en JavaScript?\"\n },\n {\n \"role\": \"assistant\",\n \"content\": \"En JavaScript, vous pouvez utiliser: for (let i = 0; i < 10; i++) { console.log(i); }\"\n }\n ]\n}"
329
  },
330
  "url": {
331
  "raw": "{{base_url}}/completion",
@@ -362,7 +362,7 @@
362
  ],
363
  "body": {
364
  "mode": "raw",
365
- "raw": "{\n \"message\": \"Écris un poème court sur l'IA\",\n \"model\": \"gpt-4o\",\n \"agent_type\": \"simple\",\n \"stream\": false,\n \"temperature\": 1.2,\n \"max_tokens\": 150\n}"
366
  },
367
  "url": {
368
  "raw": "{{base_url}}/completion",
 
214
  ],
215
  "body": {
216
  "mode": "raw",
217
+ "raw": "{\n \"message\": \"Bonjour, comment vas-tu?\",\n \"model\": \"mistral-large-latest\",\n \"agent\": \"V2\",\n \"stream\": false,\n \"temperature\": 0.7\n}"
218
  },
219
  "url": {
220
  "raw": "{{base_url}}/completion",
 
251
  ],
252
  "body": {
253
  "mode": "raw",
254
+ "raw": "{\n \"message\": \"Explique-moi la théorie de la relativité en 2 phrases\",\n \"model\": \"mistral-large-latest\",\n \"agent\": \"V2\",\n \"stream\": false,\n \"temperature\": 0.7\n}"
255
  },
256
  "url": {
257
  "raw": "{{base_url}}/completion",
 
288
  ],
289
  "body": {
290
  "mode": "raw",
291
+ "raw": "{\n \"message\": \"Raconte-moi une courte histoire\",\n \"model\": \"mistral-large-latest\",\n \"agent\": \"V2\",\n \"stream\": true,\n \"temperature\": 0.9\n}"
292
  },
293
  "url": {
294
  "raw": "{{base_url}}/completion",
 
325
  ],
326
  "body": {
327
  "mode": "raw",
328
+ "raw": "{\n \"message\": \"Et en Python?\",\n \"model\": \"mistral-large-latest\",\n \"stream\": false,\n \"conversation_history\": [\n {\n \"role\": \"user\",\n \"content\": \"Comment faire une boucle en JavaScript?\"\n },\n {\n \"role\": \"assistant\",\n \"content\": \"En JavaScript, vous pouvez utiliser: for (let i = 0; i < 10; i++) { console.log(i); }\"\n }\n ]\n}"
329
  },
330
  "url": {
331
  "raw": "{{base_url}}/completion",
 
362
  ],
363
  "body": {
364
  "mode": "raw",
365
+ "raw": "{\n \"message\": \"Écris un poème court sur l'IA\",\n \"model\": \"mistral-large-latest\",\n \"agent\": \"V2\",\n \"stream\": false,\n \"temperature\": 1.2,\n \"max_tokens\": 150\n}"
366
  },
367
  "url": {
368
  "raw": "{{base_url}}/completion",
services/agent_registry.py CHANGED
@@ -1,37 +1,32 @@
1
  """Registry for managing multiple LangGraph agents."""
2
- from typing import Dict, Callable, Any
3
  from langchain_core.language_models.chat_models import BaseChatModel
4
- from domain.enums import AgentType
5
- from graphs.base_graph import create_simple_graph, create_simple_graph_with_history
6
  from graphs.workflows.orchestrated import create_orchestrated_graph
 
7
 
8
 
9
  class AgentRegistry:
10
  """
11
  Registry for managing multiple agent graph builders.
12
 
13
- This allows for easy addition of new agent types without modifying
14
- the API layer. Each agent type maps to a graph builder function.
15
  """
16
 
17
  def __init__(self):
18
  """Initialize the agent registry with default agents."""
19
- self._builders: Dict[AgentType, Callable[[BaseChatModel], Any]] = {
20
- AgentType.SIMPLE: create_orchestrated_graph,
21
- # AgentType.RAG: create_rag_graph, # À implémenter plus tard
22
- # AgentType.TOOLS: create_tools_graph, # À implémenter plus tard
23
  }
24
-
25
- self._descriptions = {
26
- AgentType.SIMPLE: "Simple conversational agent without tools or memory",
27
- AgentType.RAG: "Agent with Retrieval Augmented Generation (not yet implemented)",
28
- AgentType.TOOLS: "Agent with tools like web search, calculator (not yet implemented)",
29
- AgentType.CUSTOM: "Custom agent graph (not yet implemented)"
30
  }
31
 
32
  def register_agent(
33
  self,
34
- agent_type: AgentType,
35
  builder: Callable[[BaseChatModel], Any],
36
  description: str = ""
37
  ) -> None:
@@ -39,45 +34,34 @@ class AgentRegistry:
39
  Register a new agent builder.
40
 
41
  Args:
42
- agent_type: Type of agent
43
  builder: Function that takes an LLM and returns a compiled graph
44
  description: Description of the agent
45
  """
46
- self._builders[agent_type] = builder
 
47
  if description:
48
- self._descriptions[agent_type] = description
49
-
50
- def get_builder(self, agent_type: AgentType) -> Callable[[BaseChatModel], Any]:
51
- """
52
- Get the builder function for an agent type.
53
-
54
- Args:
55
- agent_type: Type of agent
56
-
57
- Returns:
58
- Builder function
59
-
60
- Raises:
61
- ValueError: If agent type is not registered
62
  """
63
- if agent_type not in self._builders:
 
64
  raise ValueError(
65
- f"Agent type '{agent_type}' not implemented. "
66
- f"Available types: {list(self._builders.keys())}"
67
  )
68
- return self._builders[agent_type]
69
-
70
- def is_available(self, agent_type: AgentType) -> bool:
71
- """
72
- Check if an agent type is available.
73
-
74
- Args:
75
- agent_type: Type of agent
76
-
77
- Returns:
78
- True if agent is available, False otherwise
79
- """
80
- return agent_type in self._builders
81
 
82
  def list_agents(self) -> list[dict]:
83
  """
@@ -86,18 +70,15 @@ class AgentRegistry:
86
  Returns:
87
  List of agent information dictionaries
88
  """
89
- agents = []
90
- for agent_type in AgentType:
91
- agents.append({
92
- "type": agent_type.value,
93
- "name": agent_type.value.capitalize(),
94
- "description": self._descriptions.get(
95
- agent_type,
96
- "No description available"
97
- ),
98
- "available": self.is_available(agent_type)
99
- })
100
- return agents
101
 
102
 
103
  # Singleton instance
 
1
  """Registry for managing multiple LangGraph agents."""
2
+ from typing import Dict, Callable, Any, Optional
3
  from langchain_core.language_models.chat_models import BaseChatModel
 
 
4
  from graphs.workflows.orchestrated import create_orchestrated_graph
5
+ from graphs.workflows.orchestrated_v2 import create_orchestrated_graph_v2
6
 
7
 
8
  class AgentRegistry:
9
  """
10
  Registry for managing multiple agent graph builders.
11
 
12
+ This allows for easy addition of new agent ids without modifying
13
+ the API layer. Each agent id maps to a graph builder function.
14
  """
15
 
16
  def __init__(self):
17
  """Initialize the agent registry with default agents."""
18
+ self._agent_builders: Dict[str, Callable[[BaseChatModel], Any]] = {
19
+ "v1": create_orchestrated_graph, # V1 (unchanged)
20
+ "v2": create_orchestrated_graph_v2, # V2 (isolated)
 
21
  }
22
+ self._descriptions: Dict[str, str] = {
23
+ "v1": "Current production orchestrated workflow",
24
+ "v2": "Isolated V2 workflow (default)",
 
 
 
25
  }
26
 
27
  def register_agent(
28
  self,
29
+ agent: str,
30
  builder: Callable[[BaseChatModel], Any],
31
  description: str = ""
32
  ) -> None:
 
34
  Register a new agent builder.
35
 
36
  Args:
37
+ agent: Agent identifier (string)
38
  builder: Function that takes an LLM and returns a compiled graph
39
  description: Description of the agent
40
  """
41
+ agent_key = agent.strip().lower()
42
+ self._agent_builders[agent_key] = builder
43
  if description:
44
+ self._descriptions[agent_key] = description
45
+
46
+ def get_builder_for_request(
47
+ self,
48
+ agent: Optional[str],
49
+ ) -> Callable[[BaseChatModel], Any]:
50
+ """Resolve graph builder from request.
51
+
52
+ If agent is missing, defaults to V2.
 
 
 
 
 
53
  """
54
+ agent_key = (agent or "V2").strip().lower()
55
+ if agent_key not in self._agent_builders:
56
  raise ValueError(
57
+ f"Agent '{agent}' not implemented. "
58
+ f"Available agents: {list(self._agent_builders.keys())}"
59
  )
60
+ return self._agent_builders[agent_key]
61
+
62
+ def resolve_agent_id(self, agent: Optional[str]) -> str:
63
+ """Return canonical agent id used in API metadata."""
64
+ return (agent or "V2").strip().upper()
 
 
 
 
 
 
 
 
65
 
66
  def list_agents(self) -> list[dict]:
67
  """
 
70
  Returns:
71
  List of agent information dictionaries
72
  """
73
+ return [
74
+ {
75
+ "type": key.upper(),
76
+ "name": key.upper(),
77
+ "description": self._descriptions.get(key, "No description available"),
78
+ "available": True,
79
+ }
80
+ for key in sorted(self._agent_builders.keys())
81
+ ]
 
 
 
82
 
83
 
84
  # Singleton instance
services/agent_service.py CHANGED
@@ -4,7 +4,7 @@ import time
4
  from langchain_core.messages import AIMessageChunk, HumanMessage, AIMessage, BaseMessage, SystemMessage
5
  from langchain_core.language_models.chat_models import BaseChatModel
6
  from langgraph.checkpoint.memory import MemorySaver
7
- from domain.enums import ModelName, AgentType
8
  from .llm_service import llm_service
9
  from .agent_registry import agent_registry
10
  from .usage_utils import normalize_usage
@@ -34,7 +34,7 @@ class AgentService:
34
  self,
35
  message: str,
36
  model_name: ModelName,
37
- agent_type: AgentType = AgentType.SIMPLE,
38
  temperature: float = 0.7,
39
  max_tokens: Optional[int] = None,
40
  conversation_history: Optional[List[Dict[str, str]]] = None,
@@ -47,7 +47,7 @@ class AgentService:
47
  Args:
48
  message: User message
49
  model_name: LLM model to use
50
- agent_type: Type of agent graph
51
  temperature: Sampling temperature
52
  max_tokens: Max tokens to generate
53
  conversation_history: Optional conversation history (ignored if conversation_id is set)
@@ -65,7 +65,8 @@ class AgentService:
65
  max_tokens=max_tokens
66
  )
67
 
68
- builder = agent_registry.get_builder(agent_type)
 
69
  use_memory = bool(conversation_id)
70
  graph = builder(llm, checkpointer=_text_checkpointer) if use_memory else builder(llm)
71
 
@@ -114,7 +115,7 @@ class AgentService:
114
  return {
115
  "response": response_content,
116
  "model": model_name.value,
117
- "agent_type": agent_type.value,
118
  "usage": usage,
119
  "metadata": base_metadata,
120
  }
@@ -123,7 +124,7 @@ class AgentService:
123
  self,
124
  message: str,
125
  model_name: ModelName,
126
- agent_type: AgentType = AgentType.SIMPLE,
127
  temperature: float = 0.7,
128
  max_tokens: Optional[int] = None,
129
  conversation_history: Optional[List[Dict[str, str]]] = None,
@@ -136,7 +137,7 @@ class AgentService:
136
  Args:
137
  message: User message
138
  model_name: LLM model to use
139
- agent_type: Type of agent graph
140
  temperature: Sampling temperature
141
  max_tokens: Max tokens to generate
142
  conversation_history: Optional conversation history (ignored if conversation_id is set)
@@ -154,7 +155,8 @@ class AgentService:
154
  max_tokens=max_tokens
155
  )
156
 
157
- builder = agent_registry.get_builder(agent_type)
 
158
  use_memory = bool(conversation_id)
159
  graph = builder(llm, checkpointer=_text_checkpointer) if use_memory else builder(llm)
160
 
@@ -255,7 +257,7 @@ class AgentService:
255
  "done": False,
256
  "metadata": {
257
  "model": model_name.value,
258
- "agent_type": agent_type.value,
259
  "usage": usage_totals
260
  },
261
  "documents": documents
@@ -278,7 +280,7 @@ class AgentService:
278
  "done": True,
279
  "metadata": {
280
  "model": model_name.value,
281
- "agent_type": agent_type.value,
282
  "usage": usage_totals,
283
  "usage_by_model": usage_by_model,
284
  "latency_s": latency_s,
 
4
  from langchain_core.messages import AIMessageChunk, HumanMessage, AIMessage, BaseMessage, SystemMessage
5
  from langchain_core.language_models.chat_models import BaseChatModel
6
  from langgraph.checkpoint.memory import MemorySaver
7
+ from domain.enums import ModelName
8
  from .llm_service import llm_service
9
  from .agent_registry import agent_registry
10
  from .usage_utils import normalize_usage
 
34
  self,
35
  message: str,
36
  model_name: ModelName,
37
+ agent: Optional[str] = None,
38
  temperature: float = 0.7,
39
  max_tokens: Optional[int] = None,
40
  conversation_history: Optional[List[Dict[str, str]]] = None,
 
47
  Args:
48
  message: User message
49
  model_name: LLM model to use
50
+ agent: Agent identifier. Defaults to "V2" when omitted.
51
  temperature: Sampling temperature
52
  max_tokens: Max tokens to generate
53
  conversation_history: Optional conversation history (ignored if conversation_id is set)
 
65
  max_tokens=max_tokens
66
  )
67
 
68
+ resolved_agent = agent_registry.resolve_agent_id(agent)
69
+ builder = agent_registry.get_builder_for_request(agent=resolved_agent)
70
  use_memory = bool(conversation_id)
71
  graph = builder(llm, checkpointer=_text_checkpointer) if use_memory else builder(llm)
72
 
 
115
  return {
116
  "response": response_content,
117
  "model": model_name.value,
118
+ "agent": resolved_agent,
119
  "usage": usage,
120
  "metadata": base_metadata,
121
  }
 
124
  self,
125
  message: str,
126
  model_name: ModelName,
127
+ agent: Optional[str] = None,
128
  temperature: float = 0.7,
129
  max_tokens: Optional[int] = None,
130
  conversation_history: Optional[List[Dict[str, str]]] = None,
 
137
  Args:
138
  message: User message
139
  model_name: LLM model to use
140
+ agent: Agent identifier. Defaults to "V2" when omitted.
141
  temperature: Sampling temperature
142
  max_tokens: Max tokens to generate
143
  conversation_history: Optional conversation history (ignored if conversation_id is set)
 
155
  max_tokens=max_tokens
156
  )
157
 
158
+ resolved_agent = agent_registry.resolve_agent_id(agent)
159
+ builder = agent_registry.get_builder_for_request(agent=resolved_agent)
160
  use_memory = bool(conversation_id)
161
  graph = builder(llm, checkpointer=_text_checkpointer) if use_memory else builder(llm)
162
 
 
257
  "done": False,
258
  "metadata": {
259
  "model": model_name.value,
260
+ "agent": resolved_agent,
261
  "usage": usage_totals
262
  },
263
  "documents": documents
 
280
  "done": True,
281
  "metadata": {
282
  "model": model_name.value,
283
+ "agent": resolved_agent,
284
  "usage": usage_totals,
285
  "usage_by_model": usage_by_model,
286
  "latency_s": latency_s,
services/voice/voice_agent_service.py CHANGED
@@ -5,7 +5,7 @@ from typing import Optional
5
  from langchain_core.messages import HumanMessage
6
  from langgraph.checkpoint.memory import MemorySaver
7
 
8
- from domain.enums import ModelName, AgentType
9
  from services.llm_service import llm_service
10
  from services.agent_registry import agent_registry
11
 
@@ -29,7 +29,7 @@ class VoiceAgentService:
29
  self.checkpointer = MemorySaver()
30
 
31
  llm = llm_service.get_llm(model_name=model_name, streaming=False)
32
- builder = agent_registry.get_builder(AgentType.SIMPLE)
33
  self.graph = builder(llm, checkpointer=self.checkpointer)
34
 
35
  async def process_message(
 
5
  from langchain_core.messages import HumanMessage
6
  from langgraph.checkpoint.memory import MemorySaver
7
 
8
+ from domain.enums import ModelName
9
  from services.llm_service import llm_service
10
  from services.agent_registry import agent_registry
11
 
 
29
  self.checkpointer = MemorySaver()
30
 
31
  llm = llm_service.get_llm(model_name=model_name, streaming=False)
32
+ builder = agent_registry.get_builder_for_request("V1")
33
  self.graph = builder(llm, checkpointer=self.checkpointer)
34
 
35
  async def process_message(
services/voice/voice_pipeline.py CHANGED
@@ -6,7 +6,7 @@ from dataclasses import dataclass, field
6
 
7
  from langchain_core.runnables import RunnableGenerator
8
 
9
- from domain.enums import ModelName, AgentType
10
  from services.agent_service import agent_service
11
  from .stt_service import STTService, stt_service
12
  from .tts_service import TTSService, tts_service, TTSVoice
@@ -28,7 +28,7 @@ logger = logging.getLogger(__name__)
28
  class VoiceSessionConfig:
29
  """Configuration for a voice session."""
30
  model: ModelName = ModelName.MISTRAL_LARGE
31
- agent_type: AgentType = AgentType.SIMPLE # Uses orchestrated graph by default
32
  voice: TTSVoice = "alloy"
33
  language: Optional[str] = None
34
  temperature: float = 0.7
@@ -192,7 +192,7 @@ class VoicePipeline:
192
  async for chunk in agent_service.stream(
193
  message=text,
194
  model_name=config.model,
195
- agent_type=config.agent_type,
196
  temperature=config.temperature,
197
  max_tokens=config.max_tokens,
198
  conversation_history=config.conversation_history,
@@ -209,7 +209,7 @@ class VoicePipeline:
209
  yield AgentOutputEvent.create(
210
  response=full_response,
211
  model=metadata.get("model"),
212
- agent_type=metadata.get("agent_type"),
213
  usage=metadata.get("usage")
214
  )
215
 
 
6
 
7
  from langchain_core.runnables import RunnableGenerator
8
 
9
+ from domain.enums import ModelName
10
  from services.agent_service import agent_service
11
  from .stt_service import STTService, stt_service
12
  from .tts_service import TTSService, tts_service, TTSVoice
 
28
  class VoiceSessionConfig:
29
  """Configuration for a voice session."""
30
  model: ModelName = ModelName.MISTRAL_LARGE
31
+ agent: Optional[str] = None # Defaults to V2 when omitted
32
  voice: TTSVoice = "alloy"
33
  language: Optional[str] = None
34
  temperature: float = 0.7
 
192
  async for chunk in agent_service.stream(
193
  message=text,
194
  model_name=config.model,
195
+ agent=config.agent,
196
  temperature=config.temperature,
197
  max_tokens=config.max_tokens,
198
  conversation_history=config.conversation_history,
 
209
  yield AgentOutputEvent.create(
210
  response=full_response,
211
  model=metadata.get("model"),
212
+ agent_type=metadata.get("agent"),
213
  usage=metadata.get("usage")
214
  )
215