返回 CodeWhale
mod.rs
根目录 / crates / tui / src / rlm / mod.rs
1 //! Recursive Language Model (RLM) loop — paper-spec Algorithm 1.
2 //!
3 //! Implements Zhang, Kraska & Khattab (arXiv:2512.24601, §2 Algorithm 1):
4 //!
5 //! ```text
6 //! state ← InitREPL(prompt=P)
7 //! state ← AddFunction(state, sub_RLM)
8 //! hist ← [Metadata(state)]
9 //! while True:
10 //! code ← LLM(hist)
11 //! (state, stdout) ← REPL(state, code)
12 //! hist ← hist ∥ code ∥ Metadata(stdout)
13 //! if state[Final] is set:
14 //! return state[Final]
15 //! ```
16 //!
17 //! Invariants:
18 //! - `P` is held only as a REPL variable (`context` / `ctx`); never
19 //! appears in the root LLM's window.
20 //! - The root LLM receives small metadata messages — length, preview,
21 //! helper list, prior-round summary.
22 //! - Code rounds and sub-LLM calls travel over a single stdin/stdout
23 //! pipe to a long-lived Python subprocess. No HTTP sidecar.
24
25 use crate::models::Usage;
26
27 pub mod bridge;
28 pub mod prompt;
29 pub mod session;
30 pub mod turn;
31
32 pub(crate) use bridge::ModelClientRlmAdapter;
33 pub use bridge::RlmBridge;
34 pub use prompt::rlm_system_prompt;
35 pub use turn::{RlmTermination, RlmTurnResult, run_rlm_turn, run_rlm_turn_with_root};
36
37 fn add_usage_with_prompt_cache(total: &mut Usage, delta: &Usage) {
38 total.input_tokens = total.input_tokens.saturating_add(delta.input_tokens);
39 total.output_tokens = total.output_tokens.saturating_add(delta.output_tokens);
40 total.prompt_cache_hit_tokens =
41 add_optional_usage(total.prompt_cache_hit_tokens, delta.prompt_cache_hit_tokens);
42 total.prompt_cache_miss_tokens = add_optional_usage(
43 total.prompt_cache_miss_tokens,
44 delta.prompt_cache_miss_tokens,
45 );
46 total.prompt_cache_write_tokens = add_optional_usage(
47 total.prompt_cache_write_tokens,
48 delta.prompt_cache_write_tokens,
49 );
50 total.reasoning_tokens = add_optional_usage(total.reasoning_tokens, delta.reasoning_tokens);
51 total.reasoning_replay_tokens =
52 add_optional_usage(total.reasoning_replay_tokens, delta.reasoning_replay_tokens);
53 if let Some(delta_server) = delta.server_tool_use.as_ref() {
54 let total_server = total.server_tool_use.get_or_insert_default();
55 total_server.code_execution_requests = add_optional_usage(
56 total_server.code_execution_requests,
57 delta_server.code_execution_requests,
58 );
59 total_server.tool_search_requests = add_optional_usage(
60 total_server.tool_search_requests,
61 delta_server.tool_search_requests,
62 );
63 }
64 }
65
66 fn add_optional_usage(total: Option<u32>, delta: Option<u32>) -> Option<u32> {
67 match (total, delta) {
68 (Some(total), Some(delta)) => Some(total.saturating_add(delta)),
69 (None, Some(delta)) => Some(delta),
70 (Some(total), None) => Some(total),
71 (None, None) => None,
72 }
73 }
74
75 #[cfg(test)]
76 mod tests {
77 use super::*;
78
79 #[test]
80 fn add_usage_with_prompt_cache_preserves_cache_counts() {
81 let mut total = Usage {
82 input_tokens: 100,
83 output_tokens: 10,
84 prompt_cache_hit_tokens: Some(80),
85 prompt_cache_miss_tokens: Some(20),
86 ..Usage::default()
87 };
88 let delta = Usage {
89 input_tokens: 50,
90 output_tokens: 5,
91 prompt_cache_hit_tokens: Some(30),
92 prompt_cache_miss_tokens: Some(20),
93 reasoning_tokens: Some(4),
94 reasoning_replay_tokens: Some(3),
95 server_tool_use: Some(crate::models::ServerToolUsage {
96 code_execution_requests: Some(2),
97 tool_search_requests: Some(1),
98 }),
99 ..Usage::default()
100 };
101
102 add_usage_with_prompt_cache(&mut total, &delta);
103
104 assert_eq!(total.input_tokens, 150);
105 assert_eq!(total.output_tokens, 15);
106 assert_eq!(total.prompt_cache_hit_tokens, Some(110));
107 assert_eq!(total.prompt_cache_miss_tokens, Some(40));
108 assert_eq!(total.reasoning_tokens, Some(4));
109 assert_eq!(total.reasoning_replay_tokens, Some(3));
110 assert_eq!(
111 total.server_tool_use,
112 Some(crate::models::ServerToolUsage {
113 code_execution_requests: Some(2),
114 tool_search_requests: Some(1),
115 })
116 );
117 }
118 }
119
119 lines RUST