lmnr 0.2.5__py3-none-any.whl → 0.2.7__py3-none-any.whl
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.
- lmnr/cli/parser/nodes/code.py +27 -0
- lmnr/cli/parser/nodes/condition.py +30 -0
- lmnr/cli/parser/nodes/input.py +26 -0
- lmnr/cli/parser/nodes/llm.py +51 -0
- lmnr/cli/parser/nodes/output.py +27 -0
- lmnr/cli/parser/nodes/router.py +37 -0
- lmnr/cli/parser/nodes/semantic_search.py +81 -0
- lmnr/cli/parser/nodes/types.py +76 -95
- lmnr/cli/parser/parser.py +4 -4
- lmnr/cli/{{cookiecutter.lmnr_pipelines_dir_name}}/engine/engine.py +38 -6
- lmnr/cli/{{cookiecutter.lmnr_pipelines_dir_name}}/pipelines/{{cookiecutter.pipeline_dir_name}}/nodes/functions.py +79 -7
- lmnr/sdk/endpoint.py +31 -11
- lmnr/sdk/remote_debugger.py +61 -18
- lmnr/types.py +41 -13
- {lmnr-0.2.5.dist-info → lmnr-0.2.7.dist-info}/METADATA +4 -11
- {lmnr-0.2.5.dist-info → lmnr-0.2.7.dist-info}/RECORD +19 -12
- {lmnr-0.2.5.dist-info → lmnr-0.2.7.dist-info}/LICENSE +0 -0
- {lmnr-0.2.5.dist-info → lmnr-0.2.7.dist-info}/WHEEL +0 -0
- {lmnr-0.2.5.dist-info → lmnr-0.2.7.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,27 @@
|
|
1
|
+
from dataclasses import dataclass
|
2
|
+
import uuid
|
3
|
+
|
4
|
+
from lmnr.cli.parser.nodes import Handle, NodeFunctions
|
5
|
+
from lmnr.cli.parser.utils import map_handles
|
6
|
+
|
7
|
+
|
8
|
+
@dataclass
|
9
|
+
class CodeNode(NodeFunctions):
|
10
|
+
id: uuid.UUID
|
11
|
+
name: str
|
12
|
+
inputs: list[Handle]
|
13
|
+
outputs: list[Handle]
|
14
|
+
inputs_mappings: dict[uuid.UUID, uuid.UUID]
|
15
|
+
|
16
|
+
def handles_mapping(
|
17
|
+
self, output_handle_id_to_node_name: dict[str, str]
|
18
|
+
) -> list[tuple[str, str]]:
|
19
|
+
return map_handles(
|
20
|
+
self.inputs, self.inputs_mappings, output_handle_id_to_node_name
|
21
|
+
)
|
22
|
+
|
23
|
+
def node_type(self) -> str:
|
24
|
+
return "Code"
|
25
|
+
|
26
|
+
def config(self) -> dict:
|
27
|
+
return {}
|
@@ -0,0 +1,30 @@
|
|
1
|
+
from dataclasses import dataclass
|
2
|
+
import uuid
|
3
|
+
|
4
|
+
from lmnr.cli.parser.nodes import Handle, NodeFunctions
|
5
|
+
from lmnr.cli.parser.utils import map_handles
|
6
|
+
|
7
|
+
|
8
|
+
@dataclass
|
9
|
+
class ConditionNode(NodeFunctions):
|
10
|
+
id: uuid.UUID
|
11
|
+
name: str
|
12
|
+
inputs: list[Handle]
|
13
|
+
outputs: list[Handle]
|
14
|
+
inputs_mappings: dict[uuid.UUID, uuid.UUID]
|
15
|
+
condition: str
|
16
|
+
|
17
|
+
def handles_mapping(
|
18
|
+
self, output_handle_id_to_node_name: dict[str, str]
|
19
|
+
) -> list[tuple[str, str]]:
|
20
|
+
return map_handles(
|
21
|
+
self.inputs, self.inputs_mappings, output_handle_id_to_node_name
|
22
|
+
)
|
23
|
+
|
24
|
+
def node_type(self) -> str:
|
25
|
+
return "Condition"
|
26
|
+
|
27
|
+
def config(self) -> dict:
|
28
|
+
return {
|
29
|
+
"condition": self.condition,
|
30
|
+
}
|
@@ -0,0 +1,26 @@
|
|
1
|
+
from dataclasses import dataclass
|
2
|
+
from typing import Optional
|
3
|
+
import uuid
|
4
|
+
|
5
|
+
from lmnr.cli.parser.nodes import Handle, HandleType, NodeFunctions
|
6
|
+
from lmnr.types import NodeInput
|
7
|
+
|
8
|
+
|
9
|
+
@dataclass
|
10
|
+
class InputNode(NodeFunctions):
|
11
|
+
id: uuid.UUID
|
12
|
+
name: str
|
13
|
+
outputs: list[Handle]
|
14
|
+
input: Optional[NodeInput]
|
15
|
+
input_type: HandleType
|
16
|
+
|
17
|
+
def handles_mapping(
|
18
|
+
self, output_handle_id_to_node_name: dict[str, str]
|
19
|
+
) -> list[tuple[str, str]]:
|
20
|
+
return []
|
21
|
+
|
22
|
+
def node_type(self) -> str:
|
23
|
+
return "Input"
|
24
|
+
|
25
|
+
def config(self) -> dict:
|
26
|
+
return {}
|
@@ -0,0 +1,51 @@
|
|
1
|
+
from dataclasses import dataclass
|
2
|
+
from typing import Optional
|
3
|
+
import uuid
|
4
|
+
|
5
|
+
from lmnr.cli.parser.nodes import Handle, NodeFunctions
|
6
|
+
from lmnr.cli.parser.utils import map_handles
|
7
|
+
|
8
|
+
|
9
|
+
@dataclass
|
10
|
+
class LLMNode(NodeFunctions):
|
11
|
+
id: uuid.UUID
|
12
|
+
name: str
|
13
|
+
inputs: list[Handle]
|
14
|
+
dynamic_inputs: list[Handle]
|
15
|
+
outputs: list[Handle]
|
16
|
+
inputs_mappings: dict[uuid.UUID, uuid.UUID]
|
17
|
+
prompt: str
|
18
|
+
model: str
|
19
|
+
model_params: Optional[str]
|
20
|
+
stream: bool
|
21
|
+
structured_output_enabled: bool
|
22
|
+
structured_output_max_retries: int
|
23
|
+
structured_output_schema: Optional[str]
|
24
|
+
structured_output_schema_target: Optional[str]
|
25
|
+
|
26
|
+
def handles_mapping(
|
27
|
+
self, output_handle_id_to_node_name: dict[str, str]
|
28
|
+
) -> list[tuple[str, str]]:
|
29
|
+
combined_inputs = self.inputs + self.dynamic_inputs
|
30
|
+
return map_handles(
|
31
|
+
combined_inputs, self.inputs_mappings, output_handle_id_to_node_name
|
32
|
+
)
|
33
|
+
|
34
|
+
def node_type(self) -> str:
|
35
|
+
return "LLM"
|
36
|
+
|
37
|
+
def config(self) -> dict:
|
38
|
+
# For easier access in the template separate the provider and model here
|
39
|
+
provider, model = self.model.split(":", maxsplit=1)
|
40
|
+
|
41
|
+
return {
|
42
|
+
"prompt": self.prompt,
|
43
|
+
"provider": provider,
|
44
|
+
"model": model,
|
45
|
+
"model_params": self.model_params,
|
46
|
+
"stream": self.stream,
|
47
|
+
"structured_output_enabled": self.structured_output_enabled,
|
48
|
+
"structured_output_max_retries": self.structured_output_max_retries,
|
49
|
+
"structured_output_schema": self.structured_output_schema,
|
50
|
+
"structured_output_schema_target": self.structured_output_schema_target,
|
51
|
+
}
|
@@ -0,0 +1,27 @@
|
|
1
|
+
from dataclasses import dataclass
|
2
|
+
import uuid
|
3
|
+
|
4
|
+
from lmnr.cli.parser.nodes import Handle, NodeFunctions
|
5
|
+
from lmnr.cli.parser.utils import map_handles
|
6
|
+
|
7
|
+
|
8
|
+
@dataclass
|
9
|
+
class OutputNode(NodeFunctions):
|
10
|
+
id: uuid.UUID
|
11
|
+
name: str
|
12
|
+
inputs: list[Handle]
|
13
|
+
outputs: list[Handle]
|
14
|
+
inputs_mappings: dict[uuid.UUID, uuid.UUID]
|
15
|
+
|
16
|
+
def handles_mapping(
|
17
|
+
self, output_handle_id_to_node_name: dict[str, str]
|
18
|
+
) -> list[tuple[str, str]]:
|
19
|
+
return map_handles(
|
20
|
+
self.inputs, self.inputs_mappings, output_handle_id_to_node_name
|
21
|
+
)
|
22
|
+
|
23
|
+
def node_type(self) -> str:
|
24
|
+
return "Output"
|
25
|
+
|
26
|
+
def config(self) -> dict:
|
27
|
+
return {}
|
@@ -0,0 +1,37 @@
|
|
1
|
+
from dataclasses import dataclass
|
2
|
+
import uuid
|
3
|
+
|
4
|
+
from lmnr.cli.parser.nodes import Handle, NodeFunctions
|
5
|
+
from lmnr.cli.parser.utils import map_handles
|
6
|
+
|
7
|
+
|
8
|
+
@dataclass
|
9
|
+
class Route:
|
10
|
+
name: str
|
11
|
+
|
12
|
+
|
13
|
+
@dataclass
|
14
|
+
class RouterNode(NodeFunctions):
|
15
|
+
id: uuid.UUID
|
16
|
+
name: str
|
17
|
+
inputs: list[Handle]
|
18
|
+
outputs: list[Handle]
|
19
|
+
inputs_mappings: dict[uuid.UUID, uuid.UUID]
|
20
|
+
routes: list[Route]
|
21
|
+
has_default_route: bool
|
22
|
+
|
23
|
+
def handles_mapping(
|
24
|
+
self, output_handle_id_to_node_name: dict[str, str]
|
25
|
+
) -> list[tuple[str, str]]:
|
26
|
+
return map_handles(
|
27
|
+
self.inputs, self.inputs_mappings, output_handle_id_to_node_name
|
28
|
+
)
|
29
|
+
|
30
|
+
def node_type(self) -> str:
|
31
|
+
return "Router"
|
32
|
+
|
33
|
+
def config(self) -> dict:
|
34
|
+
return {
|
35
|
+
"routes": str([route.name for route in self.routes]),
|
36
|
+
"has_default_route": str(self.has_default_route),
|
37
|
+
}
|
@@ -0,0 +1,81 @@
|
|
1
|
+
from dataclasses import dataclass
|
2
|
+
from datetime import datetime
|
3
|
+
|
4
|
+
import uuid
|
5
|
+
|
6
|
+
from lmnr.cli.parser.nodes import Handle, NodeFunctions
|
7
|
+
from lmnr.cli.parser.utils import map_handles
|
8
|
+
|
9
|
+
|
10
|
+
@dataclass
|
11
|
+
class FileMetadata:
|
12
|
+
id: uuid.UUID
|
13
|
+
created_at: datetime
|
14
|
+
project_id: uuid.UUID
|
15
|
+
filename: str
|
16
|
+
|
17
|
+
|
18
|
+
@dataclass
|
19
|
+
class Dataset:
|
20
|
+
id: uuid.UUID
|
21
|
+
created_at: datetime
|
22
|
+
project_id: uuid.UUID
|
23
|
+
name: str
|
24
|
+
|
25
|
+
|
26
|
+
@dataclass
|
27
|
+
class SemanticSearchDatasource:
|
28
|
+
type: str
|
29
|
+
id: uuid.UUID
|
30
|
+
# TODO: Paste other fields here, use Union[FileMetadata, Dataset]
|
31
|
+
|
32
|
+
@classmethod
|
33
|
+
def from_dict(cls, datasource_dict: dict) -> "SemanticSearchDatasource":
|
34
|
+
if datasource_dict["type"] == "File":
|
35
|
+
return cls(
|
36
|
+
type="File",
|
37
|
+
id=uuid.UUID(datasource_dict["id"]),
|
38
|
+
)
|
39
|
+
elif datasource_dict["type"] == "Dataset":
|
40
|
+
return cls(
|
41
|
+
type="Dataset",
|
42
|
+
id=uuid.UUID(datasource_dict["id"]),
|
43
|
+
)
|
44
|
+
else:
|
45
|
+
raise ValueError(
|
46
|
+
f"Invalid SemanticSearchDatasource type: {datasource_dict['type']}"
|
47
|
+
)
|
48
|
+
|
49
|
+
|
50
|
+
@dataclass
|
51
|
+
class SemanticSearchNode(NodeFunctions):
|
52
|
+
id: uuid.UUID
|
53
|
+
name: str
|
54
|
+
inputs: list[Handle]
|
55
|
+
outputs: list[Handle]
|
56
|
+
inputs_mappings: dict[uuid.UUID, uuid.UUID]
|
57
|
+
limit: int
|
58
|
+
threshold: float
|
59
|
+
template: str
|
60
|
+
datasources: list[SemanticSearchDatasource]
|
61
|
+
|
62
|
+
def handles_mapping(
|
63
|
+
self, output_handle_id_to_node_name: dict[str, str]
|
64
|
+
) -> list[tuple[str, str]]:
|
65
|
+
return map_handles(
|
66
|
+
self.inputs, self.inputs_mappings, output_handle_id_to_node_name
|
67
|
+
)
|
68
|
+
|
69
|
+
def node_type(self) -> str:
|
70
|
+
return "SemanticSearch"
|
71
|
+
|
72
|
+
def config(self) -> dict:
|
73
|
+
return {
|
74
|
+
"limit": self.limit,
|
75
|
+
"threshold": self.threshold,
|
76
|
+
"template": self.template,
|
77
|
+
"datasource_ids": [str(datasource.id) for datasource in self.datasources],
|
78
|
+
"datasource_ids_list": str(
|
79
|
+
[str(datasource.id) for datasource in self.datasources]
|
80
|
+
),
|
81
|
+
}
|
lmnr/cli/parser/nodes/types.py
CHANGED
@@ -1,8 +1,17 @@
|
|
1
|
-
from
|
2
|
-
from typing import Any, Optional, Union
|
1
|
+
from typing import Any, Union
|
3
2
|
import uuid
|
4
|
-
|
5
|
-
from lmnr.cli.parser.
|
3
|
+
|
4
|
+
from lmnr.cli.parser.nodes import Handle
|
5
|
+
from lmnr.cli.parser.nodes.code import CodeNode
|
6
|
+
from lmnr.cli.parser.nodes.condition import ConditionNode
|
7
|
+
from lmnr.cli.parser.nodes.input import InputNode
|
8
|
+
from lmnr.cli.parser.nodes.llm import LLMNode
|
9
|
+
from lmnr.cli.parser.nodes.output import OutputNode
|
10
|
+
from lmnr.cli.parser.nodes.router import Route, RouterNode
|
11
|
+
from lmnr.cli.parser.nodes.semantic_search import (
|
12
|
+
SemanticSearchDatasource,
|
13
|
+
SemanticSearchNode,
|
14
|
+
)
|
6
15
|
from lmnr.types import NodeInput, ChatMessage
|
7
16
|
|
8
17
|
|
@@ -15,97 +24,15 @@ def node_input_from_json(json_val: Any) -> NodeInput:
|
|
15
24
|
raise ValueError(f"Invalid NodeInput value: {json_val}")
|
16
25
|
|
17
26
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
def handles_mapping(
|
28
|
-
self, output_handle_id_to_node_name: dict[str, str]
|
29
|
-
) -> list[tuple[str, str]]:
|
30
|
-
return []
|
31
|
-
|
32
|
-
def node_type(self) -> str:
|
33
|
-
return "Input"
|
34
|
-
|
35
|
-
def config(self) -> dict:
|
36
|
-
return {}
|
37
|
-
|
38
|
-
|
39
|
-
# TODO: Convert to Pydantic
|
40
|
-
@dataclass
|
41
|
-
class LLMNode(NodeFunctions):
|
42
|
-
id: uuid.UUID
|
43
|
-
name: str
|
44
|
-
inputs: list[Handle]
|
45
|
-
dynamic_inputs: list[Handle]
|
46
|
-
outputs: list[Handle]
|
47
|
-
inputs_mappings: dict[uuid.UUID, uuid.UUID]
|
48
|
-
prompt: str
|
49
|
-
model: str
|
50
|
-
model_params: Optional[str]
|
51
|
-
stream: bool
|
52
|
-
structured_output_enabled: bool
|
53
|
-
structured_output_max_retries: int
|
54
|
-
structured_output_schema: Optional[str]
|
55
|
-
structured_output_schema_target: Optional[str]
|
56
|
-
|
57
|
-
def handles_mapping(
|
58
|
-
self, output_handle_id_to_node_name: dict[str, str]
|
59
|
-
) -> list[tuple[str, str]]:
|
60
|
-
combined_inputs = self.inputs + self.dynamic_inputs
|
61
|
-
return map_handles(
|
62
|
-
combined_inputs, self.inputs_mappings, output_handle_id_to_node_name
|
63
|
-
)
|
64
|
-
|
65
|
-
def node_type(self) -> str:
|
66
|
-
return "LLM"
|
67
|
-
|
68
|
-
def config(self) -> dict:
|
69
|
-
# For easier access in the template separate the provider and model here
|
70
|
-
provider, model = self.model.split(":", maxsplit=1)
|
71
|
-
|
72
|
-
return {
|
73
|
-
"prompt": self.prompt,
|
74
|
-
"provider": provider,
|
75
|
-
"model": model,
|
76
|
-
"model_params": self.model_params,
|
77
|
-
"stream": self.stream,
|
78
|
-
"structured_output_enabled": self.structured_output_enabled,
|
79
|
-
"structured_output_max_retries": self.structured_output_max_retries,
|
80
|
-
"structured_output_schema": self.structured_output_schema,
|
81
|
-
"structured_output_schema_target": self.structured_output_schema_target,
|
82
|
-
}
|
83
|
-
|
84
|
-
|
85
|
-
# TODO: Convert to Pydantic
|
86
|
-
@dataclass
|
87
|
-
class OutputNode(NodeFunctions):
|
88
|
-
id: uuid.UUID
|
89
|
-
name: str
|
90
|
-
inputs: list[Handle]
|
91
|
-
outputs: list[Handle]
|
92
|
-
inputs_mappings: dict[uuid.UUID, uuid.UUID]
|
93
|
-
|
94
|
-
def handles_mapping(
|
95
|
-
self, output_handle_id_to_node_name: dict[str, str]
|
96
|
-
) -> list[tuple[str, str]]:
|
97
|
-
return map_handles(
|
98
|
-
self.inputs, self.inputs_mappings, output_handle_id_to_node_name
|
99
|
-
)
|
100
|
-
|
101
|
-
def node_type(self) -> str:
|
102
|
-
return "Output"
|
103
|
-
|
104
|
-
def config(self) -> dict:
|
105
|
-
return {}
|
106
|
-
|
107
|
-
|
108
|
-
Node = Union[InputNode, OutputNode, LLMNode]
|
27
|
+
Node = Union[
|
28
|
+
InputNode,
|
29
|
+
OutputNode,
|
30
|
+
ConditionNode,
|
31
|
+
LLMNode,
|
32
|
+
RouterNode,
|
33
|
+
SemanticSearchNode,
|
34
|
+
CodeNode,
|
35
|
+
]
|
109
36
|
|
110
37
|
|
111
38
|
def node_from_dict(node_dict: dict) -> Node:
|
@@ -128,6 +55,18 @@ def node_from_dict(node_dict: dict) -> Node:
|
|
128
55
|
for k, v in node_dict["inputsMappings"].items()
|
129
56
|
},
|
130
57
|
)
|
58
|
+
elif node_dict["type"] == "Condition":
|
59
|
+
return ConditionNode(
|
60
|
+
id=uuid.UUID(node_dict["id"]),
|
61
|
+
name=node_dict["name"],
|
62
|
+
inputs=[Handle.from_dict(handle) for handle in node_dict["inputs"]],
|
63
|
+
outputs=[Handle.from_dict(handle) for handle in node_dict["outputs"]],
|
64
|
+
inputs_mappings={
|
65
|
+
uuid.UUID(k): uuid.UUID(v)
|
66
|
+
for k, v in node_dict["inputsMappings"].items()
|
67
|
+
},
|
68
|
+
condition=node_dict["condition"],
|
69
|
+
)
|
131
70
|
elif node_dict["type"] == "LLM":
|
132
71
|
return LLMNode(
|
133
72
|
id=uuid.UUID(node_dict["id"]),
|
@@ -153,5 +92,47 @@ def node_from_dict(node_dict: dict) -> Node:
|
|
153
92
|
structured_output_schema=None,
|
154
93
|
structured_output_schema_target=None,
|
155
94
|
)
|
95
|
+
elif node_dict["type"] == "Router":
|
96
|
+
return RouterNode(
|
97
|
+
id=uuid.UUID(node_dict["id"]),
|
98
|
+
name=node_dict["name"],
|
99
|
+
inputs=[Handle.from_dict(handle) for handle in node_dict["inputs"]],
|
100
|
+
outputs=[Handle.from_dict(handle) for handle in node_dict["outputs"]],
|
101
|
+
inputs_mappings={
|
102
|
+
uuid.UUID(k): uuid.UUID(v)
|
103
|
+
for k, v in node_dict["inputsMappings"].items()
|
104
|
+
},
|
105
|
+
routes=[Route(name=route["name"]) for route in node_dict["routes"]],
|
106
|
+
has_default_route=node_dict["hasDefaultRoute"],
|
107
|
+
)
|
108
|
+
elif node_dict["type"] == "SemanticSearch":
|
109
|
+
return SemanticSearchNode(
|
110
|
+
id=uuid.UUID(node_dict["id"]),
|
111
|
+
name=node_dict["name"],
|
112
|
+
inputs=[Handle.from_dict(handle) for handle in node_dict["inputs"]],
|
113
|
+
outputs=[Handle.from_dict(handle) for handle in node_dict["outputs"]],
|
114
|
+
inputs_mappings={
|
115
|
+
uuid.UUID(k): uuid.UUID(v)
|
116
|
+
for k, v in node_dict["inputsMappings"].items()
|
117
|
+
},
|
118
|
+
limit=node_dict["limit"],
|
119
|
+
threshold=node_dict["threshold"],
|
120
|
+
template=node_dict["template"],
|
121
|
+
datasources=[
|
122
|
+
SemanticSearchDatasource.from_dict(ds)
|
123
|
+
for ds in node_dict["datasources"]
|
124
|
+
],
|
125
|
+
)
|
126
|
+
elif node_dict["type"] == "Code":
|
127
|
+
return CodeNode(
|
128
|
+
id=uuid.UUID(node_dict["id"]),
|
129
|
+
name=node_dict["name"],
|
130
|
+
inputs=[Handle.from_dict(handle) for handle in node_dict["inputs"]],
|
131
|
+
outputs=[Handle.from_dict(handle) for handle in node_dict["outputs"]],
|
132
|
+
inputs_mappings={
|
133
|
+
uuid.UUID(k): uuid.UUID(v)
|
134
|
+
for k, v in node_dict["inputsMappings"].items()
|
135
|
+
},
|
136
|
+
)
|
156
137
|
else:
|
157
138
|
raise ValueError(f"Node type {node_dict['type']} not supported")
|
lmnr/cli/parser/parser.py
CHANGED
@@ -17,6 +17,9 @@ def runnable_graph_to_template_vars(graph: dict) -> dict:
|
|
17
17
|
node = node_from_dict(node_obj)
|
18
18
|
handles_mapping = node.handles_mapping(output_handle_id_to_node_name)
|
19
19
|
node_type = node.node_type()
|
20
|
+
|
21
|
+
unique_handles = set([handle_name for (handle_name, _) in handles_mapping])
|
22
|
+
|
20
23
|
tasks.append(
|
21
24
|
{
|
22
25
|
"name": node.name,
|
@@ -28,10 +31,7 @@ def runnable_graph_to_template_vars(graph: dict) -> dict:
|
|
28
31
|
handle_name for (handle_name, _) in handles_mapping
|
29
32
|
],
|
30
33
|
"handle_args": ", ".join(
|
31
|
-
[
|
32
|
-
f"{handle_name}: NodeInput"
|
33
|
-
for (handle_name, _) in handles_mapping
|
34
|
-
]
|
34
|
+
[f"{handle_name}: NodeInput" for handle_name in unique_handles]
|
35
35
|
),
|
36
36
|
"prev": [],
|
37
37
|
"next": [],
|
@@ -9,7 +9,8 @@ import queue
|
|
9
9
|
from .task import Task
|
10
10
|
from .action import NodeRunError, RunOutput
|
11
11
|
from .state import State
|
12
|
-
from
|
12
|
+
from lmnr.types import NodeInput
|
13
|
+
from lmnr_engine.types import Message
|
13
14
|
|
14
15
|
|
15
16
|
logger = logging.getLogger(__name__)
|
@@ -169,13 +170,17 @@ class Engine:
|
|
169
170
|
active_tasks.remove(task_id)
|
170
171
|
|
171
172
|
if depth > 0:
|
172
|
-
|
173
|
-
# TODO: Implement this for cycles
|
174
|
-
raise NotImplementedError()
|
173
|
+
self.propagate_reset(task_id, task_id, tasks)
|
175
174
|
|
175
|
+
# terminate graph on recursion depth exceeding 10
|
176
176
|
if depth == 10:
|
177
|
-
|
178
|
-
|
177
|
+
logging.error("Max recursion depth exceeded, terminating graph")
|
178
|
+
error = Message(
|
179
|
+
id=uuid.uuid4(),
|
180
|
+
value="Max recursion depth exceeded",
|
181
|
+
start_time=start_time,
|
182
|
+
end_time=datetime.datetime.now(),
|
183
|
+
)
|
179
184
|
|
180
185
|
if not next:
|
181
186
|
# if there are no next tasks, we can terminate the graph
|
@@ -259,3 +264,30 @@ class Engine:
|
|
259
264
|
task,
|
260
265
|
queue,
|
261
266
|
)
|
267
|
+
|
268
|
+
def propagate_reset(
|
269
|
+
self, current_task_name: str, start_task_name: str, tasks: dict[str, Task]
|
270
|
+
) -> None:
|
271
|
+
task = tasks[current_task_name]
|
272
|
+
|
273
|
+
for next_task_name in task.next:
|
274
|
+
if next_task_name == start_task_name:
|
275
|
+
return
|
276
|
+
|
277
|
+
next_task = tasks[next_task_name]
|
278
|
+
|
279
|
+
# in majority of cases there will be only one handle name
|
280
|
+
# however we need to handle the case when single output is mapped
|
281
|
+
# to multiple inputs on the next node
|
282
|
+
handle_names = []
|
283
|
+
for k, v in next_task.handles_mapping:
|
284
|
+
if v == task.name:
|
285
|
+
handle_names.append(k)
|
286
|
+
|
287
|
+
for handle_name in handle_names:
|
288
|
+
next_state = next_task.input_states[handle_name]
|
289
|
+
|
290
|
+
if next_state.get_state().is_success():
|
291
|
+
next_state.set_state(State.empty())
|
292
|
+
next_state.semaphore.release()
|
293
|
+
self.propagate_reset(next_task_name, start_task_name, tasks)
|
@@ -1,6 +1,7 @@
|
|
1
1
|
import requests
|
2
2
|
import json
|
3
3
|
|
4
|
+
from lmnr.types import ConditionedValue
|
4
5
|
from lmnr_engine.engine.action import NodeRunError, RunOutput
|
5
6
|
from lmnr_engine.types import ChatMessage, NodeInput
|
6
7
|
|
@@ -24,8 +25,8 @@ def {{task.function_name}}({{ task.handle_args }}, _env: dict[str, str]) -> RunO
|
|
24
25
|
rendered_prompt = """{{task.config.prompt}}"""
|
25
26
|
{% set prompt_variables = task.input_handle_names|reject("equalto", "chat_messages") %}
|
26
27
|
{% for prompt_variable in prompt_variables %}
|
27
|
-
# TODO: Fix this. Using double curly braces in quotes because normal double curly braces
|
28
|
-
# get replaced during rendering by Cookiecutter. This is a hacky solution
|
28
|
+
{# TODO: Fix this. Using double curly braces in quotes because normal double curly braces
|
29
|
+
# get replaced during rendering by Cookiecutter. This is a hacky solution.#}
|
29
30
|
rendered_prompt = rendered_prompt.replace("{{'{{'}}{{prompt_variable}}{{'}}'}}", {{prompt_variable}}) # type: ignore
|
30
31
|
{% endfor %}
|
31
32
|
|
@@ -68,7 +69,8 @@ def {{task.function_name}}({{ task.handle_args }}, _env: dict[str, str]) -> RunO
|
|
68
69
|
completion_message = chat_completion["choices"][0]["message"]["content"]
|
69
70
|
|
70
71
|
meta_log = {}
|
71
|
-
|
72
|
+
{# TODO: Add node chunk id #}
|
73
|
+
meta_log["node_chunk_id"] = None
|
72
74
|
meta_log["model"] = "{{task.config.model}}"
|
73
75
|
meta_log["prompt"] = rendered_prompt
|
74
76
|
meta_log["input_message_count"] = len(messages)
|
@@ -77,7 +79,8 @@ def {{task.function_name}}({{ task.handle_args }}, _env: dict[str, str]) -> RunO
|
|
77
79
|
meta_log["total_token_count"] = (
|
78
80
|
chat_completion["usage"]["prompt_tokens"] + chat_completion["usage"]["completion_tokens"]
|
79
81
|
)
|
80
|
-
|
82
|
+
{# TODO: Add approximate cost #}
|
83
|
+
meta_log["approximate_cost"] = None
|
81
84
|
{% elif task.config.provider == "anthropic" %}
|
82
85
|
data = {
|
83
86
|
"model": "{{task.config.model}}",
|
@@ -85,7 +88,7 @@ def {{task.function_name}}({{ task.handle_args }}, _env: dict[str, str]) -> RunO
|
|
85
88
|
}
|
86
89
|
data.update(params)
|
87
90
|
|
88
|
-
# TODO: Generate appropriate code based on this if-else block
|
91
|
+
{# TODO: Generate appropriate code based on this if-else block #}
|
89
92
|
if len(messages) == 1 and messages[0].role == "system":
|
90
93
|
messages[0].role = "user"
|
91
94
|
message_jsons = [
|
@@ -116,7 +119,8 @@ def {{task.function_name}}({{ task.handle_args }}, _env: dict[str, str]) -> RunO
|
|
116
119
|
completion_message = chat_completion["content"][0]["text"]
|
117
120
|
|
118
121
|
meta_log = {}
|
119
|
-
|
122
|
+
{# TODO: Add node chunk id#}
|
123
|
+
meta_log["node_chunk_id"] = None
|
120
124
|
meta_log["model"] = "{{task.config.model}}"
|
121
125
|
meta_log["prompt"] = rendered_prompt
|
122
126
|
meta_log["input_message_count"] = len(messages)
|
@@ -125,13 +129,81 @@ def {{task.function_name}}({{ task.handle_args }}, _env: dict[str, str]) -> RunO
|
|
125
129
|
meta_log["total_token_count"] = (
|
126
130
|
chat_completion["usage"]["input_tokens"] + chat_completion["usage"]["output_tokens"]
|
127
131
|
)
|
128
|
-
|
132
|
+
{# TODO: Add approximate cost#}
|
133
|
+
meta_log["approximate_cost"] = None
|
129
134
|
{% else %}
|
130
135
|
{% endif %}
|
131
136
|
|
132
137
|
return RunOutput(status="Success", output=completion_message)
|
133
138
|
|
134
139
|
|
140
|
+
{% elif task.node_type == "SemanticSearch" %}
|
141
|
+
def {{task.function_name}}(query: NodeInput, _env: dict[str, str]) -> RunOutput:
|
142
|
+
{% set datasources_length=task.config.datasource_ids|length %}
|
143
|
+
{% if datasources_length == 0 %}
|
144
|
+
raise NodeRunError("No datasources provided")
|
145
|
+
{% endif %}
|
146
|
+
|
147
|
+
headers = {
|
148
|
+
"Authorization": f"Bearer {_env['LMNR_PROJECT_API_KEY']}",
|
149
|
+
}
|
150
|
+
data = {
|
151
|
+
"query": query,
|
152
|
+
"limit": {{ task.config.limit }},
|
153
|
+
"threshold": {{ task.config.threshold }},
|
154
|
+
"datasourceIds": {{ task.config.datasource_ids_list }},
|
155
|
+
}
|
156
|
+
query_res = requests.post("https://api.lmnr.ai/v2/semantic-search", headers=headers, json=data)
|
157
|
+
if query_res.status_code != 200:
|
158
|
+
raise NodeRunError(f"Vector search request failed:{query_res.status_code}\n{query_res.text}")
|
159
|
+
|
160
|
+
results = query_res.json()
|
161
|
+
|
162
|
+
def render_query_res_point(template: str, point: dict, relevance_index: int) -> str:
|
163
|
+
data = point["data"]
|
164
|
+
data["relevance_index"] = relevance_index
|
165
|
+
res = template
|
166
|
+
for key, value in data.items():
|
167
|
+
res = res.replace("{{'{{'}}" + key + "{{'}}'}}", str(value))
|
168
|
+
return res
|
169
|
+
|
170
|
+
rendered_res_points = [render_query_res_point("""{{task.config.template}}""", res_point, index + 1) for (index, res_point) in enumerate(results)]
|
171
|
+
output = "\n".join(rendered_res_points)
|
172
|
+
|
173
|
+
return RunOutput(status="Success", output=output)
|
174
|
+
|
175
|
+
|
176
|
+
{% elif task.node_type == "Router" %}
|
177
|
+
def {{task.function_name}}(condition: NodeInput, input: NodeInput, _env: dict[str, str]) -> RunOutput:
|
178
|
+
routes = {{ task.config.routes }}
|
179
|
+
has_default_route = {{ task.config.has_default_route }}
|
180
|
+
|
181
|
+
for route in routes:
|
182
|
+
if route == condition:
|
183
|
+
return RunOutput(status="Success", output=ConditionedValue(condition=route, value=input))
|
184
|
+
|
185
|
+
if has_default_route:
|
186
|
+
return RunOutput(status="Success", output=ConditionedValue(condition=routes[-1], value=input))
|
187
|
+
|
188
|
+
raise NodeRunError(f"No route found for condition {condition}")
|
189
|
+
|
190
|
+
|
191
|
+
{% elif task.node_type == "Condition" %}
|
192
|
+
def {{task.function_name}}(input: NodeInput, _env: dict[str, str]) -> RunOutput:
|
193
|
+
condition = "{{task.config.condition}}"
|
194
|
+
|
195
|
+
if input.condition == condition:
|
196
|
+
return RunOutput(status="Success", output=input.value)
|
197
|
+
else:
|
198
|
+
return RunOutput(status="Termination", output=None)
|
199
|
+
|
200
|
+
|
201
|
+
{% elif task.node_type == "Code" %}
|
202
|
+
def {{task.function_name}}({{ task.handle_args }}, _env: dict[str, str]) -> RunOutput:
|
203
|
+
# Implement any functionality you want here
|
204
|
+
raise NodeRunError("Implement your code here")
|
205
|
+
|
206
|
+
|
135
207
|
{% elif task.node_type == "Output" %}
|
136
208
|
def {{task.function_name}}(output: NodeInput, _env: dict[str, str]) -> RunOutput:
|
137
209
|
return RunOutput(status="Success", output=output)
|
lmnr/sdk/endpoint.py
CHANGED
@@ -4,7 +4,7 @@ import pydantic
|
|
4
4
|
import requests
|
5
5
|
from lmnr.types import (
|
6
6
|
EndpointRunError, EndpointRunResponse, NodeInput, EndpointRunRequest,
|
7
|
-
|
7
|
+
ToolCallError, ToolCallRequest, ToolCallResponse, SDKError
|
8
8
|
)
|
9
9
|
from typing import Callable, Optional
|
10
10
|
from websockets.sync.client import connect
|
@@ -126,31 +126,52 @@ class Laminar:
|
|
126
126
|
}
|
127
127
|
) as websocket:
|
128
128
|
websocket.send(request.model_dump_json())
|
129
|
+
req_id = None
|
129
130
|
|
130
131
|
while True:
|
131
132
|
message = websocket.recv()
|
132
133
|
try:
|
133
|
-
tool_call =
|
134
|
+
tool_call = ToolCallRequest.model_validate_json(message)
|
135
|
+
req_id = tool_call.req_id
|
134
136
|
matching_tools = [
|
135
137
|
tool for tool in tools
|
136
|
-
if tool.__name__ == tool_call.function.name
|
138
|
+
if tool.__name__ == tool_call.toolCall.function.name
|
137
139
|
]
|
138
140
|
if not matching_tools:
|
139
141
|
raise SDKError(
|
140
|
-
f'Tool {tool_call.function.name} not found.'
|
142
|
+
f'Tool {tool_call.toolCall.function.name} not found.'
|
141
143
|
' Registered tools: '
|
142
144
|
f'{", ".join([tool.__name__ for tool in tools])}'
|
143
145
|
)
|
144
146
|
tool = matching_tools[0]
|
145
|
-
if tool.__name__ == tool_call.function.name:
|
146
147
|
# default the arguments to an empty dictionary
|
148
|
+
if tool.__name__ == tool_call.toolCall.function.name:
|
147
149
|
arguments = {}
|
148
150
|
try:
|
149
|
-
arguments = json.loads(tool_call.function.arguments)
|
151
|
+
arguments = json.loads(tool_call.toolCall.function.arguments)
|
150
152
|
except:
|
151
153
|
pass
|
152
|
-
|
153
|
-
|
154
|
+
try:
|
155
|
+
response = tool(**arguments)
|
156
|
+
except Exception as e:
|
157
|
+
error_message = 'Error occurred while running tool' +\
|
158
|
+
f'{tool.__name__}: {e}'
|
159
|
+
e = ToolCallError(error=error_message, reqId=req_id)
|
160
|
+
websocket.send(e.model_dump_json())
|
161
|
+
formatted_response = None
|
162
|
+
try:
|
163
|
+
formatted_response = ToolCallResponse(
|
164
|
+
reqId=tool_call.reqId,
|
165
|
+
response=response
|
166
|
+
)
|
167
|
+
except pydantic.ValidationError as e:
|
168
|
+
formatted_response = ToolCallResponse(
|
169
|
+
reqId=tool_call.reqId,
|
170
|
+
response=str(response)
|
171
|
+
)
|
172
|
+
websocket.send(
|
173
|
+
formatted_response.model_dump_json()
|
174
|
+
)
|
154
175
|
except pydantic.ValidationError as e:
|
155
176
|
message_json = json.loads(message)
|
156
177
|
keys = list(message_json.keys())
|
@@ -161,6 +182,5 @@ class Laminar:
|
|
161
182
|
result = EndpointRunResponse.model_validate(message_json)
|
162
183
|
websocket.close()
|
163
184
|
return result
|
164
|
-
except Exception:
|
165
|
-
websocket
|
166
|
-
raise SDKError('Error communicating to backend through websocket')
|
185
|
+
except Exception as e:
|
186
|
+
raise SDKError(f'Error communicating to backend through websocket {e}')
|
lmnr/sdk/remote_debugger.py
CHANGED
@@ -1,13 +1,21 @@
|
|
1
1
|
from typing import Callable, Optional
|
2
2
|
from websockets.sync.client import connect
|
3
|
+
import pydantic
|
3
4
|
import websockets
|
4
|
-
from lmnr.types import
|
5
|
+
from lmnr.types import (
|
6
|
+
DeregisterDebuggerRequest, NodeInput, RegisterDebuggerRequest,
|
7
|
+
SDKError, ToolCallError, ToolCallRequest, ToolCallResponse
|
8
|
+
)
|
5
9
|
import uuid
|
6
10
|
import json
|
7
11
|
from threading import Thread
|
8
12
|
|
9
13
|
class RemoteDebugger:
|
10
|
-
def __init__(
|
14
|
+
def __init__(
|
15
|
+
self,
|
16
|
+
project_api_key: str,
|
17
|
+
tools: list[Callable[..., NodeInput]] = []
|
18
|
+
):
|
11
19
|
self.project_api_key = project_api_key
|
12
20
|
self.url = 'wss://api.lmnr.ai/v2/endpoint/ws'
|
13
21
|
self.tools = tools
|
@@ -25,7 +33,8 @@ class RemoteDebugger:
|
|
25
33
|
self.stop_flag = True
|
26
34
|
self.thread.join()
|
27
35
|
self.session = None
|
28
|
-
# python allows running threads only once, so we need to create
|
36
|
+
# python allows running threads only once, so we need to create
|
37
|
+
# a new thread
|
29
38
|
# in case the user wants to start the debugger again
|
30
39
|
self.thread = Thread(target=self._run)
|
31
40
|
|
@@ -41,10 +50,13 @@ class RemoteDebugger:
|
|
41
50
|
}
|
42
51
|
) as websocket:
|
43
52
|
websocket.send(request.model_dump_json())
|
44
|
-
print(self.
|
53
|
+
print(self._format_session_id_and_registerd_functions())
|
54
|
+
req_id = None
|
55
|
+
|
45
56
|
while not self.stop_flag:
|
46
57
|
try:
|
47
|
-
# blocks the thread until a message
|
58
|
+
# blocks the thread until a message
|
59
|
+
# is received or a timeout (3 seconds) occurs
|
48
60
|
message = websocket.recv(3)
|
49
61
|
except TimeoutError:
|
50
62
|
continue
|
@@ -52,45 +64,76 @@ class RemoteDebugger:
|
|
52
64
|
print("Connection closed. Please restart the debugger.")
|
53
65
|
return
|
54
66
|
try:
|
55
|
-
tool_call =
|
67
|
+
tool_call = ToolCallRequest.model_validate_json(message)
|
68
|
+
req_id = tool_call.reqId
|
56
69
|
except:
|
57
70
|
raise SDKError(f'Invalid message received:\n{message}')
|
58
71
|
matching_tools = [
|
59
72
|
tool for tool in self.tools
|
60
|
-
if tool.__name__ == tool_call.function.name
|
73
|
+
if tool.__name__ == tool_call.toolCall.function.name
|
61
74
|
]
|
62
75
|
if not matching_tools:
|
63
|
-
error_message =
|
64
|
-
|
65
|
-
|
76
|
+
error_message = \
|
77
|
+
f'Tool {tool_call.toolCall.function.name} not found' +\
|
78
|
+
'. Registered tools: ' +\
|
79
|
+
{", ".join([tool.__name__ for tool in self.tools])}
|
80
|
+
e = ToolCallError(error=error_message, reqId=req_id)
|
66
81
|
websocket.send(e.model_dump_json())
|
67
82
|
continue
|
68
83
|
tool = matching_tools[0]
|
69
|
-
if tool.__name__ == tool_call.function.name:
|
84
|
+
if tool.__name__ == tool_call.toolCall.function.name:
|
70
85
|
# default the arguments to an empty dictionary
|
71
86
|
arguments = {}
|
72
87
|
try:
|
73
|
-
arguments = json.loads(
|
88
|
+
arguments = json.loads(
|
89
|
+
tool_call.toolCall.function.arguments)
|
74
90
|
except:
|
75
91
|
pass
|
76
92
|
try:
|
77
|
-
response = tool(**arguments)
|
78
|
-
websocket.send(json.dumps(response))
|
93
|
+
response = tool(**arguments)
|
79
94
|
except Exception as e:
|
80
|
-
error_message =
|
81
|
-
|
95
|
+
error_message = 'Error occurred while running tool' +\
|
96
|
+
f'{tool.__name__}: {e}'
|
97
|
+
e = ToolCallError(error=error_message, reqId=req_id)
|
82
98
|
websocket.send(e.model_dump_json())
|
83
|
-
|
99
|
+
formatted_response = None
|
100
|
+
try:
|
101
|
+
formatted_response = ToolCallResponse(
|
102
|
+
reqId=tool_call.reqId,
|
103
|
+
response=response
|
104
|
+
)
|
105
|
+
except pydantic.ValidationError as e:
|
106
|
+
formatted_response = ToolCallResponse(
|
107
|
+
reqId=tool_call.reqId,
|
108
|
+
response=str(response)
|
109
|
+
)
|
110
|
+
websocket.send(
|
111
|
+
formatted_response.model_dump_json()
|
112
|
+
)
|
113
|
+
websocket.send(
|
114
|
+
DeregisterDebuggerRequest(
|
115
|
+
debuggerSessionId=self.session,
|
116
|
+
deregister=True
|
117
|
+
).model_dump_json()
|
118
|
+
)
|
84
119
|
|
85
120
|
def _generate_session_id(self) -> str:
|
86
121
|
return uuid.uuid4().urn[9:]
|
87
122
|
|
88
|
-
def
|
123
|
+
def _format_session_id_and_registerd_functions(self) -> str:
|
124
|
+
registered_functions = \
|
125
|
+
',\n'.join(['- ' + tool.__name__ for tool in self.tools])
|
89
126
|
return \
|
90
127
|
f"""
|
91
128
|
========================================
|
92
129
|
Debugger Session ID:
|
93
130
|
{self.session}
|
131
|
+
========================================
|
132
|
+
|
133
|
+
Registered functions:
|
134
|
+
{registered_functions}
|
135
|
+
|
94
136
|
========================================
|
95
137
|
"""
|
138
|
+
|
96
139
|
|
lmnr/types.py
CHANGED
@@ -1,13 +1,21 @@
|
|
1
|
-
|
2
1
|
import requests
|
3
2
|
import pydantic
|
3
|
+
import uuid
|
4
4
|
from typing import Union, Optional
|
5
5
|
|
6
|
+
|
6
7
|
class ChatMessage(pydantic.BaseModel):
|
7
8
|
role: str
|
8
9
|
content: str
|
9
10
|
|
10
|
-
|
11
|
+
|
12
|
+
class ConditionedValue(pydantic.BaseModel):
|
13
|
+
condition: str
|
14
|
+
value: "NodeInput"
|
15
|
+
|
16
|
+
|
17
|
+
NodeInput = Union[str, list[ChatMessage], ConditionedValue] # TypeAlias
|
18
|
+
|
11
19
|
|
12
20
|
class EndpointRunRequest(pydantic.BaseModel):
|
13
21
|
inputs: dict[str, NodeInput]
|
@@ -15,10 +23,12 @@ class EndpointRunRequest(pydantic.BaseModel):
|
|
15
23
|
env: dict[str, str] = pydantic.Field(default_factory=dict)
|
16
24
|
metadata: dict[str, str] = pydantic.Field(default_factory=dict)
|
17
25
|
|
26
|
+
|
18
27
|
class EndpointRunResponse(pydantic.BaseModel):
|
19
28
|
outputs: dict[str, dict[str, NodeInput]]
|
20
29
|
run_id: str
|
21
30
|
|
31
|
+
|
22
32
|
class EndpointRunError(Exception):
|
23
33
|
error_code: str
|
24
34
|
error_message: str
|
@@ -26,39 +36,57 @@ class EndpointRunError(Exception):
|
|
26
36
|
def __init__(self, response: requests.Response):
|
27
37
|
try:
|
28
38
|
resp_json = response.json()
|
29
|
-
self.error_code = resp_json[
|
30
|
-
self.error_message = resp_json[
|
39
|
+
self.error_code = resp_json["error_code"]
|
40
|
+
self.error_message = resp_json["error_message"]
|
31
41
|
super().__init__(self.error_message)
|
32
|
-
except:
|
42
|
+
except Exception:
|
33
43
|
super().__init__(response.text)
|
34
|
-
|
44
|
+
|
35
45
|
def __str__(self) -> str:
|
36
46
|
try:
|
37
|
-
return str(
|
38
|
-
|
47
|
+
return str(
|
48
|
+
{"error_code": self.error_code, "error_message": self.error_message}
|
49
|
+
)
|
50
|
+
except Exception:
|
39
51
|
return super().__str__()
|
40
|
-
|
52
|
+
|
53
|
+
|
41
54
|
class SDKError(Exception):
|
42
55
|
def __init__(self, error_message: str):
|
43
56
|
super().__init__(error_message)
|
44
57
|
|
45
|
-
|
58
|
+
|
59
|
+
class ToolCallFunction(pydantic.BaseModel):
|
46
60
|
name: str
|
47
61
|
arguments: str
|
48
62
|
|
63
|
+
|
49
64
|
class ToolCall(pydantic.BaseModel):
|
50
65
|
id: Optional[str]
|
51
66
|
type: Optional[str]
|
52
|
-
function:
|
67
|
+
function: ToolCallFunction
|
68
|
+
|
69
|
+
|
70
|
+
# TODO: allow snake_case and manually convert to camelCase
|
71
|
+
class ToolCallRequest(pydantic.BaseModel):
|
72
|
+
reqId: uuid.UUID
|
73
|
+
toolCall: ToolCall
|
74
|
+
|
75
|
+
|
76
|
+
class ToolCallResponse(pydantic.BaseModel):
|
77
|
+
reqId: uuid.UUID
|
78
|
+
response: NodeInput
|
79
|
+
|
53
80
|
|
54
|
-
ToolCallResponse = NodeInput
|
55
81
|
class ToolCallError(pydantic.BaseModel):
|
82
|
+
reqId: uuid.UUID
|
56
83
|
error: str
|
57
84
|
|
58
|
-
|
85
|
+
|
59
86
|
class RegisterDebuggerRequest(pydantic.BaseModel):
|
60
87
|
debuggerSessionId: str
|
61
88
|
|
89
|
+
|
62
90
|
class DeregisterDebuggerRequest(pydantic.BaseModel):
|
63
91
|
debuggerSessionId: str
|
64
92
|
deregister: bool
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: lmnr
|
3
|
-
Version: 0.2.
|
3
|
+
Version: 0.2.7
|
4
4
|
Summary: Python SDK for Laminar AI
|
5
5
|
License: Apache-2.0
|
6
6
|
Author: lmnr.ai
|
@@ -17,11 +17,12 @@ Requires-Dist: cookiecutter (>=2.6.0,<3.0.0)
|
|
17
17
|
Requires-Dist: pydantic (>=2.7.4,<3.0.0)
|
18
18
|
Requires-Dist: python-dotenv (>=1.0.1,<2.0.0)
|
19
19
|
Requires-Dist: requests (>=2.32.3,<3.0.0)
|
20
|
-
Requires-Dist: urllib3 (==1.26.6)
|
21
20
|
Requires-Dist: websockets (>=12.0,<13.0)
|
22
21
|
Description-Content-Type: text/markdown
|
23
22
|
|
24
|
-
#
|
23
|
+
# Laminar AI
|
24
|
+
|
25
|
+
This repo provides core for code generation, Laminar CLI, and Laminar SDK.
|
25
26
|
|
26
27
|
## Quickstart
|
27
28
|
```sh
|
@@ -139,14 +140,6 @@ Set up `DEBUGGER_SESSION_ID` environment variable in your pipeline.
|
|
139
140
|
|
140
141
|
You can run as many sessions as you need, experimenting with your flows.
|
141
142
|
|
142
|
-
#### 5. Stop the debugger
|
143
|
-
|
144
|
-
In order to stop the session, do
|
145
|
-
|
146
|
-
```python
|
147
|
-
debugger.stop()
|
148
|
-
```
|
149
|
-
|
150
143
|
## CLI for code generation
|
151
144
|
|
152
145
|
### Basic usage
|
@@ -5,24 +5,31 @@ lmnr/cli/cli.py,sha256=pzr5LUILi7TcaJIkC-CzmT7RG7-HWApQmUpgK9bc7mI,2847
|
|
5
5
|
lmnr/cli/cookiecutter.json,sha256=PeiMMzCPzDhsapqYoAceYGPI5lOUNimvFzh5KeQv5QE,359
|
6
6
|
lmnr/cli/parser/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
7
7
|
lmnr/cli/parser/nodes/__init__.py,sha256=BNbbfn0WwbFDA6TNhLOaT_Ji69rCL5voUibqMD7Knng,1163
|
8
|
-
lmnr/cli/parser/nodes/
|
9
|
-
lmnr/cli/parser/
|
8
|
+
lmnr/cli/parser/nodes/code.py,sha256=GXqOxN6tdiStZGWLbN3WZCmDfzwYIgSRmZ5t72AOIXc,661
|
9
|
+
lmnr/cli/parser/nodes/condition.py,sha256=AJny0ILXbSy1hTwsRvZvDUqts9INNx63yQSkD7Dp7KU,740
|
10
|
+
lmnr/cli/parser/nodes/input.py,sha256=Xwktcih7Mezqv4cEejfPkpG8uJxDsbqRytBvKmlJDYE,578
|
11
|
+
lmnr/cli/parser/nodes/llm.py,sha256=iQWYFnQi5PcQD9WJpTSHbSzClM6s0wBOoEqyN5c4yQo,1674
|
12
|
+
lmnr/cli/parser/nodes/output.py,sha256=1XBppSscxM01kfZhE9oOh2GgdCVzyPVe2RAxLI5HmUc,665
|
13
|
+
lmnr/cli/parser/nodes/router.py,sha256=dmCx4ho8_GdFJXQa8UevMf_uEP7AKBv_MJ2zpLC6Vck,894
|
14
|
+
lmnr/cli/parser/nodes/semantic_search.py,sha256=o_XCR7BShAq8VGeKjPTwL6MxLdB07XHSd5CE71sFFiY,2105
|
15
|
+
lmnr/cli/parser/nodes/types.py,sha256=NRhlgI3WGd86AToo-tU974DEZzbLaH4iDdP-hEEQiIo,5343
|
16
|
+
lmnr/cli/parser/parser.py,sha256=kAZEeg358lyj_Q1IIhQB_bA7LW3Aw6RduShIfUSmLqQ,2173
|
10
17
|
lmnr/cli/parser/utils.py,sha256=wVaqHVOR9VXl8Og9nkVyCVgHIcgbtYGkDOEGPtmjZ8g,715
|
11
18
|
lmnr/cli/{{cookiecutter.lmnr_pipelines_dir_name}}/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
12
19
|
lmnr/cli/{{cookiecutter.lmnr_pipelines_dir_name}}/engine/__init__.py,sha256=pLVZqvDnNf9foGR-HXnX2F7WC2TWmyCTNcUctG8SXAI,27
|
13
20
|
lmnr/cli/{{cookiecutter.lmnr_pipelines_dir_name}}/engine/action.py,sha256=mZMQwwPV5LtSfwdwQ7HefI3ttvwuokp4mhVI_Xn1Zck,274
|
14
|
-
lmnr/cli/{{cookiecutter.lmnr_pipelines_dir_name}}/engine/engine.py,sha256=
|
21
|
+
lmnr/cli/{{cookiecutter.lmnr_pipelines_dir_name}}/engine/engine.py,sha256=kCY6J7oQpm3f9YCYY2ZBzM_9bUv_XYTCRD_uFa6PLWQ,9640
|
15
22
|
lmnr/cli/{{cookiecutter.lmnr_pipelines_dir_name}}/engine/state.py,sha256=wTx7jAv7b63-8k34cYfQp_DJxhCCOYT_qRHkmnZfnc0,1686
|
16
23
|
lmnr/cli/{{cookiecutter.lmnr_pipelines_dir_name}}/engine/task.py,sha256=ware5VIrZvluHH3mpH6h7G0YDk5L0buSQ7s09za4Fro,1200
|
17
24
|
lmnr/cli/{{cookiecutter.lmnr_pipelines_dir_name}}/pipelines/{{cookiecutter.pipeline_dir_name}}/__init__.py,sha256=bsfbNUBYtKv37qzc_GLhSAzBam2lnowP_dlr8pubhcg,80
|
18
|
-
lmnr/cli/{{cookiecutter.lmnr_pipelines_dir_name}}/pipelines/{{cookiecutter.pipeline_dir_name}}/nodes/functions.py,sha256=
|
25
|
+
lmnr/cli/{{cookiecutter.lmnr_pipelines_dir_name}}/pipelines/{{cookiecutter.pipeline_dir_name}}/nodes/functions.py,sha256=Bwu8p7m16NAyt9wC0DTQL0MrHbM44WylLs5wTLwSxBM,7845
|
19
26
|
lmnr/cli/{{cookiecutter.lmnr_pipelines_dir_name}}/pipelines/{{cookiecutter.pipeline_dir_name}}/{{cookiecutter.pipeline_dir_name}}.py,sha256=WG-ZMofPpGXCx5jdWVry3_XBzcKjqn8ZycFSiWEOBPg,2858
|
20
27
|
lmnr/cli/{{cookiecutter.lmnr_pipelines_dir_name}}/types.py,sha256=iWuflMV7TiaBPs6-B-BlrovvWpZgHGGHK0v8rSqER7A,997
|
21
|
-
lmnr/sdk/endpoint.py,sha256=
|
22
|
-
lmnr/sdk/remote_debugger.py,sha256=
|
23
|
-
lmnr/types.py,sha256=
|
24
|
-
lmnr-0.2.
|
25
|
-
lmnr-0.2.
|
26
|
-
lmnr-0.2.
|
27
|
-
lmnr-0.2.
|
28
|
-
lmnr-0.2.
|
28
|
+
lmnr/sdk/endpoint.py,sha256=0HjcxMUcJz-klFZO2f5xtTaoLjcaEb8vrJ_YldTWUc8,7467
|
29
|
+
lmnr/sdk/remote_debugger.py,sha256=vCpMz7y3uboOi81qEwr8z3fhQ2H1y2YtJAxXVtb6uCA,5141
|
30
|
+
lmnr/types.py,sha256=OR9xRAQ5uTTwpJTDL_e3jZqxYJWvyX96CCoxr3oo94g,2112
|
31
|
+
lmnr-0.2.7.dist-info/LICENSE,sha256=67b_wJHVV1CBaWkrKFWU1wyqTPSdzH77Ls-59631COg,10411
|
32
|
+
lmnr-0.2.7.dist-info/METADATA,sha256=Ya1KVPAiyGxAZybuXSum8wmy4l-SnyYvaTTrvQ7uZRU,5427
|
33
|
+
lmnr-0.2.7.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
34
|
+
lmnr-0.2.7.dist-info/entry_points.txt,sha256=Qg7ZRax4k-rcQsZ26XRYQ8YFSBiyY2PNxYfq4a6PYXI,41
|
35
|
+
lmnr-0.2.7.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|