Synapse Core repose sur un flux d'exécution séquentiel et hautement événementiel. Le pipeline de prompt est le cœur du système.
Le système de prompt est structuré en 5 phases événementielles orchestrées par PromptPipeline :
PromptBuildEvent)Construction initiale du prompt par ContextBuilderSubscriber :
SynapseTone.systemPrompt si agent a un ton)ToolRegistry)PromptEnrichEvent)Enrichissement du contexte via subscribers :
RagContextSubscriber : Injecte les chunks RAG pertinents (si agent a des sources RAG)MemoryContextSubscriber : Injecte les souvenirs vectoriels de l'utilisateur (score ≥ 0.7)PromptOptimizeEvent)Optimisation et troncature :
ContextTruncationSubscriber : Tronque l'historique et le contexte si dépassement de maxTokensPromptFinalizeEvent)Finalisation avec la directive fondamentale :
MasterPromptSubscriber : Injecte le master prompt global en queue du message système — s'exécute après ENRICH et OPTIMIZE pour garantir qu'il est toujours présent et jamais tronquéPromptCaptureEvent)Capture du prompt final pour le debug :
DebugLogSubscriber : Enregistre le payload complet en SynapseDebugLogUne fois le prompt construit, le système entre dans une boucle multi-tour (max maxTurns = 5) :
Tour 1:
↓
ChatService::ask() → Config Load
↓
PromptPipeline (5 phases) → Prompt final
↓
LlmClient::streamGenerateContent() → Streaming SSE
↓
ChunkProcessor (parse SSE) → NormalizedChunk
↓
Dispatch SynapseChunkReceivedEvent (répété chaque token)
↓
Tool Call détecté ?
NON → Fin (Dispatch SynapseGenerationCompletedEvent)
OUI → Exécution ci-dessous
ToolExecutor::execute(toolName, params) → Résultat
↓
Dispatch SynapseToolCallCompletedEvent
↓
Tour 2+ : Retour au streaming avec résultat du tool en message assistant
| Composant | Rôle |
|---|---|
| ChatService | Point d'entrée principal (ask(), resetConversation()) |
| ConfigProviderInterface | Fournit la config runtime (via BDD) |
| PromptPipeline | Orchestre les 5 phases du prompt |
| PromptBuilder | Construit le prompt système initial |
| Subscribers | Enrichissent le prompt (RAG, mémoire, contexte app) |
| LlmClient | Envoie au provider (Gemini, OVH, etc.) |
| ChunkProcessor | Parse le streaming SSE en chunks normalisés |
| MultiTurnExecutor | Gère la boucle jusqu'à maxTurns |
| ToolRegistry | Registre des outils disponibles |
| ToolExecutor | Exécute les tool calls |
| AgentResolver | Résout un agent par nom (agents code + agents BDD) |
| WorkflowRunner | Exécute un SynapseWorkflow via MultiAgent |
Ordre de séquence :
SynapseGenerationStartedEvent — Début globalPromptBuildEvent, PromptEnrichEvent, PromptOptimizeEvent, PromptFinalizeEvent, PromptCaptureEventSynapseChunkReceivedEvent — Répété pour chaque token (streaming)SynapseStatusChangedEvent — Passage de thinking → generating (optionnel, si extended thinking)SynapseTokenStreamedEvent — Chaque token individuel (granularité maximale)SynapseToolCallRequestedEvent — Si outil demandéToolExecutor exécute → SynapseToolCallCompletedEvent — Résultat du toolSynapseGenerationCompletedEvent — Fin de génération textuelleSynapseExchangeCompletedEvent — Fin technique (logs, debug)Hors cycle : SynapseEmbeddingCompletedEvent, SynapseSpendingLimitExceededEvent, SynapseFallbackActivatedEvent
graph TD
User([Message Utilisateur]) --> ChatService[ChatService::ask]
ChatService --> ConfigLoad[Chargement Config]
ConfigLoad --> Pipeline["PromptPipeline<br/>(5 phases)"]
Pipeline --> Build["BUILD<br/>PromptBuildEvent"]
Build --> Enrich["ENRICH<br/>PromptEnrichEvent<br/>(RAG, Mémoire, Master)"]
Enrich --> Optimize["OPTIMIZE<br/>PromptOptimizeEvent<br/>(Troncature)"]
Optimize --> Finalize["FINALIZE<br/>PromptFinalizeEvent"]
Finalize --> Capture["CAPTURE<br/>PromptCaptureEvent<br/>(Debug Log)"]
Capture --> LlmClient[LlmClient::streamGenerateContent]
LlmClient --> Streaming[Streaming SSE]
Streaming --> Processor[ChunkProcessor]
Processor --> DispatchChunk["Dispatch<br/>SynapseChunkReceivedEvent"]
DispatchChunk --> Check{Tool Call ?}
Check -- NON --> Complete["Dispatch<br/>SynapseGenerationCompletedEvent"]
Check -- OUI --> ToolExec[ToolExecutor::execute]
ToolExec --> DispatchTool["Dispatch<br/>SynapseToolCallCompletedEvent"]
DispatchTool --> Streaming
Complete --> ExchangeEvent["Dispatch<br/>SynapseExchangeCompletedEvent"]
ExchangeEvent --> DB["Persistance BDD<br/>(SynapseMessage, etc.)"]
DB --> UserResult([Réponse finale])
En plus du flux chat standard, Synapse supporte l'exécution de pipelines multi-agents via WorkflowRunner.
WorkflowRunner::run(SynapseWorkflow, Input, options)
↓
MultiAgent::execute(definition, input, AgentContext)
↓ pour chaque étape
AgentResolver::resolve(agent_name, childContext)
→ ConfiguredAgent (entité BDD) ou agent code (classe PHP)
↓
AgentInterface::call(Input, options) ← ChatService::ask() en sous-jacent
↓
Output collecté, output_key stocké
↓
Étape suivante avec input_mapping depuis les outputs précédents
↓
SynapseWorkflowRun enregistré (statut, tokens, durée, inputs, outputs)
ConfiguredAgent est le pont entre une entité SynapseAgent (BDD) et le contrat AgentInterface. Il est créé à la volée par AgentResolver et délègue l'exécution à ChatService::ask().
Deux comportements clés garantis par ConfiguredAgent::call() :
MultiAgent passe un Input::ofStructured(...) (mapping de sorties d'étapes précédentes), buildMessageFromStructured() convertit l'input en texte exploitable.module='agent' et action='agent_call' sont injectées par défaut pour que chaque appel soit traçé dans SynapseLlmCall.CodeAgentRegistry — agents "code" (classes PHP avec tag synapse.agent)AgentRegistry → SynapseAgentRepository::findByKey() — agents "config" (BDD, y compris sandbox)En cas de collision de nom, l'agent code prend la préséance (warning loggé).
AgentContext transporte la profondeur courante et la profondeur max (synapse.agents.max_depth, défaut : 2 via AgentContext::DEFAULT_MAX_DEPTH). Si la limite est atteinte, AgentResolver dispatche AgentDepthLimitReachedEvent et lève AgentDepthExceededException.
Internement, tous les messages sont dans le format OpenAI Chat Completions :
$contents = [
['role' => 'system', 'content' => '...system prompt...'],
['role' => 'user', 'content' => '...message utilisateur...'],
['role' => 'assistant', 'content' => '...réponse modèle...', 'tool_calls' => [...]],
['role' => 'tool', 'tool_call_id' => '...', 'content' => '...résultat outil...'],
];
Chaque LlmClient traduit de/vers ce format (ex: GeminiClient adapte Gemini→OpenAI, OvhAiClient passe directement).
How can I help you explore Laravel packages today?