1use rand::Rng;
10use serde::{Deserialize, Serialize};
11
12#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)]
14pub struct Fitness(pub f64);
15
16impl Fitness {
17 pub fn new(value: f64) -> Self {
19 Self(value.clamp(0.0, 1.0))
20 }
21
22 pub fn perfect() -> Self {
24 Self(1.0)
25 }
26
27 pub fn zero() -> Self {
29 Self(0.0)
30 }
31
32 pub fn value(&self) -> f64 {
34 self.0
35 }
36}
37
38#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct Genome {
41 pub prompt: String,
43 pub traits: Vec<f64>,
45 pub trait_names: Vec<String>,
47}
48
49impl Genome {
50 pub fn new(prompt: &str) -> Self {
52 Self {
53 prompt: prompt.to_string(),
54 traits: vec![0.5; 5], trait_names: vec![
56 "exploration".to_string(),
57 "precision".to_string(),
58 "creativity".to_string(),
59 "skepticism".to_string(),
60 "verbosity".to_string(),
61 ],
62 }
63 }
64
65 pub fn with_traits(prompt: &str, traits: Vec<(String, f64)>) -> Self {
67 let (names, values): (Vec<_>, Vec<_>) = traits.into_iter().unzip();
68 Self {
69 prompt: prompt.to_string(),
70 traits: values,
71 trait_names: names,
72 }
73 }
74
75 pub fn get_trait(&self, name: &str) -> Option<f64> {
77 self.trait_names
78 .iter()
79 .position(|n| n == name)
80 .map(|i| self.traits[i])
81 }
82
83 pub fn set_trait(&mut self, name: &str, value: f64) {
85 if let Some(i) = self.trait_names.iter().position(|n| n == name) {
86 self.traits[i] = value.clamp(0.0, 1.0);
87 }
88 }
89
90 pub fn to_llm_params(&self) -> LlmParams {
98 let exploration = self.get_trait("exploration").unwrap_or(0.5);
99 let precision = self.get_trait("precision").unwrap_or(0.5);
100 let creativity = self.get_trait("creativity").unwrap_or(0.5);
101 let skepticism = self.get_trait("skepticism").unwrap_or(0.5);
102 let verbosity = self.get_trait("verbosity").unwrap_or(0.5);
103
104 LlmParams {
105 temperature: 0.1 + exploration * 1.4,
107 top_p: 1.0 - (precision * 0.5),
109 presence_penalty: creativity,
111 frequency_penalty: skepticism * 0.5,
113 max_tokens_multiplier: 0.5 + verbosity * 1.5,
115 }
116 }
117}
118
119#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)]
121pub struct LlmParams {
122 pub temperature: f64,
124 pub top_p: f64,
126 pub presence_penalty: f64,
128 pub frequency_penalty: f64,
130 pub max_tokens_multiplier: f64,
132}
133
134impl LlmParams {
135 pub fn max_tokens(&self, base: u32) -> u32 {
137 ((base as f64) * self.max_tokens_multiplier) as u32
138 }
139
140 pub fn conservative() -> Self {
142 Self {
143 temperature: 0.3,
144 top_p: 0.9,
145 presence_penalty: 0.0,
146 frequency_penalty: 0.1,
147 max_tokens_multiplier: 1.0,
148 }
149 }
150
151 pub fn creative() -> Self {
153 Self {
154 temperature: 1.2,
155 top_p: 0.95,
156 presence_penalty: 0.5,
157 frequency_penalty: 0.0,
158 max_tokens_multiplier: 1.5,
159 }
160 }
161}
162
163pub trait GeneticOperator {
165 fn crossover(&self, parent_a: &Genome, parent_b: &Genome) -> Genome;
167
168 fn mutate(&self, genome: &mut Genome, mutation_rate: f64);
170}
171
172#[derive(Debug, Clone, Default)]
174pub struct StandardOperator;
175
176impl GeneticOperator for StandardOperator {
177 fn crossover(&self, parent_a: &Genome, parent_b: &Genome) -> Genome {
178 let mut rng = rand::thread_rng();
179
180 let crossover_point = rng.gen_range(0..parent_a.traits.len());
182 let mut child_traits = Vec::with_capacity(parent_a.traits.len());
183
184 for i in 0..parent_a.traits.len() {
185 if i < crossover_point {
186 child_traits.push(parent_a.traits[i]);
187 } else {
188 child_traits.push(parent_b.traits[i]);
189 }
190 }
191
192 let prompt = if rng.gen_bool(0.5) {
194 parent_a.prompt.clone()
195 } else {
196 parent_b.prompt.clone()
197 };
198
199 Genome {
200 prompt,
201 traits: child_traits,
202 trait_names: parent_a.trait_names.clone(),
203 }
204 }
205
206 fn mutate(&self, genome: &mut Genome, mutation_rate: f64) {
207 let mut rng = rand::thread_rng();
208
209 for trait_val in &mut genome.traits {
210 if rng.gen_bool(mutation_rate) {
211 let delta: f64 = rng.gen_range(-0.2..0.2);
213 *trait_val = (*trait_val + delta).clamp(0.0, 1.0);
214 }
215 }
216 }
217}
218
219pub fn tournament_select(population: &[(Genome, Fitness)], tournament_size: usize) -> &Genome {
221 let mut rng = rand::thread_rng();
222 let mut best: Option<&(Genome, Fitness)> = None;
223
224 for _ in 0..tournament_size {
225 let idx = rng.gen_range(0..population.len());
226 let candidate = &population[idx];
227
228 if best.is_none() || candidate.1 > best.unwrap().1 {
229 best = Some(candidate);
230 }
231 }
232
233 &best.unwrap().0
234}
235
236impl StandardOperator {
237 pub fn produce_next_generation(
239 &self,
240 population: &[(Genome, Fitness)],
241 size: usize,
242 mutation_rate: f64,
243 tournament_size: usize,
244 ) -> Vec<Genome> {
245 use rayon::prelude::*;
246
247 (0..size)
248 .into_par_iter()
249 .map(|_| {
250 let parent_a = tournament_select(population, tournament_size);
251 let parent_b = tournament_select(population, tournament_size);
252
253 let mut child = self.crossover(parent_a, parent_b);
254 self.mutate(&mut child, mutation_rate);
255 child
256 })
257 .collect()
258 }
259}
260
261#[cfg(test)]
262mod tests {
263 use super::*;
264
265 #[test]
266 fn test_genome_traits() {
267 let mut genome = Genome::new("Test agent");
268 assert_eq!(genome.get_trait("exploration"), Some(0.5));
269
270 genome.set_trait("exploration", 0.8);
271 assert_eq!(genome.get_trait("exploration"), Some(0.8));
272 }
273
274 #[test]
275 fn test_crossover() {
276 let parent_a =
277 Genome::with_traits("A", vec![("a".to_string(), 0.0), ("b".to_string(), 0.0)]);
278 let parent_b =
279 Genome::with_traits("B", vec![("a".to_string(), 1.0), ("b".to_string(), 1.0)]);
280
281 let operator = StandardOperator;
282 let child = operator.crossover(&parent_a, &parent_b);
283
284 assert!(child.traits.iter().all(|&t| t == 0.0 || t == 1.0));
286 }
287
288 #[test]
289 fn test_mutation() {
290 let mut genome = Genome::new("Test");
291 let original_traits = genome.traits.clone();
292
293 let operator = StandardOperator;
294 operator.mutate(&mut genome, 1.0); assert!(genome
298 .traits
299 .iter()
300 .zip(original_traits.iter())
301 .any(|(a, b)| a != b));
302 }
303}