uipath-langchain 0.0.112__py3-none-any.whl → 0.1.24__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.
- uipath_langchain/_cli/_templates/main.py.template +12 -13
- uipath_langchain/_cli/cli_init.py +127 -156
- uipath_langchain/_cli/cli_new.py +2 -6
- uipath_langchain/_resources/AGENTS.md +21 -0
- uipath_langchain/_resources/REQUIRED_STRUCTURE.md +92 -0
- uipath_langchain/{tracers → _tracing}/__init__.py +0 -2
- uipath_langchain/_tracing/_instrument_traceable.py +134 -0
- uipath_langchain/_utils/__init__.py +1 -2
- uipath_langchain/_utils/_request_mixin.py +351 -54
- uipath_langchain/_utils/_settings.py +2 -11
- uipath_langchain/agent/exceptions/__init__.py +6 -0
- uipath_langchain/agent/exceptions/exceptions.py +11 -0
- uipath_langchain/agent/guardrails/__init__.py +21 -0
- uipath_langchain/agent/guardrails/actions/__init__.py +11 -0
- uipath_langchain/agent/guardrails/actions/base_action.py +23 -0
- uipath_langchain/agent/guardrails/actions/block_action.py +41 -0
- uipath_langchain/agent/guardrails/actions/escalate_action.py +274 -0
- uipath_langchain/agent/guardrails/actions/log_action.py +57 -0
- uipath_langchain/agent/guardrails/guardrail_nodes.py +125 -0
- uipath_langchain/agent/guardrails/guardrails_factory.py +70 -0
- uipath_langchain/agent/guardrails/guardrails_subgraph.py +247 -0
- uipath_langchain/agent/guardrails/types.py +20 -0
- uipath_langchain/agent/react/__init__.py +14 -0
- uipath_langchain/agent/react/agent.py +113 -0
- uipath_langchain/agent/react/constants.py +2 -0
- uipath_langchain/agent/react/init_node.py +20 -0
- uipath_langchain/agent/react/llm_node.py +43 -0
- uipath_langchain/agent/react/router.py +97 -0
- uipath_langchain/agent/react/terminate_node.py +82 -0
- uipath_langchain/agent/react/tools/__init__.py +7 -0
- uipath_langchain/agent/react/tools/tools.py +50 -0
- uipath_langchain/agent/react/types.py +39 -0
- uipath_langchain/agent/react/utils.py +49 -0
- uipath_langchain/agent/tools/__init__.py +17 -0
- uipath_langchain/agent/tools/context_tool.py +53 -0
- uipath_langchain/agent/tools/escalation_tool.py +111 -0
- uipath_langchain/agent/tools/integration_tool.py +181 -0
- uipath_langchain/agent/tools/process_tool.py +49 -0
- uipath_langchain/agent/tools/static_args.py +138 -0
- uipath_langchain/agent/tools/structured_tool_with_output_type.py +14 -0
- uipath_langchain/agent/tools/tool_factory.py +45 -0
- uipath_langchain/agent/tools/tool_node.py +22 -0
- uipath_langchain/agent/tools/utils.py +11 -0
- uipath_langchain/chat/__init__.py +4 -0
- uipath_langchain/chat/bedrock.py +187 -0
- uipath_langchain/chat/gemini.py +330 -0
- uipath_langchain/chat/mapper.py +309 -0
- uipath_langchain/chat/models.py +261 -38
- uipath_langchain/chat/openai.py +132 -0
- uipath_langchain/chat/supported_models.py +42 -0
- uipath_langchain/embeddings/embeddings.py +136 -36
- uipath_langchain/middlewares.py +0 -2
- uipath_langchain/py.typed +0 -0
- uipath_langchain/retrievers/context_grounding_retriever.py +7 -9
- uipath_langchain/runtime/__init__.py +36 -0
- uipath_langchain/runtime/_serialize.py +46 -0
- uipath_langchain/runtime/config.py +61 -0
- uipath_langchain/runtime/errors.py +43 -0
- uipath_langchain/runtime/factory.py +315 -0
- uipath_langchain/runtime/graph.py +159 -0
- uipath_langchain/runtime/runtime.py +453 -0
- uipath_langchain/runtime/schema.py +349 -0
- uipath_langchain/runtime/storage.py +115 -0
- uipath_langchain/vectorstores/context_grounding_vectorstore.py +90 -110
- {uipath_langchain-0.0.112.dist-info → uipath_langchain-0.1.24.dist-info}/METADATA +42 -20
- uipath_langchain-0.1.24.dist-info/RECORD +76 -0
- {uipath_langchain-0.0.112.dist-info → uipath_langchain-0.1.24.dist-info}/WHEEL +1 -1
- uipath_langchain-0.1.24.dist-info/entry_points.txt +5 -0
- uipath_langchain/_cli/_runtime/_context.py +0 -21
- uipath_langchain/_cli/_runtime/_exception.py +0 -17
- uipath_langchain/_cli/_runtime/_input.py +0 -136
- uipath_langchain/_cli/_runtime/_output.py +0 -234
- uipath_langchain/_cli/_runtime/_runtime.py +0 -371
- uipath_langchain/_cli/_utils/_graph.py +0 -202
- uipath_langchain/_cli/cli_run.py +0 -80
- uipath_langchain/tracers/AsyncUiPathTracer.py +0 -274
- uipath_langchain/tracers/_events.py +0 -33
- uipath_langchain/tracers/_instrument_traceable.py +0 -416
- uipath_langchain/tracers/_utils.py +0 -52
- uipath_langchain-0.0.112.dist-info/RECORD +0 -36
- uipath_langchain-0.0.112.dist-info/entry_points.txt +0 -2
- {uipath_langchain-0.0.112.dist-info → uipath_langchain-0.1.24.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,29 +1,28 @@
|
|
|
1
|
-
from langchain_anthropic import ChatAnthropic
|
|
2
1
|
from langchain_core.messages import HumanMessage, SystemMessage
|
|
3
|
-
from langchain_openai import ChatOpenAI
|
|
4
2
|
from langgraph.graph import START, StateGraph, END
|
|
3
|
+
from uipath_langchain.chat import UiPathChat
|
|
5
4
|
from pydantic import BaseModel
|
|
6
|
-
import os
|
|
7
5
|
|
|
8
|
-
|
|
6
|
+
llm = UiPathChat(model="gpt-4o-mini-2024-07-18")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class GraphState(BaseModel):
|
|
9
10
|
topic: str
|
|
10
11
|
|
|
12
|
+
|
|
11
13
|
class GraphOutput(BaseModel):
|
|
12
14
|
report: str
|
|
13
15
|
|
|
14
|
-
async def generate_report(state: GraphInput) -> GraphOutput:
|
|
15
|
-
if os.getenv("ANTHROPIC_API_KEY"):
|
|
16
|
-
llm_model = ChatAnthropic(model="claude-3-5-sonnet-latest")
|
|
17
|
-
elif os.getenv("OPENAI_API_KEY"):
|
|
18
|
-
llm_model = ChatOpenAI(model="gpt-4o-mini", temperature=0)
|
|
19
|
-
else:
|
|
20
|
-
raise Exception("Missing API Key. Please define either ANTHROPIC_API_KEY or OPENAI_API_KEY.")
|
|
21
16
|
|
|
17
|
+
async def generate_report(state: GraphState) -> GraphOutput:
|
|
22
18
|
system_prompt = "You are a report generator. Please provide a brief report based on the given topic."
|
|
23
|
-
output = await
|
|
19
|
+
output = await llm.ainvoke(
|
|
20
|
+
[SystemMessage(system_prompt), HumanMessage(state.topic)]
|
|
21
|
+
)
|
|
24
22
|
return GraphOutput(report=output.content)
|
|
25
23
|
|
|
26
|
-
|
|
24
|
+
|
|
25
|
+
builder = StateGraph(GraphState, output=GraphOutput)
|
|
27
26
|
|
|
28
27
|
builder.add_node("generate_report", generate_report)
|
|
29
28
|
|
|
@@ -1,101 +1,148 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
import
|
|
2
|
+
import importlib.resources
|
|
3
3
|
import os
|
|
4
|
-
import
|
|
5
|
-
from
|
|
4
|
+
import shutil
|
|
5
|
+
from collections.abc import Generator
|
|
6
|
+
from enum import Enum
|
|
7
|
+
from typing import Any
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
import click
|
|
8
10
|
from uipath._cli._utils._console import ConsoleLogger
|
|
9
|
-
from uipath._cli._utils._parse_ast import generate_bindings_json # type: ignore
|
|
10
11
|
from uipath._cli.middlewares import MiddlewareResult
|
|
11
12
|
|
|
12
|
-
from .
|
|
13
|
+
from uipath_langchain.runtime.config import LangGraphConfig
|
|
13
14
|
|
|
14
15
|
console = ConsoleLogger()
|
|
15
16
|
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
"""
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
18
|
+
class FileOperationStatus(str, Enum):
|
|
19
|
+
"""Status of a file operation."""
|
|
20
|
+
|
|
21
|
+
CREATED = "created"
|
|
22
|
+
UPDATED = "updated"
|
|
23
|
+
SKIPPED = "skipped"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def generate_agent_md_file(
|
|
27
|
+
target_directory: str,
|
|
28
|
+
file_name: str,
|
|
29
|
+
resource_name: str,
|
|
30
|
+
no_agents_md_override: bool,
|
|
31
|
+
) -> tuple[str, FileOperationStatus] | None:
|
|
32
|
+
"""Generate an agent-specific file from the packaged resource.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
target_directory: The directory where the file should be created.
|
|
36
|
+
file_name: The name of the file should be created.
|
|
37
|
+
resource_name: The name of the resource folder where should be the file.
|
|
38
|
+
no_agents_md_override: Whether to override existing files.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
A tuple of (file_name, status) where status is a FileOperationStatus:
|
|
42
|
+
- CREATED: File was created
|
|
43
|
+
- UPDATED: File was overwritten
|
|
44
|
+
- SKIPPED: File exists and no_agents_md_override is True
|
|
45
|
+
Returns None if an error occurred.
|
|
46
|
+
"""
|
|
47
|
+
target_path = os.path.join(target_directory, file_name)
|
|
48
|
+
will_override = os.path.exists(target_path)
|
|
49
|
+
|
|
50
|
+
if will_override and no_agents_md_override:
|
|
51
|
+
return file_name, FileOperationStatus.SKIPPED
|
|
52
|
+
try:
|
|
53
|
+
source_path = importlib.resources.files(resource_name).joinpath(file_name)
|
|
37
54
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
) -> Dict[str, Any] | list[Any]:
|
|
41
|
-
"""Process the schema to handle nullable types by removing anyOf with null and keeping the base type."""
|
|
42
|
-
if isinstance(schema, dict):
|
|
43
|
-
if "anyOf" in schema and len(schema["anyOf"]) == 2:
|
|
44
|
-
types = [t.get("type") for t in schema["anyOf"]]
|
|
45
|
-
if "null" in types:
|
|
46
|
-
non_null_type = next(
|
|
47
|
-
t for t in schema["anyOf"] if t.get("type") != "null"
|
|
48
|
-
)
|
|
49
|
-
return non_null_type
|
|
55
|
+
with importlib.resources.as_file(source_path) as s_path:
|
|
56
|
+
shutil.copy(s_path, target_path)
|
|
50
57
|
|
|
51
|
-
return
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
58
|
+
return (
|
|
59
|
+
file_name,
|
|
60
|
+
FileOperationStatus.UPDATED
|
|
61
|
+
if will_override
|
|
62
|
+
else FileOperationStatus.CREATED,
|
|
63
|
+
)
|
|
55
64
|
|
|
65
|
+
except Exception as e:
|
|
66
|
+
console.warning(f"Could not create {file_name}: {e}")
|
|
67
|
+
return None
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def generate_specific_agents_md_files(
|
|
71
|
+
target_directory: str, no_agents_md_override: bool
|
|
72
|
+
) -> Generator[tuple[str, FileOperationStatus], None, None]:
|
|
73
|
+
"""Generate agent-specific files from the packaged resource.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
target_directory: The directory where the files should be created.
|
|
77
|
+
no_agents_md_override: Whether to override existing files.
|
|
78
|
+
|
|
79
|
+
Yields:
|
|
80
|
+
Tuple of (file_name, status) for each file operation, where status is a FileOperationStatus:
|
|
81
|
+
- CREATED: File was created
|
|
82
|
+
- UPDATED: File was overwritten
|
|
83
|
+
- SKIPPED: File exists and was not overwritten
|
|
84
|
+
"""
|
|
85
|
+
agent_dir = os.path.join(target_directory, ".agent")
|
|
86
|
+
os.makedirs(agent_dir, exist_ok=True)
|
|
87
|
+
|
|
88
|
+
file_configs = [
|
|
89
|
+
(target_directory, "CLAUDE.md", "uipath._resources"),
|
|
90
|
+
(agent_dir, "CLI_REFERENCE.md", "uipath._resources"),
|
|
91
|
+
(agent_dir, "SDK_REFERENCE.md", "uipath._resources"),
|
|
92
|
+
(target_directory, "AGENTS.md", "uipath_langchain._resources"),
|
|
93
|
+
(agent_dir, "REQUIRED_STRUCTURE.md", "uipath_langchain._resources"),
|
|
94
|
+
]
|
|
95
|
+
|
|
96
|
+
for directory, file_name, resource_name in file_configs:
|
|
97
|
+
result = generate_agent_md_file(
|
|
98
|
+
directory, file_name, resource_name, no_agents_md_override
|
|
99
|
+
)
|
|
100
|
+
if result:
|
|
101
|
+
yield result
|
|
56
102
|
|
|
57
|
-
def generate_schema_from_graph(graph: CompiledStateGraph) -> Dict[str, Any]:
|
|
58
|
-
"""Extract input/output schema from a LangGraph graph"""
|
|
59
|
-
schema = {
|
|
60
|
-
"input": {"type": "object", "properties": {}, "required": []},
|
|
61
|
-
"output": {"type": "object", "properties": {}, "required": []},
|
|
62
|
-
}
|
|
63
103
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
input_schema = graph.input_schema.model_json_schema()
|
|
67
|
-
unpacked_ref_def_properties = resolve_refs(input_schema)
|
|
104
|
+
def generate_agents_md_files(options: dict[str, Any]) -> None:
|
|
105
|
+
"""Generate agent MD files and log categorized summary.
|
|
68
106
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
107
|
+
Args:
|
|
108
|
+
options: Options dictionary
|
|
109
|
+
"""
|
|
110
|
+
current_directory = os.getcwd()
|
|
111
|
+
no_agents_md_override = options.get("no_agents_md_override", False)
|
|
73
112
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
)
|
|
113
|
+
created_files = []
|
|
114
|
+
updated_files = []
|
|
115
|
+
skipped_files = []
|
|
78
116
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
117
|
+
for file_name, status in generate_specific_agents_md_files(
|
|
118
|
+
current_directory, no_agents_md_override
|
|
119
|
+
):
|
|
120
|
+
if status == FileOperationStatus.CREATED:
|
|
121
|
+
created_files.append(file_name)
|
|
122
|
+
elif status == FileOperationStatus.UPDATED:
|
|
123
|
+
updated_files.append(file_name)
|
|
124
|
+
elif status == FileOperationStatus.SKIPPED:
|
|
125
|
+
skipped_files.append(file_name)
|
|
83
126
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
)
|
|
127
|
+
if created_files:
|
|
128
|
+
files_str = ", ".join(click.style(f, fg="cyan") for f in created_files)
|
|
129
|
+
console.success(f"Created: {files_str}")
|
|
88
130
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
)
|
|
131
|
+
if updated_files:
|
|
132
|
+
files_str = ", ".join(click.style(f, fg="cyan") for f in updated_files)
|
|
133
|
+
console.success(f"Updated: {files_str}")
|
|
93
134
|
|
|
94
|
-
|
|
135
|
+
if skipped_files:
|
|
136
|
+
files_str = ", ".join(click.style(f, fg="yellow") for f in skipped_files)
|
|
137
|
+
console.info(f"Skipped (already exist): {files_str}")
|
|
95
138
|
|
|
96
139
|
|
|
97
|
-
async def langgraph_init_middleware_async(
|
|
140
|
+
async def langgraph_init_middleware_async(
|
|
141
|
+
options: dict[str, Any] | None = None,
|
|
142
|
+
) -> MiddlewareResult:
|
|
98
143
|
"""Middleware to check for langgraph.json and create uipath.json with schemas"""
|
|
144
|
+
options = options or {}
|
|
145
|
+
|
|
99
146
|
config = LangGraphConfig()
|
|
100
147
|
if not config.exists:
|
|
101
148
|
return MiddlewareResult(
|
|
@@ -103,86 +150,8 @@ async def langgraph_init_middleware_async(entrypoint: str) -> MiddlewareResult:
|
|
|
103
150
|
) # Continue with normal flow if no langgraph.json
|
|
104
151
|
|
|
105
152
|
try:
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
all_bindings = {"version": "2.0", "resources": []}
|
|
109
|
-
mermaids = {}
|
|
110
|
-
|
|
111
|
-
for graph in config.graphs:
|
|
112
|
-
if entrypoint and graph.name != entrypoint:
|
|
113
|
-
continue
|
|
114
|
-
|
|
115
|
-
try:
|
|
116
|
-
loaded_graph = await graph.load_graph()
|
|
117
|
-
state_graph = (
|
|
118
|
-
loaded_graph.builder
|
|
119
|
-
if isinstance(loaded_graph, CompiledStateGraph)
|
|
120
|
-
else loaded_graph
|
|
121
|
-
)
|
|
122
|
-
compiled_graph = state_graph.compile()
|
|
123
|
-
graph_schema = generate_schema_from_graph(compiled_graph)
|
|
124
|
-
|
|
125
|
-
mermaids[graph.name] = compiled_graph.get_graph(xray=1).draw_mermaid()
|
|
126
|
-
|
|
127
|
-
try:
|
|
128
|
-
# Make sure the file path exists
|
|
129
|
-
if os.path.exists(graph.file_path):
|
|
130
|
-
file_bindings = generate_bindings_json(graph.file_path)
|
|
131
|
-
|
|
132
|
-
# Merge bindings
|
|
133
|
-
if "resources" in file_bindings:
|
|
134
|
-
all_bindings["resources"] = file_bindings["resources"]
|
|
135
|
-
except Exception as e:
|
|
136
|
-
console.warning(
|
|
137
|
-
f"Warning: Could not generate bindings for {graph.file_path}: {str(e)}"
|
|
138
|
-
)
|
|
139
|
-
|
|
140
|
-
new_entrypoint: dict[str, Any] = {
|
|
141
|
-
"filePath": graph.name,
|
|
142
|
-
"uniqueId": str(uuid.uuid4()),
|
|
143
|
-
"type": "agent",
|
|
144
|
-
"input": graph_schema["input"],
|
|
145
|
-
"output": graph_schema["output"],
|
|
146
|
-
}
|
|
147
|
-
entrypoints.append(new_entrypoint)
|
|
148
|
-
|
|
149
|
-
except Exception as e:
|
|
150
|
-
console.error(f"Error during graph load: {e}")
|
|
151
|
-
return MiddlewareResult(
|
|
152
|
-
should_continue=False,
|
|
153
|
-
should_include_stacktrace=True,
|
|
154
|
-
)
|
|
155
|
-
finally:
|
|
156
|
-
await graph.cleanup()
|
|
157
|
-
|
|
158
|
-
if entrypoint and not entrypoints:
|
|
159
|
-
console.error(f"Error: No graph found with name '{entrypoint}'")
|
|
160
|
-
return MiddlewareResult(
|
|
161
|
-
should_continue=False,
|
|
162
|
-
)
|
|
163
|
-
|
|
164
|
-
uipath_config = {"entryPoints": entrypoints, "bindings": all_bindings}
|
|
165
|
-
|
|
166
|
-
# Save the uipath.json file
|
|
167
|
-
config_path = "uipath.json"
|
|
168
|
-
with open(config_path, "w") as f:
|
|
169
|
-
json.dump(uipath_config, f, indent=2)
|
|
170
|
-
|
|
171
|
-
for graph_name, mermaid_content in mermaids.items():
|
|
172
|
-
mermaid_file_path = f"{graph_name}.mermaid"
|
|
173
|
-
try:
|
|
174
|
-
with open(mermaid_file_path, "w") as f:
|
|
175
|
-
f.write(mermaid_content)
|
|
176
|
-
console.success(f" Created '{mermaid_file_path}' file.")
|
|
177
|
-
except Exception as write_error:
|
|
178
|
-
console.error(
|
|
179
|
-
f"Error writing mermaid file for '{graph_name}': {str(write_error)}"
|
|
180
|
-
)
|
|
181
|
-
return MiddlewareResult(
|
|
182
|
-
should_continue=False,
|
|
183
|
-
should_include_stacktrace=True,
|
|
184
|
-
)
|
|
185
|
-
console.success(f" Created '{config_path}' file.")
|
|
153
|
+
generate_agents_md_files(options)
|
|
154
|
+
|
|
186
155
|
return MiddlewareResult(should_continue=False)
|
|
187
156
|
|
|
188
157
|
except Exception as e:
|
|
@@ -193,6 +162,8 @@ async def langgraph_init_middleware_async(entrypoint: str) -> MiddlewareResult:
|
|
|
193
162
|
)
|
|
194
163
|
|
|
195
164
|
|
|
196
|
-
def langgraph_init_middleware(
|
|
165
|
+
def langgraph_init_middleware(
|
|
166
|
+
options: dict[str, Any] | None = None,
|
|
167
|
+
) -> MiddlewareResult:
|
|
197
168
|
"""Middleware to check for langgraph.json and create uipath.json with schemas"""
|
|
198
|
-
return asyncio.run(langgraph_init_middleware_async(
|
|
169
|
+
return asyncio.run(langgraph_init_middleware_async(options))
|
uipath_langchain/_cli/cli_new.py
CHANGED
|
@@ -31,10 +31,9 @@ version = "0.0.1"
|
|
|
31
31
|
description = "{project_name}"
|
|
32
32
|
authors = [{{ name = "John Doe", email = "john.doe@myemail.com" }}]
|
|
33
33
|
dependencies = [
|
|
34
|
-
"uipath-langchain>=0.0
|
|
35
|
-
"langchain-anthropic>=0.3.8",
|
|
34
|
+
"uipath-langchain>=0.1.0",
|
|
36
35
|
]
|
|
37
|
-
requires-python = ">=3.
|
|
36
|
+
requires-python = ">=3.11"
|
|
38
37
|
"""
|
|
39
38
|
|
|
40
39
|
with open(project_toml_path, "w") as f:
|
|
@@ -54,9 +53,6 @@ def langgraph_new_middleware(name: str) -> MiddlewareResult:
|
|
|
54
53
|
console.success("Created 'langgraph.json' file.")
|
|
55
54
|
generate_pyproject(directory, name)
|
|
56
55
|
console.success("Created 'pyproject.toml' file.")
|
|
57
|
-
console.config(
|
|
58
|
-
f""" Please ensure to define either {click.style("ANTHROPIC_API_KEY", fg="bright_yellow")} or {click.style("OPENAI_API_KEY", fg="bright_yellow")} in your .env file. """
|
|
59
|
-
)
|
|
60
56
|
init_command = """uipath init"""
|
|
61
57
|
run_command = """uipath run agent '{"topic": "UiPath"}'"""
|
|
62
58
|
console.hint(
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Agent Code Patterns Reference
|
|
2
|
+
|
|
3
|
+
This document provides practical code patterns for building UiPath coded agents using LangGraph and the UiPath Python SDK.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Documentation Structure
|
|
8
|
+
|
|
9
|
+
This documentation is split into multiple files for efficient context loading. Load only the files you need:
|
|
10
|
+
|
|
11
|
+
1. **@.agent/REQUIRED_STRUCTURE.md** - Agent structure patterns and templates
|
|
12
|
+
- **When to load:** Creating a new agent or understanding required patterns
|
|
13
|
+
- **Contains:** Required Pydantic models (Input, State, Output), LLM initialization patterns, standard agent template
|
|
14
|
+
|
|
15
|
+
2. **@.agent/SDK_REFERENCE.md** - Complete SDK API reference
|
|
16
|
+
- **When to load:** Calling UiPath SDK methods, working with services (actions, assets, jobs, etc.)
|
|
17
|
+
- **Contains:** All SDK services and methods with full signatures and type annotations
|
|
18
|
+
|
|
19
|
+
3. **@.agent/CLI_REFERENCE.md** - CLI commands documentation
|
|
20
|
+
- **When to load:** Working with `uipath init`, `uipath run`, or `uipath eval` commands
|
|
21
|
+
- **Contains:** Command syntax, options, usage examples, and workflows
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
## Required Agent Structure
|
|
2
|
+
|
|
3
|
+
**IMPORTANT**: All UiPath coded agents MUST follow this standard structure unless explicitly specified otherwise by the user.
|
|
4
|
+
|
|
5
|
+
### Required Components
|
|
6
|
+
|
|
7
|
+
Every agent implementation MUST include these three Pydantic models:
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
from pydantic import BaseModel
|
|
11
|
+
|
|
12
|
+
class Input(BaseModel):
|
|
13
|
+
"""Define input fields that the agent accepts"""
|
|
14
|
+
# Add your input fields here
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
class State(BaseModel):
|
|
18
|
+
"""Define the agent's internal state that flows between nodes"""
|
|
19
|
+
# Add your state fields here
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
class Output(BaseModel):
|
|
23
|
+
"""Define output fields that the agent returns"""
|
|
24
|
+
# Add your output fields here
|
|
25
|
+
pass
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Required LLM Initialization
|
|
29
|
+
|
|
30
|
+
Unless the user explicitly requests a different LLM provider, always use `UiPathChat`:
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
from uipath_langchain.chat import UiPathChat
|
|
34
|
+
|
|
35
|
+
llm = UiPathChat(model="gpt-4o-2024-08-06", temperature=0.7)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
**Alternative LLMs** (only use if explicitly requested):
|
|
39
|
+
- `ChatOpenAI` from `langchain_openai`
|
|
40
|
+
- `ChatAnthropic` from `langchain_anthropic`
|
|
41
|
+
- Other LangChain-compatible LLMs
|
|
42
|
+
|
|
43
|
+
### Standard Agent Template
|
|
44
|
+
|
|
45
|
+
Every agent should follow this basic structure:
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from langchain_core.messages import SystemMessage, HumanMessage
|
|
49
|
+
from langgraph.graph import START, StateGraph, END
|
|
50
|
+
from uipath_langchain.chat import UiPathChat
|
|
51
|
+
from pydantic import BaseModel
|
|
52
|
+
|
|
53
|
+
# 1. Define Input, State, and Output models
|
|
54
|
+
class Input(BaseModel):
|
|
55
|
+
field: str
|
|
56
|
+
|
|
57
|
+
class State(BaseModel):
|
|
58
|
+
field: str
|
|
59
|
+
result: str = ""
|
|
60
|
+
|
|
61
|
+
class Output(BaseModel):
|
|
62
|
+
result: str
|
|
63
|
+
|
|
64
|
+
# 2. Initialize UiPathChat LLM
|
|
65
|
+
llm = UiPathChat(model="gpt-4o-2024-08-06", temperature=0.7)
|
|
66
|
+
|
|
67
|
+
# 3. Define agent nodes (async functions)
|
|
68
|
+
async def process_node(state: State) -> State:
|
|
69
|
+
response = await llm.ainvoke([HumanMessage(state.field)])
|
|
70
|
+
return State(field=state.field, result=response.content)
|
|
71
|
+
|
|
72
|
+
async def output_node(state: State) -> Output:
|
|
73
|
+
return Output(result=state.result)
|
|
74
|
+
|
|
75
|
+
# 4. Build the graph
|
|
76
|
+
builder = StateGraph(State, input=Input, output=Output)
|
|
77
|
+
builder.add_node("process", process_node)
|
|
78
|
+
builder.add_node("output", output_node)
|
|
79
|
+
builder.add_edge(START, "process")
|
|
80
|
+
builder.add_edge("process", "output")
|
|
81
|
+
builder.add_edge("output", END)
|
|
82
|
+
|
|
83
|
+
# 5. Compile the graph
|
|
84
|
+
graph = builder.compile()
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**Key Rules**:
|
|
88
|
+
1. Always use async/await for all node functions
|
|
89
|
+
2. All nodes (except output) must accept and return `State`
|
|
90
|
+
3. The final output node must return `Output`
|
|
91
|
+
4. Use `StateGraph(State, input=Input, output=Output)` for initialization
|
|
92
|
+
5. Always compile with `graph = builder.compile()`
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
import logging
|
|
3
|
+
import sys
|
|
4
|
+
from typing import Any, Callable
|
|
5
|
+
|
|
6
|
+
from uipath.tracing import traced
|
|
7
|
+
|
|
8
|
+
# Original module and traceable function references
|
|
9
|
+
original_langsmith: Any = None
|
|
10
|
+
original_traceable: Any = None
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _map_traceable_to_traced_args(
|
|
16
|
+
run_type: str | None = None,
|
|
17
|
+
name: str | None = None,
|
|
18
|
+
tags: list[str] | None = None,
|
|
19
|
+
metadata: dict[str, Any] | None = None,
|
|
20
|
+
**kwargs: Any,
|
|
21
|
+
) -> dict[str, Any]:
|
|
22
|
+
"""
|
|
23
|
+
Map LangSmith @traceable arguments to UiPath @traced() arguments.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
run_type: Function type (tool, chain, llm, retriever, etc.)
|
|
27
|
+
name: Custom name for the traced function
|
|
28
|
+
tags: List of tags for categorization
|
|
29
|
+
metadata: Additional metadata dictionary
|
|
30
|
+
**kwargs: Additional arguments (ignored)
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
Dict containing mapped arguments for @traced()
|
|
34
|
+
"""
|
|
35
|
+
traced_args = {}
|
|
36
|
+
|
|
37
|
+
# Direct mappings
|
|
38
|
+
if name is not None:
|
|
39
|
+
traced_args["name"] = name
|
|
40
|
+
|
|
41
|
+
# Pass through run_type directly to UiPath @traced()
|
|
42
|
+
if run_type:
|
|
43
|
+
traced_args["run_type"] = run_type
|
|
44
|
+
|
|
45
|
+
# For span_type, we can derive from run_type or use a default
|
|
46
|
+
if run_type:
|
|
47
|
+
# Map run_type to appropriate span_type for OpenTelemetry
|
|
48
|
+
span_type_mapping = {
|
|
49
|
+
"tool": "tool_call",
|
|
50
|
+
"chain": "chain_execution",
|
|
51
|
+
"llm": "llm_call",
|
|
52
|
+
"retriever": "retrieval",
|
|
53
|
+
"embedding": "embedding",
|
|
54
|
+
"prompt": "prompt_template",
|
|
55
|
+
"parser": "output_parser",
|
|
56
|
+
}
|
|
57
|
+
traced_args["span_type"] = span_type_mapping.get(run_type, run_type)
|
|
58
|
+
|
|
59
|
+
# Note: UiPath @traced() doesn't support custom attributes directly
|
|
60
|
+
# Tags and metadata information is lost in the current mapping
|
|
61
|
+
# This could be enhanced in future versions
|
|
62
|
+
|
|
63
|
+
return traced_args
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def otel_traceable_adapter(
|
|
67
|
+
func: Callable[..., Any] | None = None,
|
|
68
|
+
*,
|
|
69
|
+
run_type: str | None = None,
|
|
70
|
+
name: str | None = None,
|
|
71
|
+
tags: list[str] | None = None,
|
|
72
|
+
metadata: dict[str, Any] | None = None,
|
|
73
|
+
**kwargs: Any,
|
|
74
|
+
):
|
|
75
|
+
"""
|
|
76
|
+
OTEL-based adapter that converts LangSmith @traceable decorator calls to UiPath @traced().
|
|
77
|
+
|
|
78
|
+
This function maintains the same interface as LangSmith's @traceable but uses
|
|
79
|
+
UiPath's OpenTelemetry-based tracing system underneath.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
func: Function to be decorated (when used without parentheses)
|
|
83
|
+
run_type: Type of function (tool, chain, llm, etc.)
|
|
84
|
+
name: Custom name for tracing
|
|
85
|
+
tags: List of tags for categorization
|
|
86
|
+
metadata: Additional metadata dictionary
|
|
87
|
+
**kwargs: Additional arguments (for future compatibility)
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
Decorated function or decorator function
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
def decorator(f: Callable[..., Any]) -> Callable[..., Any]:
|
|
94
|
+
# Map arguments to @traced() format
|
|
95
|
+
traced_args = _map_traceable_to_traced_args(
|
|
96
|
+
run_type=run_type, name=name, tags=tags, metadata=metadata, **kwargs
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
# Apply UiPath @traced() decorator
|
|
100
|
+
return traced(**traced_args)(f)
|
|
101
|
+
|
|
102
|
+
# Handle both @traceable and @traceable(...) usage patterns
|
|
103
|
+
if func is None:
|
|
104
|
+
# Called as @traceable(...) - return decorator
|
|
105
|
+
return decorator
|
|
106
|
+
else:
|
|
107
|
+
# Called as @traceable - apply decorator directly
|
|
108
|
+
return decorator(func)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _instrument_traceable_attributes():
|
|
112
|
+
"""Apply the patch to langsmith module at import time."""
|
|
113
|
+
global original_langsmith, original_traceable
|
|
114
|
+
|
|
115
|
+
# Import the original module if not already done
|
|
116
|
+
if original_langsmith is None:
|
|
117
|
+
# Temporarily remove our custom module from sys.modules
|
|
118
|
+
if "langsmith" in sys.modules:
|
|
119
|
+
original_langsmith = sys.modules["langsmith"]
|
|
120
|
+
del sys.modules["langsmith"]
|
|
121
|
+
|
|
122
|
+
# Import the original module
|
|
123
|
+
original_langsmith = importlib.import_module("langsmith")
|
|
124
|
+
|
|
125
|
+
# Store the original traceable
|
|
126
|
+
original_traceable = original_langsmith.traceable
|
|
127
|
+
|
|
128
|
+
# Replace the traceable function with our patched version
|
|
129
|
+
original_langsmith.traceable = otel_traceable_adapter
|
|
130
|
+
|
|
131
|
+
# Put our modified module back
|
|
132
|
+
sys.modules["langsmith"] = original_langsmith
|
|
133
|
+
|
|
134
|
+
return original_langsmith
|