openkind_engine/
engine.rs1use async_trait::async_trait;
4use openkind_core::{ModelInfo, SystemRequest, SystemResponse};
5
6use crate::error::EngineResult;
7
8#[async_trait]
12pub trait DecisionEngine: Send + Sync {
13 fn backend_id(&self) -> &str;
15
16 fn model_metadata(&self) -> ModelInfo {
20 ModelInfo {
21 name: self.backend_id().to_string(),
22 description: format!("{} backend", self.backend_id()),
23 release_date: "1970-01-01".to_string(),
24 }
25 }
26
27 async fn evaluate(&self, req: SystemRequest) -> EngineResult<SystemResponse>;
29
30 fn estimate_input_tokens(&self, req: &SystemRequest) -> u32 {
34 let state_chars = match &req.state {
35 openkind_core::State::Text(s) => s.len(),
36 openkind_core::State::Object(m) => count_json_bytes(m),
37 openkind_core::State::Array(a) => count_json_bytes(a),
38 };
39 let question_chars =
40 req.questions
41 .values()
42 .map(|q| match q {
43 openkind_core::Question::Noul(n) => count_json_bytes(&n.instructions)
44 .saturating_add(n.criteria.as_ref().map(count_json_bytes).unwrap_or(0)),
45 openkind_core::Question::Choice(c) => count_json_bytes(&c.instructions)
46 .saturating_add(count_json_bytes(&c.criteria)),
47 openkind_core::Question::Score(s) => count_json_bytes(&s.instructions)
48 .saturating_add(count_json_bytes(&s.criteria)),
49 })
50 .fold(0usize, usize::saturating_add);
51 let total_chars = state_chars.saturating_add(question_chars);
52 if total_chars == 0 {
53 0
54 } else {
55 u32::try_from(total_chars.div_ceil(4)).unwrap_or(u32::MAX)
56 }
57 }
58}
59
60struct ByteCounter(usize);
62
63impl std::io::Write for ByteCounter {
64 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
65 self.0 = self.0.saturating_add(buf.len());
66 Ok(buf.len())
67 }
68
69 fn flush(&mut self) -> std::io::Result<()> {
70 Ok(())
71 }
72}
73
74fn count_json_bytes<T: serde::Serialize + ?Sized>(val: &T) -> usize {
75 let mut counter = ByteCounter(0);
76 serde_json::to_writer(&mut counter, val)
77 .map(|()| counter.0)
78 .unwrap_or(0)
79}