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}