Skip to main content

katago_analysis/engine/
response.rs

1use serde::Deserialize;
2use serde_json::{Map, Value};
3
4use crate::{Model, Player};
5
6/// A response from the analysis engine.
7#[derive(Debug, Clone, Deserialize)]
8#[serde(try_from = "Value")]
9pub enum Response {
10    /// The result of analyzing a position.
11    Analyze(AnalysisResponse),
12
13    /// Indicates that analysis was terminated before analyzing the specified position.
14    NoResults {
15        /// The request ID.
16        id: String,
17
18        /// The position index, where 0 is the position before the first move.
19        turn_number: usize,
20    },
21
22    /// KataGo's version information.
23    QueryVersion {
24        /// The request ID.
25        id: String,
26
27        /// A string indicating the most recent KataGo release version that this version is a descendant of,
28        /// such as `"1.6.1"`.
29        version: String,
30
31        /// The precise git hash this KataGo version was compiled from, or the string `"<omitted>"` if KataGo was
32        /// compiled separately from its repo or without Git support.
33        git_hash: String,
34    },
35
36    /// Indicates that the cache was cleared.
37    ClearCache {
38        /// The request ID.
39        id: String,
40    },
41
42    /// Acknowledgement of a terminate request. The engine will proceed to send [`NoResults`][Response::NoResults] or
43    /// partial [`Analyze`][Response::Analyze] responses for each position after they have been terminated.
44    Terminate {
45        /// The request ID.
46        id: String,
47
48        /// The ID of the request being terminated.
49        terminate_id: String,
50
51        /// The positions being terminated, if specified in the request.
52        #[serde(default)]
53        turn_numbers: Option<Vec<usize>>,
54    },
55
56    /// Acknowledgement of a request to terminate all analyses. The engine will proceed to send
57    /// [`NoResults`][Response::NoResults] or partial [`Analyze`][Response::Analyze] responses for each position
58    /// after they have been terminated.
59    TerminateAll {
60        /// The request ID.
61        id: String,
62
63        /// The positions being terminated, if specified in the request.
64        #[serde(default)]
65        turn_numbers: Option<Vec<usize>>,
66    },
67
68    /// Information about the currently loaded neural network models.
69    QueryModels {
70        /// The request ID.
71        id: String,
72
73        /// A list of available models.
74        models: Vec<Model>,
75    },
76
77    /// An error with no known associated request.
78    GeneralError {
79        /// The error message.
80        error: String,
81    },
82
83    /// An error in processing a request.
84    FieldError {
85        /// The request ID.
86        id: String,
87
88        /// The error message.
89        error: String,
90
91        /// The request field which is the source of the error.
92        field: String,
93    },
94
95    /// A warning in processing a request. The engine will still generate analysis responses for the request.
96    FieldWarning {
97        /// The request ID.
98        id: String,
99
100        /// The warning message.
101        warning: String,
102
103        /// The request field which is the source of the warning.
104        field: String,
105    },
106}
107
108impl TryFrom<Value> for Response {
109    type Error = String;
110
111    fn try_from(value: Value) -> Result<Self, Self::Error> {
112        fn try_parse_field_error(map: &Map<String, Value>) -> Option<Response> {
113            let error = map.get("error")?.as_str()?;
114            let field = map.get("field")?.as_str()?;
115            let id = map.get("id")?.as_str()?;
116            Some(Response::FieldError {
117                id: id.to_string(),
118                error: error.to_string(),
119                field: field.to_string(),
120            })
121        }
122
123        fn try_parse_general_error(map: &Map<String, Value>) -> Option<Response> {
124            let error = map.get("error")?.as_str()?;
125            Some(Response::GeneralError {
126                error: error.to_string(),
127            })
128        }
129
130        fn try_parse_field_warning(map: &Map<String, Value>) -> Option<Response> {
131            let warning = map.get("warning")?.as_str()?;
132            let field = map.get("field")?.as_str()?;
133            let id = map.get("id")?.as_str()?;
134            Some(Response::FieldWarning {
135                id: id.to_string(),
136                warning: warning.to_string(),
137                field: field.to_string(),
138            })
139        }
140
141        fn try_parse_query_version(map: &Map<String, Value>) -> Option<Response> {
142            let action = map.get("action")?.as_str()?;
143            if action != "query_version" {
144                return None;
145            }
146            let id = map.get("id")?.as_str()?;
147            let version = map.get("version")?.as_str()?;
148            let git_hash = map.get("git_hash")?.as_str()?;
149            Some(Response::QueryVersion {
150                id: id.to_string(),
151                version: version.to_string(),
152                git_hash: git_hash.to_string(),
153            })
154        }
155
156        fn try_parse_clear_cache(map: &Map<String, Value>) -> Option<Response> {
157            let action = map.get("action")?.as_str()?;
158            if action != "clear_cache" {
159                return None;
160            }
161            let id = map.get("id")?.as_str()?;
162            Some(Response::ClearCache { id: id.to_string() })
163        }
164
165        fn try_parse_no_results(map: &Map<String, Value>) -> Option<Response> {
166            map.get("noResults")?;
167            let id = map.get("id")?.as_str()?;
168            let turn_number = map.get("turnNumber")?.as_u64()? as usize;
169            Some(Response::NoResults {
170                id: id.to_string(),
171                turn_number,
172            })
173        }
174
175        fn try_parse_terminate(map: &Map<String, Value>) -> Option<Response> {
176            let action = map.get("action")?.as_str()?;
177            if action != "terminate" {
178                return None;
179            }
180            let id = map.get("id")?.as_str()?;
181            let terminate_id = map.get("terminateId")?.as_str()?;
182            let turn_numbers = map
183                .get("turnNumbers")
184                .and_then(|v| serde_json::from_value(v.clone()).ok());
185            Some(Response::Terminate {
186                id: id.to_string(),
187                terminate_id: terminate_id.to_string(),
188                turn_numbers,
189            })
190        }
191
192        fn try_parse_terminate_all(map: &Map<String, Value>) -> Option<Response> {
193            let action = map.get("action")?.as_str()?;
194            if action != "terminate_all" {
195                return None;
196            }
197            let id = map.get("id")?.as_str()?;
198            let turn_numbers = map
199                .get("turnNumbers")
200                .and_then(|v| serde_json::from_value(v.clone()).ok());
201            Some(Response::TerminateAll {
202                id: id.to_string(),
203                turn_numbers,
204            })
205        }
206
207        fn try_parse_query_models(map: &Map<String, Value>) -> Option<Response> {
208            let action = map.get("action")?.as_str()?;
209            if action != "query_models" {
210                return None;
211            }
212            let id = map.get("id")?.as_str()?;
213            let models = map.get("models")?;
214            Some(Response::QueryModels {
215                id: id.to_string(),
216                models: serde_json::from_value(models.clone()).ok()?,
217            })
218        }
219
220        fn try_parse_analysis(value: Value) -> Option<Response> {
221            serde_json::from_value(value).ok().map(Response::Analyze)
222        }
223
224        let map = value.as_object().ok_or("expected object")?;
225        try_parse_field_error(map)
226            .or_else(|| try_parse_general_error(map))
227            .or_else(|| try_parse_field_warning(map))
228            .or_else(|| try_parse_query_version(map))
229            .or_else(|| try_parse_clear_cache(map))
230            .or_else(|| try_parse_no_results(map))
231            .or_else(|| try_parse_terminate(map))
232            .or_else(|| try_parse_terminate_all(map))
233            .or_else(|| try_parse_query_models(map))
234            .or_else(|| try_parse_analysis(value))
235            .ok_or("unrecognized response format".to_string())
236    }
237}
238
239/// The result of analyzing a position.
240#[derive(Debug, Clone, Deserialize)]
241#[serde(rename_all = "camelCase")]
242pub struct AnalysisResponse {
243    /// The request ID.
244    pub id: String,
245
246    /// Whether this is a partial analysis result. `false` indicates no more responses will be sent.
247    pub is_during_search: bool,
248
249    /// The position index, where 0 is the position before the first move.
250    pub turn_number: usize,
251
252    /// The list of moves the engine considered.
253    pub move_infos: Vec<MoveInfo>,
254
255    /// Information about the root position.
256    pub root_info: RootInfo,
257
258    /// The ownership prediction, in row-major order.
259    pub ownership: Option<Vec<f64>>,
260
261    /// The standard deviation of the ownership prediction, in row-major order.
262    pub ownership_stdev: Option<Vec<f64>>,
263
264    /// The policy prediction, in row-major order with the pass move at the end.
265    pub policy: Option<Vec<f64>>,
266
267    /// The humanSL policy prediction, in row-major order with the pass move at the end.
268    pub human_policy: Option<Vec<f64>>,
269}
270
271/// The result of analyzing a candidate move.
272#[derive(Debug, Clone, Deserialize)]
273#[serde(rename_all = "camelCase")]
274pub struct MoveInfo {
275    /// The move location in GTP format (`"A1"`, `"pass"`, etc.). This corresponds to the `move` field in KataGo's
276    /// response.
277    #[serde(rename = "move")]
278    pub mv: String,
279
280    /// The number of visits invested in this move.
281    pub visits: u32,
282
283    /// The number of visits the root "wants" to invest in this move.
284    pub edge_visits: u32,
285
286    /// The winrate, in the range [0, 1].
287    pub winrate: f64,
288
289    /// The predicted number of points that the current side is leading by.
290    pub score_lead: f64,
291
292    /// The predicted standard deviation of the score lead.
293    pub score_stdev: f64,
294
295    /// The predicted score at the end of the game after selfplay.
296    pub score_selfplay: f64,
297
298    /// The policy prior of this move.
299    pub prior: f64,
300
301    /// The predicted probability that the game will have a void result.
302    pub no_result_value: Option<f64>,
303
304    /// The humanSL policy prior of this move.
305    pub human_prior: Option<f64>,
306
307    /// The utility of this move.
308    pub utility: f64,
309
310    /// The LCB of this move's winrate.
311    pub lcb: f64,
312
313    /// The LCB of this move's utility.
314    pub utility_lcb: f64,
315
316    /// The total weight of this move's visits.
317    pub weight: f64,
318
319    /// The total weight of the visits the root "wants" to invest in this move.
320    pub edge_weight: f64,
321
322    /// The relative ranking of this move, where 0 is best.
323    pub order: usize,
324
325    /// The value used to determine the move ranking.
326    pub play_selection_value: f64,
327
328    /// If present, indicates the move that was actually searched to get the evaluation of this move.
329    pub is_symmetry_of: Option<String>,
330
331    /// The principal variation for this move.
332    pub pv: Vec<String>,
333
334    /// The number of visits invested in each position in the principal variation.
335    pub pv_visits: Option<Vec<u32>>,
336
337    /// The number of visits invested in each move in the principal variation.
338    pub pv_edge_visits: Option<Vec<u32>>,
339
340    /// The ownership prediction, in row-major order.
341    pub ownership: Option<Vec<f64>>,
342
343    /// The standard deviation of the ownership prediction, in row-major order.
344    pub ownership_stdev: Option<Vec<f64>>,
345}
346
347/// The result of analyzing the root position.
348#[derive(Debug, Clone, Deserialize)]
349#[serde(rename_all = "camelCase")]
350pub struct RootInfo {
351    /// The winrate, in the range [0, 1].
352    pub winrate: f64,
353
354    /// The predicted number of points that the current side is leading by.
355    pub score_lead: f64,
356
357    /// The predicted score at the end of the game after selfplay.
358    pub score_selfplay: f64,
359
360    /// The utility.
361    pub utility: f64,
362
363    /// The number of visits received.
364    pub visits: u32,
365
366    /// The hash of this position.
367    pub this_hash: String,
368
369    /// The hash of this position that is invariant under board symmetries.
370    pub sym_hash: String,
371
372    /// The player to move.
373    pub current_player: Player,
374
375    /// The winrate prediction from the neural network.
376    pub raw_winrate: f64,
377
378    /// The score lead prediction from the neural network.
379    pub raw_lead: f64,
380
381    /// The selfplay score prediction from the neural network.
382    pub raw_score_selfplay: f64,
383
384    /// The selfplay score standard deviation prediction from the neural network.
385    pub raw_score_selfplay_stdev: f64,
386
387    /// The void result probability prediction from the neural network.
388    pub raw_no_result_prob: f64,
389
390    /// The short-term winrate uncertainty prediction from the neural network.
391    pub raw_st_wr_error: f64,
392
393    /// The short-term score uncertainty prediction from the neural network.
394    pub raw_st_score_error: f64,
395
396    /// A measure of how much meaningful game is left until the winner is known, predicted by the neural network.
397    pub raw_var_time_left: f64,
398
399    /// The winrate prediction from the humanSL neural network.
400    pub human_winrate: Option<f64>,
401
402    /// The score prediction from the humanSL neural network.
403    pub human_score_mean: Option<f64>,
404
405    /// The score standard deviation prediction from the humanSL neural network.
406    pub human_score_stdev: Option<f64>,
407
408    /// The short-term winrate uncertainty prediction from the humanSL neural network.
409    pub human_st_wr_error: Option<f64>,
410
411    /// The short-term score uncertainty prediction from the humanSL neural network.
412    pub human_st_score_error: Option<f64>,
413}