@1kbirds/chidori 0.1.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,964 @@
1
+ use std::collections::VecDeque;
2
+ use pyo3::exceptions;
3
+ use pyo3::prelude::*;
4
+ use tonic::{Response, Status};
5
+ use futures::executor;
6
+ use futures::StreamExt;
7
+ use pyo3::types::{PyDict, PyList, PyString};
8
+ use pyo3::types::{PyBool, PyFloat, PyInt};
9
+ use pyo3::prelude::*;
10
+ use std::collections::HashMap;
11
+ use std::time::Duration;
12
+ use tokio::runtime::Runtime;
13
+ use log::{debug, info};
14
+ use pyo3::exceptions::PyTypeError;
15
+ use tonic::transport::Channel;
16
+ use ::prompt_graph_exec::tonic_runtime::run_server;
17
+ use ::prompt_graph_core::proto2::execution_runtime_client::ExecutionRuntimeClient;
18
+ use ::prompt_graph_core::proto2::{File, Empty, ExecutionStatus, RequestInputProposalResponse, RequestOnlyId, RequestFileMerge, RequestAtFrame, RequestNewBranch, RequestListBranches, ListBranchesRes, QueryAtFrame, Query, QueryAtFrameResponse, SerializedValue, ChangeValueWithCounter, NodeWillExecuteOnBranch, FileAddressedChangeValueWithCounter, FilteredPollNodeWillExecuteEventsRequest, RespondPollNodeWillExecuteEvents, RequestAckNodeWillExecuteEvent, ChangeValue, Path, SerializedValueObject, SerializedValueArray, Item};
19
+ use ::prompt_graph_core::proto2::prompt_graph_node_loader::LoadFrom;
20
+ use ::prompt_graph_core::proto2::serialized_value::Val;
21
+ use ::prompt_graph_core::graph_definition::{create_prompt_node, create_op_map, create_code_node, create_component_node, create_vector_memory_node, create_observation_node, create_node_parameter, SourceNodeType, create_loader_node, create_custom_node};
22
+ use ::prompt_graph_core::utils::wasm_error::CoreError;
23
+ use ::prompt_graph_core::build_runtime_graph::graph_parse::{CleanedDefinitionGraph, CleanIndividualNode, construct_query_from_output_type, derive_for_individual_node};
24
+
25
+ // https://pyo3.rs/v0.19.0/
26
+
27
+ // https://chat.openai.com/c/4dfe5b29-0cd9-493f-be8f-9362d376e764
28
+
29
+ // TODO: create worker object
30
+
31
+ #[derive(Debug)]
32
+ pub struct CoreErrorWrapper(CoreError);
33
+
34
+ impl std::convert::From<CoreErrorWrapper> for PyErr {
35
+ fn from(err: CoreErrorWrapper) -> PyErr {
36
+ exceptions::PyOSError::new_err(err.0.to_string())
37
+ }
38
+ }
39
+
40
+ impl std::convert::From<CoreError> for PyErrWrapper {
41
+ fn from(err: CoreError) -> PyErrWrapper {
42
+ PyErrWrapper(exceptions::PyOSError::new_err(err.to_string()))
43
+ }
44
+ }
45
+
46
+ #[derive(Debug)]
47
+ pub struct PyErrWrapper(pyo3::PyErr);
48
+
49
+ #[derive(Debug)]
50
+ pub struct AnyhowErrWrapper(anyhow::Error);
51
+
52
+
53
+ impl std::convert::From<tonic::transport::Error> for PyErrWrapper {
54
+ fn from(status: tonic::transport::Error) -> Self {
55
+ // Convert the `Status` to a `PyErr` here, then wrap it in `PyErrWrapper`.
56
+ PyErrWrapper(exceptions::PyOSError::new_err(status.to_string()))
57
+ }
58
+ }
59
+
60
+ impl std::convert::From<Status> for PyErrWrapper {
61
+ fn from(status: Status) -> Self {
62
+ // Convert the `Status` to a `PyErr` here, then wrap it in `PyErrWrapper`.
63
+ PyErrWrapper(exceptions::PyOSError::new_err(status.message().to_string()))
64
+ }
65
+ }
66
+
67
+
68
+ impl std::convert::From<PyErrWrapper> for PyErr {
69
+ fn from(err: PyErrWrapper) -> PyErr {
70
+ err.0
71
+ }
72
+ }
73
+
74
+ impl std::convert::From<AnyhowErrWrapper> for PyErr {
75
+ fn from(err: AnyhowErrWrapper) -> PyErr {
76
+ exceptions::PyOSError::new_err(err.0.to_string())
77
+ }
78
+ }
79
+
80
+ impl Into<pyo3::PyResult<()>> for PyErrWrapper {
81
+ fn into(self) -> pyo3::PyResult<()> {
82
+ Err(self.0)
83
+ }
84
+ }
85
+
86
+ pub struct PyExecutionStatus(Response<ExecutionStatus>);
87
+
88
+
89
+ impl IntoPy<Py<PyAny>> for PyExecutionStatus {
90
+ fn into_py(self, py: Python) -> Py<PyAny> {
91
+ let PyExecutionStatus(resp) = self;
92
+ let exec_status = resp.into_inner();
93
+ let dict = PyDict::new(py);
94
+ dict.set_item("id", exec_status.id).unwrap();
95
+ dict.set_item("monotonic_counter", exec_status.monotonic_counter).unwrap();
96
+ dict.set_item("branch", exec_status.branch).unwrap();
97
+ dict.into_py(py)
98
+ }
99
+ }
100
+
101
+
102
+ pub struct PyListBranchesRes(Response<ListBranchesRes>);
103
+
104
+
105
+ impl IntoPy<Py<PyAny>> for PyListBranchesRes {
106
+ fn into_py(self, py: Python) -> Py<PyAny> {
107
+ let PyListBranchesRes(resp) = self;
108
+ let branches = resp.into_inner();
109
+ let branch_list = branches.branches.into_iter().map(|branch| {
110
+ let mut dict = PyDict::new(py);
111
+ dict.set_item("id", branch.id).unwrap();
112
+ dict.set_item("diverges_at_counter", branch.diverges_at_counter).unwrap();
113
+ dict.set_item("source_branch_ids", format!("{:?}", branch.source_branch_ids)).unwrap();
114
+ dict
115
+ }).collect::<Vec<_>>();
116
+ PyList::new(py, branch_list).into_py(py)
117
+ }
118
+ }
119
+
120
+
121
+ pub struct PyQueryAtFrameResponse(Response<QueryAtFrameResponse>);
122
+
123
+ impl IntoPy<Py<PyAny>> for PyQueryAtFrameResponse {
124
+ fn into_py(self, py: Python) -> Py<PyAny> {
125
+ let PyQueryAtFrameResponse(resp) = self;
126
+ let res = resp.into_inner();
127
+
128
+ let mut dict = PyDict::new(py);
129
+ res.values.into_iter().for_each(|value| {
130
+ let c = value.change_value.unwrap();
131
+ let k = c.path.unwrap().address.join(":");
132
+ let v = c.value.unwrap();
133
+ // TODO: SerializeValue to python
134
+ dict.set_item(k, SerializedValueWrapper(v)).unwrap();
135
+ });
136
+ PyList::new(py, dict).into_py(py)
137
+ }
138
+ }
139
+
140
+ #[derive(Debug)]
141
+ pub struct SerializedValueWrapper(SerializedValue);
142
+
143
+ impl ToPyObject for SerializedValueWrapper {
144
+ fn to_object(&self, py: Python<'_>) -> PyObject {
145
+ if let None = self.0.val {
146
+ let x: Option<bool> = None;
147
+ return x.into_py(py);
148
+ }
149
+ match self.0.val.as_ref().unwrap() {
150
+ Val::Float(x) => { x.into_py(py) }
151
+ Val::Number(x) => { x.into_py(py) }
152
+ Val::String(x) => { x.into_py(py) }
153
+ Val::Boolean(x) => { x.into_py(py) }
154
+ Val::Array(val) => {
155
+ let py_list = PyList::empty(py);
156
+ for item in &val.values {
157
+ py_list.append(SerializedValueWrapper(item.clone()).to_object(py)).unwrap();
158
+ }
159
+ py_list.into_py(py)
160
+ }
161
+ Val::Object(val) => {
162
+ let py_dict = PyDict::new(py);
163
+ for (key, value) in &val.values {
164
+ py_dict.set_item(key, SerializedValueWrapper(value.clone()).to_object(py)).unwrap();
165
+ }
166
+ py_dict.into_py(py)
167
+ }
168
+ }
169
+ }
170
+ }
171
+
172
+ #[derive(Debug)]
173
+ pub struct ChangeValueWithCounterWrapper(ChangeValueWithCounter);
174
+
175
+ impl IntoPy<Py<PyAny>> for ChangeValueWithCounterWrapper {
176
+ fn into_py(self, py: Python) -> Py<PyAny> {
177
+ let mut dict = PyDict::new(py);
178
+ self.0.filled_values.into_iter().for_each(|c| {
179
+ let k = c.path.map(|path| path.address.join(":"));
180
+ let v = c.value;
181
+ if let (Some(k), Some(v)) = (k, v) {
182
+ dict.set_item(k, SerializedValueWrapper(v)).unwrap();
183
+ }
184
+ });
185
+ PyList::new(py, dict).into_py(py)
186
+ }
187
+ }
188
+
189
+ fn add_to_dict<'p>(
190
+ py: Python<'p>,
191
+ keys: &Vec<String>,
192
+ value: PyObject,
193
+ d: &'p PyDict,
194
+ ) -> PyResult<()> {
195
+ if keys.len() == 1 {
196
+ d.set_item(keys[0].clone(), value)?;
197
+ } else {
198
+ let key = &keys[0];
199
+ if d.contains(key)? {
200
+ let nested_dict = d.get_item(key).unwrap().downcast::<PyDict>()?;
201
+ add_to_dict(py, &keys[1..].to_vec(), value, &nested_dict)?;
202
+ } else {
203
+ let nested_dict = PyDict::new(py);
204
+ d.set_item(key, &nested_dict)?;
205
+ add_to_dict(py, &keys[1..].to_vec(), value, &nested_dict)?;
206
+ }
207
+ }
208
+ Ok(())
209
+ }
210
+
211
+
212
+ fn pyany_to_serialized_value(p: &PyAny) -> SerializedValue {
213
+ match p.get_type().name() {
214
+ Ok(s) => {
215
+ match s {
216
+ "int" => {
217
+ let val = p.extract::<i32>().unwrap();
218
+ SerializedValue {
219
+ val: Some(Val::Number(val)),
220
+ }
221
+ }
222
+ "float" => {
223
+ let val = p.extract::<f32>().unwrap();
224
+ SerializedValue {
225
+ val: Some(Val::Float(val)),
226
+ }
227
+ }
228
+ "str" => {
229
+ let val = p.extract::<String>().unwrap();
230
+ SerializedValue {
231
+ val: Some(Val::String(val)),
232
+ }
233
+ }
234
+ "bool" => {
235
+ let val = p.extract::<bool>().unwrap();
236
+ SerializedValue {
237
+ val: Some(Val::Boolean(val)),
238
+ }
239
+ }
240
+ "list" => {
241
+ let list = p.downcast::<PyList>().unwrap();
242
+ let arr = list
243
+ .iter()
244
+ .map(|item| pyany_to_serialized_value(item))
245
+ .collect();
246
+ SerializedValue {
247
+ val: Some(Val::Array(SerializedValueArray { values: arr } )),
248
+ }
249
+ }
250
+ "dict" => {
251
+ let dict = p.downcast::<PyDict>().unwrap();
252
+ let mut map = HashMap::new();
253
+ for (key, value) in dict {
254
+ let key_string = key.extract::<String>().unwrap();
255
+ map.insert(key_string, pyany_to_serialized_value(value));
256
+ }
257
+ SerializedValue {
258
+ val: Some(Val::Object(SerializedValueObject { values: map })),
259
+ }
260
+ }
261
+ _ => SerializedValue::default(),
262
+ }
263
+ }
264
+ Err(_) => SerializedValue::default(),
265
+ }
266
+ }
267
+
268
+
269
+
270
+ fn dict_to_paths<'p>(
271
+ py: Python<'p>,
272
+ d: &'p PyDict
273
+ ) -> PyResult<Vec<(Vec<String>, SerializedValue)>> {
274
+ let mut paths = Vec::new();
275
+ let mut queue: VecDeque<(Vec<String>, &'p PyDict)> = VecDeque::new();
276
+ queue.push_back((Vec::new(), d));
277
+
278
+ while let Some((mut path, dict)) = queue.pop_front() {
279
+ for (key, val) in dict {
280
+ let key_str = key.extract::<String>()?;
281
+ path.push(key_str.clone());
282
+ match val.downcast::<PyDict>() {
283
+ Ok(sub_dict) => {
284
+ queue.push_back((path.clone(), sub_dict));
285
+ },
286
+ Err(_) => {
287
+ paths.push((path.clone(), pyany_to_serialized_value(val)));
288
+ }
289
+ }
290
+ path.pop();
291
+ }
292
+ }
293
+
294
+ Ok(paths)
295
+ }
296
+
297
+
298
+ #[derive(Debug)]
299
+ pub struct NodeWillExecuteOnBranchWrapper(NodeWillExecuteOnBranch);
300
+
301
+ impl ToPyObject for NodeWillExecuteOnBranchWrapper {
302
+ fn to_object(&self, py: Python<'_>) -> PyObject {
303
+ let NodeWillExecuteOnBranch { branch, counter, node, custom_node_type_name} = &self.0;
304
+ let dict = PyDict::new(py);
305
+ dict.set_item("branch", branch).unwrap();
306
+ dict.set_item("counter", counter).unwrap();
307
+ if let Some(node) = node {
308
+ dict.set_item("node_name", &node.source_node).unwrap();
309
+ dict.set_item("type_name", &custom_node_type_name).unwrap();
310
+
311
+ let event_dict = PyDict::new(py);
312
+ // TODO: this needs to be fixed
313
+ for change in &node.change_values_used_in_execution {
314
+ if let Some(v) = &change.change_value {
315
+ match v {
316
+ ChangeValue { path: Some(path), value: Some(value), .. } => {
317
+ add_to_dict(
318
+ py,
319
+ &path.address,
320
+ SerializedValueWrapper(value.clone()).to_object(py),
321
+ &event_dict,
322
+ ).unwrap();
323
+ },
324
+ _ => {}
325
+ }
326
+ }
327
+ }
328
+
329
+ dict.set_item("event", event_dict).unwrap();
330
+ }
331
+ dict.into_py(py)
332
+ }
333
+ }
334
+
335
+ pub struct PyRespondPollNodeWillExecuteEvents(Response<RespondPollNodeWillExecuteEvents>);
336
+
337
+ impl IntoPy<Py<PyAny>> for PyRespondPollNodeWillExecuteEvents {
338
+ fn into_py(self, py: Python) -> Py<PyAny> {
339
+ let PyRespondPollNodeWillExecuteEvents(resp) = self;
340
+ let res = resp.into_inner();
341
+ let RespondPollNodeWillExecuteEvents { node_will_execute_events } = res;
342
+ let x: Vec<NodeWillExecuteOnBranchWrapper> = node_will_execute_events.iter().cloned().map(NodeWillExecuteOnBranchWrapper).collect();
343
+ PyList::new(py, x).into_py(py)
344
+ }
345
+ }
346
+
347
+
348
+ async fn get_client(url: String) -> Result<ExecutionRuntimeClient<tonic::transport::Channel>, PyErrWrapper> {
349
+ ExecutionRuntimeClient::connect(url.clone()).await.map_err(PyErrWrapper::from)
350
+ }
351
+
352
+ // TODO: return a handle to nodes so that we can understand and inspect them
353
+ // TODO: include a __repr__ method on those nodes
354
+
355
+
356
+ // TODO: add an api for wiring together a system ot nodes
357
+ // TODO: require the description of the complete system, in order to avoid mutably handling changing edges
358
+ // TODO: system description must be fully encapsulated in a single object and applied all at once
359
+
360
+ #[pyclass]
361
+ #[derive(Clone)]
362
+ struct NodeHandle {
363
+ url: String,
364
+ file_id: String,
365
+ node: Item,
366
+ exec_status: ExecutionStatus,
367
+ indiv: CleanIndividualNode
368
+ }
369
+
370
+ impl NodeHandle {
371
+ fn from(url: String, file_id: String, node: Item, exec_status: ExecutionStatus) -> anyhow::Result<NodeHandle> {
372
+ let indiv = derive_for_individual_node(&node)?;
373
+ Ok(NodeHandle {
374
+ url,
375
+ file_id,
376
+ node,
377
+ exec_status,
378
+ indiv
379
+ })
380
+ }
381
+ }
382
+
383
+ #[pymethods]
384
+ impl NodeHandle {
385
+ fn get_name(&self) -> String {
386
+ self.node.core.as_ref().unwrap().name.clone()
387
+ }
388
+
389
+ /// This updates the definition of this node to query for the target NodeHandle's output. Moving forward
390
+ /// it will execute whenever the target node resolves.
391
+ #[pyo3(signature = (node_handle=None))]
392
+ fn run_when<'a>(mut self_: PyRefMut<'_, Self>, py: Python<'a>, node_handle: Option<NodeHandle>) -> PyResult<&'a PyAny> {
393
+ let file_id = self_.file_id.clone();
394
+ let url = self_.url.clone();
395
+ let mut node = self_.node.clone();
396
+ pyo3_asyncio::tokio::future_into_py(py, async move {
397
+ // TODO: scan the queries and don't insert if the query already exists
398
+ if let Some(node_handle) = node_handle {
399
+ let queries = &mut node.core.as_mut().unwrap().queries;
400
+ let q = construct_query_from_output_type(
401
+ &node_handle.get_name(),
402
+ &node_handle.get_name(),
403
+ &node_handle.indiv.output_path
404
+ ).map_err(AnyhowErrWrapper)?;
405
+ queries.push(Query { query: Some(q)});
406
+ Ok(push_file_merge(&url, &file_id, node).await?)
407
+ } else {
408
+ Err(PyErr::new::<PyTypeError, _>("node_handle must be a NodeHandle"))
409
+ }
410
+ })
411
+ }
412
+
413
+ #[pyo3(signature = (branch=0, frame=0))]
414
+ fn query<'a>(mut self_: PyRefMut<'_, Self>, py: Python<'a>, branch: u64, frame: u64) -> PyResult<&'a PyAny> {
415
+ let file_id = self_.file_id.clone();
416
+ let url = self_.url.clone();
417
+ let name = self_.get_name();
418
+
419
+ let query = construct_query_from_output_type(&name, &name, &self_.indiv.output_path)
420
+ .map_err(AnyhowErrWrapper)?;
421
+
422
+ // TODO: we need this to watch for changes to the query instead
423
+ // TODO: or this should await until either the target counter has elapsed or the query has resulted
424
+
425
+ pyo3_asyncio::tokio::future_into_py(py, async move {
426
+ let mut client = get_client(url).await?;
427
+ Ok(PyQueryAtFrameResponse(client.run_query(QueryAtFrame {
428
+ id: file_id,
429
+ query: Some(Query {
430
+ query: Some(query)
431
+ }),
432
+ frame,
433
+ branch,
434
+ }).await.map_err(PyErrWrapper::from)?))
435
+ })
436
+ }
437
+
438
+ fn __str__(&self) -> PyResult<String> {
439
+ // TODO: best practice is that these could be used to re-construct the same object
440
+ let name = self.get_name();
441
+ Ok(format!("NodeHandle(file_id={}, node={})", self.file_id, name))
442
+ }
443
+
444
+ fn __repr__(&self) -> PyResult<String> {
445
+ let name = self.get_name();
446
+ Ok(format!("NodeHandle(file_id={}, node={})", self.file_id, name))
447
+ }
448
+ }
449
+
450
+
451
+ // TODO: all operations only apply to a specific branch at a time
452
+ // TODO: maintain an internal map of the generated change responses for node additions to the associated query necessary to get that result
453
+ #[pyclass]
454
+ struct Chidori {
455
+ file_id: String,
456
+ current_head: u64,
457
+ current_branch: u64,
458
+ url: String
459
+ }
460
+
461
+
462
+
463
+ async fn push_file_merge(url: &String, file_id: &String, node: Item) -> Result<NodeHandle, PyErr> {
464
+ let mut client = get_client(url.clone()).await?;
465
+ let exec_status = client.merge(RequestFileMerge {
466
+ id: file_id.clone(),
467
+ file: Some(File {
468
+ nodes: vec![node.clone()],
469
+ ..Default::default()
470
+ }),
471
+ branch: 0,
472
+ }).await.map_err(PyErrWrapper::from)?.into_inner();
473
+ Ok(NodeHandle::from(
474
+ url.clone(),
475
+ file_id.clone(),
476
+ node,
477
+ exec_status
478
+ ).map_err(AnyhowErrWrapper)?)
479
+ }
480
+
481
+ // TODO: internally all operations should have an assigned counter
482
+ // we can keep the actual target counter hidden from the host sdk
483
+ #[pymethods]
484
+ impl Chidori {
485
+
486
+ #[new]
487
+ #[pyo3(signature = (file_id=String::from("0"), url=String::from("http://127.0.0.1:9800"), api_token=None))]
488
+ fn new(file_id: String, url: String, api_token: Option<String>) -> Self {
489
+ debug!("Creating new Chidori instance with file_id={}, url={}, api_token={:?}", file_id, url, api_token);
490
+ Chidori {
491
+ file_id,
492
+ current_head: 0,
493
+ current_branch: 0,
494
+ url,
495
+ }
496
+ }
497
+
498
+ fn start_server<'a>(mut self_: PyRefMut<'_, Self>, py: Python<'a>, file_path: Option<String>) -> PyResult<&'a PyAny> {
499
+ let url_server = self_.url.clone();
500
+ std::thread::spawn(move || {
501
+ let result = run_server(url_server, file_path);
502
+ match result {
503
+ Ok(_) => { },
504
+ Err(e) => {
505
+ println!("Error running server: {}", e);
506
+ },
507
+ }
508
+ });
509
+
510
+ let url = self_.url.clone();
511
+ pyo3_asyncio::tokio::future_into_py(py, async move {
512
+ 'retry: loop {
513
+ let client = get_client(url.clone());
514
+ match client.await {
515
+ Ok(connection) => {
516
+ eprintln!("Connection successfully established {:?}", &url);
517
+ break 'retry
518
+ },
519
+ Err(e) => {
520
+ eprintln!("Error connecting to server: {} with Error {}. Retrying...", &url, &e.0);
521
+ std::thread::sleep(std::time::Duration::from_millis(1000));
522
+ }
523
+ }
524
+ }
525
+ Ok(())
526
+ })
527
+ }
528
+
529
+ #[pyo3(signature = (branch=0, frame=0))]
530
+ fn play<'a>(mut self_: PyRefMut<'_, Self>, py: Python<'a>, branch: u64, frame: u64) -> PyResult<&'a PyAny> {
531
+ let file_id = self_.file_id.clone();
532
+ let url = self_.url.clone();
533
+ pyo3_asyncio::tokio::future_into_py(py, async move {
534
+ let mut client = get_client(url).await?;
535
+ Ok(PyExecutionStatus(client.play(RequestAtFrame {
536
+ id: file_id,
537
+ frame,
538
+ branch,
539
+ }).await.map_err(PyErrWrapper::from)?))
540
+ })
541
+ }
542
+
543
+
544
+ #[pyo3(signature = (frame=0))]
545
+ fn pause<'a>(mut self_: PyRefMut<'_, Self>, py: Python<'a>, frame: u64) -> PyResult<&'a PyAny> {
546
+ let file_id = self_.file_id.clone();
547
+ let url = self_.url.clone();
548
+ let branch = self_.current_branch.clone();
549
+ pyo3_asyncio::tokio::future_into_py(py, async move {
550
+ let mut client = get_client(url).await?;
551
+ Ok(PyExecutionStatus(client.pause(RequestAtFrame {
552
+ id: file_id,
553
+ frame,
554
+ branch,
555
+ }).await.map_err(PyErrWrapper::from)?))
556
+ })
557
+ }
558
+
559
+
560
+ fn branch<'a>(mut self_: PyRefMut<'_, Self>, py: Python<'a>) -> PyResult<&'a PyAny> {
561
+ let file_id = self_.file_id.clone();
562
+ let url = self_.url.clone();
563
+ let branch = self_.current_branch.clone();
564
+ pyo3_asyncio::tokio::future_into_py(py, async move {
565
+ let mut client = get_client(url).await?;
566
+ let result_branch = client.branch(RequestNewBranch {
567
+ id: file_id,
568
+ source_branch_id: branch,
569
+ diverges_at_counter: 0,
570
+ }).await.map_err(PyErrWrapper::from)?;
571
+ // TODO: need to somehow handle writing to the current_branch
572
+ Ok(PyExecutionStatus(result_branch))
573
+ })
574
+ }
575
+
576
+ #[pyo3(signature = (query=String::new(), branch=0, frame=0))]
577
+ fn query<'a>(mut self_: PyRefMut<'_, Self>, py: Python<'a>, query: String, branch: u64, frame: u64) -> PyResult<&'a PyAny> {
578
+ let file_id = self_.file_id.clone();
579
+ let url = self_.url.clone();
580
+ pyo3_asyncio::tokio::future_into_py(py, async move {
581
+ let mut client = get_client(url).await?;
582
+ Ok(PyQueryAtFrameResponse(client.run_query(QueryAtFrame {
583
+ id: file_id,
584
+ query: Some(Query {
585
+ query: Some(query)
586
+ }),
587
+ frame,
588
+ branch,
589
+ }).await.map_err(PyErrWrapper::from)?))
590
+ })
591
+ }
592
+
593
+ fn list_branches<'a>(mut self_: PyRefMut<'_, Self>, py: Python<'a>) -> PyResult<&'a PyAny> {
594
+ let file_id = self_.file_id.clone();
595
+ let url = self_.url.clone();
596
+ pyo3_asyncio::tokio::future_into_py(py, async move {
597
+ let mut client = get_client(url).await?;
598
+ Ok(PyListBranchesRes(client.list_branches(RequestListBranches {
599
+ id: file_id,
600
+ }).await.map_err(PyErrWrapper::from)?))
601
+ })
602
+ }
603
+
604
+ fn display_graph_structure<'a>(
605
+ mut self_: PyRefMut<'_, Self>,
606
+ py: Python<'a>
607
+ ) -> PyResult<&'a PyAny> {
608
+ let file_id = self_.file_id.clone();
609
+ let url = self_.url.clone();
610
+ let branch = self_.current_branch.clone();
611
+ pyo3_asyncio::tokio::future_into_py(py, async move {
612
+ let mut client = get_client(url).await?;
613
+ let file = client.current_file_state(RequestOnlyId {
614
+ id: file_id,
615
+ branch
616
+ }).await.map_err(PyErrWrapper::from)?;
617
+ let mut file = file.into_inner();
618
+ let mut g = CleanedDefinitionGraph::zero();
619
+ g.merge_file(&mut file).unwrap();
620
+ Ok(g.get_dot_graph())
621
+ })
622
+ }
623
+
624
+ // TODO: some of these register handlers instead
625
+ // TODO: list registered graphs should not stream
626
+ // TODO: add a message that sends the current graph state
627
+
628
+ fn list_registered_graphs<'a>(
629
+ mut self_: PyRefMut<'_, Self>,
630
+ py: Python<'a>
631
+ ) -> PyResult<&'a PyAny> {
632
+ let file_id = self_.file_id.clone();
633
+ let url = self_.url.clone();
634
+ pyo3_asyncio::tokio::future_into_py(py, async move {
635
+ let mut client = get_client(url).await?;
636
+ let resp = client.list_registered_graphs(Empty {
637
+ }).await.map_err(PyErrWrapper::from)?;
638
+ let mut stream = resp.into_inner();
639
+ while let Some(x) = stream.next().await {
640
+ // callback.call(py, (x,), None);
641
+ info!("Registered Graph = {:?}", x);
642
+ };
643
+ Ok(())
644
+ })
645
+ }
646
+
647
+ fn list_input_proposals<'a>(
648
+ mut self_: PyRefMut<'_, Self>,
649
+ py: Python<'a>,
650
+ callback: PyObject
651
+ ) -> PyResult<&'a PyAny> {
652
+ let file_id = self_.file_id.clone();
653
+ let url = self_.url.clone();
654
+ let branch = self_.current_branch;
655
+ pyo3_asyncio::tokio::future_into_py(py, async move {
656
+ let mut client = get_client(url).await?;
657
+ let resp = client.list_input_proposals(RequestOnlyId {
658
+ id: file_id,
659
+ branch,
660
+ }).await.map_err(PyErrWrapper::from)?;
661
+ let mut stream = resp.into_inner();
662
+ while let Some(x) = stream.next().await {
663
+ // callback.call(py, (x,), None);
664
+ info!("InputProposals = {:?}", x);
665
+ };
666
+ Ok(())
667
+ })
668
+ }
669
+
670
+ // fn respond_to_input_proposal(mut self_: PyRefMut<'_, Self>) -> PyResult<()> {
671
+ // Ok(())
672
+ // }
673
+
674
+ // TODO: this is a successful callback example
675
+ fn list_change_events<'a>(
676
+ mut self_: PyRefMut<'_, Self>,
677
+ py: Python<'a>,
678
+ callback: PyObject
679
+ ) -> PyResult<&'a PyAny> {
680
+ let file_id = self_.file_id.clone();
681
+ let url = self_.url.clone();
682
+ let branch = self_.current_branch;
683
+ pyo3_asyncio::tokio::future_into_py(py, async move {
684
+ let mut client = get_client(url).await?;
685
+ let resp = client.list_change_events(RequestOnlyId {
686
+ id: file_id,
687
+ branch,
688
+ }).await.map_err(PyErrWrapper::from)?;
689
+ let mut stream = resp.into_inner();
690
+ while let Some(x) = stream.next().await {
691
+ Python::with_gil(|py| pyo3_asyncio::tokio::into_future(callback.as_ref(py).call((x.map(ChangeValueWithCounterWrapper).map_err(PyErrWrapper::from)?,), None)?))?
692
+ .await?;
693
+ };
694
+ Ok(())
695
+ })
696
+ }
697
+
698
+
699
+ // TODO: nodes that are added should return a clean definition of what their addition looks like
700
+ // TODO: adding a node should also display any errors
701
+ /// x = None
702
+ /// with open("/Users/coltonpierson/Downloads/files_and_dirs.zip", "rb") as zip_file:
703
+ /// contents = zip_file.read()
704
+ /// x = await p.load_zip_file("LoadZip", """ output: String """, contents)
705
+ /// x
706
+ #[pyo3(signature = (name=String::new(), output_tables=vec![], output=String::new(), bytes=vec![]))]
707
+ fn load_zip_file<'a>(
708
+ mut self_: PyRefMut<'_, Self>,
709
+ py: Python<'a>,
710
+ name: String,
711
+ output_tables: Vec<String>,
712
+ output: String,
713
+ bytes: Vec<u8>
714
+ ) -> PyResult<&'a PyAny> {
715
+ let file_id = self_.file_id.clone();
716
+ let url = self_.url.clone();
717
+ pyo3_asyncio::tokio::future_into_py(py, async move {
718
+ let node = create_loader_node(
719
+ name,
720
+ vec![],
721
+ output,
722
+ LoadFrom::ZipfileBytes(bytes),
723
+ output_tables
724
+ );
725
+ Ok(push_file_merge(&url, &file_id, node).await?)
726
+ })
727
+ }
728
+
729
+ // TODO: nodes that are added should return a clean definition of what their addition looks like
730
+ // TODO: adding a node should also display any errors
731
+ #[pyo3(signature = (name=String::new(), queries=vec![None], output_tables=vec![], template=String::new(), model=String::from("GPT_3_5_TURBO")))]
732
+ fn prompt_node<'a>(
733
+ mut self_: PyRefMut<'_, Self>,
734
+ py: Python<'a>,
735
+ name: String,
736
+ queries: Vec<Option<String>>,
737
+ output_tables: Vec<String>,
738
+ template: String,
739
+ model: String
740
+ ) -> PyResult<&'a PyAny> {
741
+ let file_id = self_.file_id.clone();
742
+ let url = self_.url.clone();
743
+ pyo3_asyncio::tokio::future_into_py(py, async move {
744
+ let node = create_prompt_node(
745
+ name,
746
+ queries,
747
+ template,
748
+ model,
749
+ output_tables
750
+ ).map_err(PyErrWrapper::from)?;
751
+ Ok(push_file_merge(&url, &file_id, node).await?)
752
+ })
753
+ }
754
+
755
+ fn poll_local_code_node_execution<'a>(
756
+ mut self_: PyRefMut<'_, Self>,
757
+ py: Python<'a>,
758
+ ) -> PyResult<&'a PyAny> {
759
+ let file_id = self_.file_id.clone();
760
+ let url = self_.url.clone();
761
+
762
+ pyo3_asyncio::tokio::future_into_py(py, async move {
763
+ let mut client = get_client(url).await?;
764
+ let result = client.poll_node_will_execute_events(FilteredPollNodeWillExecuteEventsRequest {
765
+ id: file_id.clone(),
766
+ }).await.map_err(PyErrWrapper::from)?;
767
+ debug!("poll_local_code_node_execution result = {:?}", result);
768
+ Ok(PyRespondPollNodeWillExecuteEvents(result))
769
+ })
770
+ }
771
+
772
+ #[pyo3(signature = (branch=0, counter=0))]
773
+ fn ack_local_code_node_execution<'a>(
774
+ mut self_: PyRefMut<'_, Self>,
775
+ py: Python<'a>,
776
+ branch: u64,
777
+ counter: u64,
778
+ ) -> PyResult<&'a PyAny> {
779
+ let file_id = self_.file_id.clone();
780
+ let url = self_.url.clone();
781
+ pyo3_asyncio::tokio::future_into_py(py, async move {
782
+ let mut client = get_client(url).await?;
783
+ Ok(PyExecutionStatus(client.ack_node_will_execute_event(RequestAckNodeWillExecuteEvent {
784
+ id: file_id.clone(),
785
+ branch,
786
+ counter,
787
+ }).await.map_err(PyErrWrapper::from)?))
788
+ })
789
+ }
790
+
791
+ #[pyo3(signature = (branch=0, counter=0, node_name=String::new(), response=None))]
792
+ fn respond_local_code_node_execution<'a>(
793
+ mut self_: PyRefMut<'_, Self>,
794
+ py: Python<'a>,
795
+ branch: u64,
796
+ counter: u64,
797
+ node_name: String,
798
+ response: Option<PyObject>
799
+ ) -> PyResult<&'a PyAny> {
800
+
801
+ let file_id = self_.file_id.clone();
802
+ let url = self_.url.clone();
803
+
804
+ // TODO: need parent counters from the original change
805
+ // TODO: need source node
806
+
807
+ let response_paths = if let Some(response) = response {
808
+ dict_to_paths(py, response.downcast::<PyDict>(py)?)?
809
+ } else {
810
+ vec![]
811
+ };
812
+
813
+ pyo3_asyncio::tokio::future_into_py(py, async move {
814
+ let mut client = get_client(url).await?;
815
+
816
+ // TODO: need to add the output table paths to these
817
+ let filled_values = response_paths.into_iter().map(|path| {
818
+ ChangeValue {
819
+ path: Some(Path {
820
+ address: path.0,
821
+ }),
822
+ value: Some(path.1),
823
+ branch,
824
+ }
825
+ });
826
+
827
+ // TODO: this needs to look more like a real change
828
+ Ok(PyExecutionStatus(client.push_worker_event(FileAddressedChangeValueWithCounter {
829
+ branch,
830
+ counter,
831
+ node_name,
832
+ id: file_id.clone(),
833
+ change: Some(ChangeValueWithCounter {
834
+ filled_values: filled_values.collect(),
835
+ parent_monotonic_counters: vec![],
836
+ monotonic_counter: counter,
837
+ branch,
838
+ source_node: "".to_string(),
839
+ })
840
+ }).await.map_err(PyErrWrapper::from)?))
841
+ })
842
+ }
843
+
844
+ // TODO: handle dispatch to this handler - should accept a callback
845
+ // https://github.com/PyO3/pyo3/issues/525
846
+ #[pyo3(signature = (name=String::new(), queries=vec![None], output_tables=vec![], output=String::from("type O {}"), node_type_name=String::new()))]
847
+ fn custom_node<'a>(
848
+ mut self_: PyRefMut<'_, Self>,
849
+ py: Python<'a>,
850
+ name: String,
851
+ queries: Vec<Option<String>>,
852
+ output_tables: Vec<String>,
853
+ output: String,
854
+ node_type_name: String,
855
+ ) -> PyResult<&'a PyAny> {
856
+ let file_id = self_.file_id.clone();
857
+ let url = self_.url.clone();
858
+ let branch = self_.current_branch;
859
+ pyo3_asyncio::tokio::future_into_py(py, async move {
860
+ // Register the node with the system
861
+ let node = create_custom_node(
862
+ name,
863
+ queries,
864
+ output,
865
+ node_type_name,
866
+ output_tables
867
+ );
868
+ Ok(push_file_merge(&url, &file_id, node).await?)
869
+ })
870
+ }
871
+
872
+ #[pyo3(signature = (name=String::new(), queries=vec![None], output_tables=vec![], output=String::from("type O { }"), code=String::new(), is_template=false))]
873
+ fn deno_code_node<'a>(
874
+ mut self_: PyRefMut<'_, Self>,
875
+ py: Python<'a>,
876
+ name: String,
877
+ queries: Vec<Option<String>>,
878
+ output_tables: Vec<String>,
879
+ output: String,
880
+ code: String,
881
+ is_template: bool
882
+ ) -> PyResult<&'a PyAny> {
883
+ let file_id = self_.file_id.clone();
884
+ let url = self_.url.clone();
885
+ let branch = self_.current_branch.clone();
886
+ pyo3_asyncio::tokio::future_into_py(py, async move {
887
+ let node = create_code_node(
888
+ name,
889
+ queries,
890
+ output,
891
+ SourceNodeType::Code("DENO".to_string(), code, is_template),
892
+ output_tables
893
+ );
894
+ Ok(push_file_merge(&url, &file_id, node).await?)
895
+ })
896
+ }
897
+
898
+
899
+ #[pyo3(signature = (name=String::new(), queries=vec![None], output_tables=vec![], output=String::from("type O { }"), template=String::new(), action="WRITE".to_string(), embedding_model="TEXT_EMBEDDING_ADA_002".to_string(), db_vendor="QDRANT".to_string(), collection_name=String::new()))]
900
+ fn vector_memory_node<'a>(
901
+ mut self_: PyRefMut<'_, Self>,
902
+ py: Python<'a>,
903
+ name: String,
904
+ queries: Vec<Option<String>>,
905
+ output_tables: Vec<String>,
906
+ output: String,
907
+ template: String,
908
+ action: String, // READ / WRITE
909
+ embedding_model: String,
910
+ db_vendor: String,
911
+ collection_name: String,
912
+ ) -> PyResult<&'a PyAny> {
913
+ let file_id = self_.file_id.clone();
914
+ let url = self_.url.clone();
915
+ let branch = self_.current_branch.clone();
916
+
917
+ pyo3_asyncio::tokio::future_into_py(py, async move {
918
+ let node = create_vector_memory_node(
919
+ name,
920
+ queries,
921
+ output,
922
+ action,
923
+ embedding_model,
924
+ template,
925
+ db_vendor,
926
+ collection_name,
927
+ output_tables
928
+ ).map_err(PyErrWrapper::from)?;
929
+ Ok(push_file_merge(&url, &file_id, node).await?)
930
+ })
931
+ }
932
+
933
+
934
+ //
935
+ // fn observation_node(mut self_: PyRefMut<'_, Self>, name: String, query_def: Option<String>, template: String, model: String) -> PyResult<()> {
936
+ // let file_id = self_.file_id.clone();
937
+ // let node = create_observation_node(
938
+ // "".to_string(),
939
+ // None,
940
+ // "".to_string(),
941
+ // );
942
+ // executor::block_on(self_.client.merge(RequestFileMerge {
943
+ // id: file_id,
944
+ // file: Some(File {
945
+ // nodes: vec![node],
946
+ // ..Default::default()
947
+ // }),
948
+ // branch: 0,
949
+ // }));
950
+ // Ok(())
951
+ // }
952
+ }
953
+
954
+
955
+ /// A Python module implemented in Rust. The name of this function must match
956
+ /// the `lib.name` setting in the `Cargo.toml`, else Python will not be able to
957
+ /// import the module.
958
+ #[pymodule]
959
+ #[pyo3(name = "chidori")]
960
+ fn chidori(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
961
+ pyo3_log::init();
962
+ m.add_class::<Chidori>()?;
963
+ Ok(())
964
+ }