tokio_prompt_orchestrator/session/mod.rs
1//! # Session Context Manager
2//!
3//! Tracks multi-turn conversation history per `SessionId` and enriches
4//! incoming `PromptRequest`s with relevant prior context before they enter
5//! the pipeline.
6//!
7//! ## Overview
8//!
9//! Many LLM applications need conversational memory: the model must "know"
10//! what was said in earlier turns of the same chat session. Without
11//! session tracking every request arrives context-free and the user has to
12//! repeat themselves.
13//!
14//! The [`SessionContext`] type provides:
15//!
16//! - **Per-session history** stored in a `DashMap` (lock-free concurrent
17//! hash map). Each entry is a bounded ring buffer of
18//! [`ConversationTurn`]s.
19//! - **Auto-injection**: [`SessionContext::enrich`] prepends the last
20//! `context_window` turns to the prompt input, formatted as a human-readable
21//! dialogue transcript.
22//! - **Response recording**: [`SessionContext::record_response`] appends the
23//! model's answer after a successful inference call.
24//! - **Sliding window**: only the most recent `max_turns` turns are kept;
25//! older entries are evicted automatically to bound memory use.
26//! - **Session expiry**: inactive sessions are evicted after a configurable
27//! TTL, preventing unbounded map growth.
28//! - **Optional summarisation stub**: when history grows beyond
29//! `summarise_after_turns`, callers receive a
30//! [`SessionAction::RequestSummary`] signal. The caller is responsible
31//! for sending a summarisation request through the pipeline and replacing
32//! history with the returned summary via [`SessionContext::summarise`].
33//!
34//! ## Example
35//!
36//! ```no_run
37//! use std::collections::HashMap;
38//! use std::time::Duration;
39//! use tokio_prompt_orchestrator::{SessionId, PromptRequest};
40//! use tokio_prompt_orchestrator::session::{SessionContext, SessionConfig};
41//!
42//! #[tokio::main]
43//! async fn main() {
44//! let ctx = SessionContext::new(SessionConfig::default());
45//!
46//! let session = SessionId::new("alice");
47//!
48//! // First turn — no prior history, prompt is unchanged.
49//! let req = PromptRequest {
50//! session: session.clone(),
51//! request_id: "r1".into(),
52//! input: "What is the capital of France?".into(),
53//! meta: HashMap::new(),
54//! deadline: None,
55//! };
56//! let (enriched, _action) = ctx.enrich(req).await;
57//! assert_eq!(enriched.input, "What is the capital of France?");
58//!
59//! // Record the model's response.
60//! ctx.record_response(&session, "The capital of France is Paris.").await;
61//!
62//! // Second turn — prior history is prepended automatically.
63//! let req2 = PromptRequest {
64//! session: session.clone(),
65//! request_id: "r2".into(),
66//! input: "What is its population?".into(),
67//! meta: HashMap::new(),
68//! deadline: None,
69//! };
70//! let (enriched2, _action) = ctx.enrich(req2).await;
71//! assert!(enriched2.input.contains("Paris"));
72//! }
73//! ```
74
75pub mod budget;
76pub mod context;
77
78pub use budget::{
79 BudgetError, BudgetOutcome, SessionBudget, SessionBudgetSnapshot, SessionLimits,
80};
81pub use context::{
82 ConversationTurn, SessionAction, SessionConfig, SessionContext, SessionStats, TurnRole,
83};