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.
Files changed (82) hide show
  1. uipath_langchain/_cli/_templates/main.py.template +12 -13
  2. uipath_langchain/_cli/cli_init.py +127 -156
  3. uipath_langchain/_cli/cli_new.py +2 -6
  4. uipath_langchain/_resources/AGENTS.md +21 -0
  5. uipath_langchain/_resources/REQUIRED_STRUCTURE.md +92 -0
  6. uipath_langchain/{tracers → _tracing}/__init__.py +0 -2
  7. uipath_langchain/_tracing/_instrument_traceable.py +134 -0
  8. uipath_langchain/_utils/__init__.py +1 -2
  9. uipath_langchain/_utils/_request_mixin.py +351 -54
  10. uipath_langchain/_utils/_settings.py +2 -11
  11. uipath_langchain/agent/exceptions/__init__.py +6 -0
  12. uipath_langchain/agent/exceptions/exceptions.py +11 -0
  13. uipath_langchain/agent/guardrails/__init__.py +21 -0
  14. uipath_langchain/agent/guardrails/actions/__init__.py +11 -0
  15. uipath_langchain/agent/guardrails/actions/base_action.py +23 -0
  16. uipath_langchain/agent/guardrails/actions/block_action.py +41 -0
  17. uipath_langchain/agent/guardrails/actions/escalate_action.py +274 -0
  18. uipath_langchain/agent/guardrails/actions/log_action.py +57 -0
  19. uipath_langchain/agent/guardrails/guardrail_nodes.py +125 -0
  20. uipath_langchain/agent/guardrails/guardrails_factory.py +70 -0
  21. uipath_langchain/agent/guardrails/guardrails_subgraph.py +247 -0
  22. uipath_langchain/agent/guardrails/types.py +20 -0
  23. uipath_langchain/agent/react/__init__.py +14 -0
  24. uipath_langchain/agent/react/agent.py +113 -0
  25. uipath_langchain/agent/react/constants.py +2 -0
  26. uipath_langchain/agent/react/init_node.py +20 -0
  27. uipath_langchain/agent/react/llm_node.py +43 -0
  28. uipath_langchain/agent/react/router.py +97 -0
  29. uipath_langchain/agent/react/terminate_node.py +82 -0
  30. uipath_langchain/agent/react/tools/__init__.py +7 -0
  31. uipath_langchain/agent/react/tools/tools.py +50 -0
  32. uipath_langchain/agent/react/types.py +39 -0
  33. uipath_langchain/agent/react/utils.py +49 -0
  34. uipath_langchain/agent/tools/__init__.py +17 -0
  35. uipath_langchain/agent/tools/context_tool.py +53 -0
  36. uipath_langchain/agent/tools/escalation_tool.py +111 -0
  37. uipath_langchain/agent/tools/integration_tool.py +181 -0
  38. uipath_langchain/agent/tools/process_tool.py +49 -0
  39. uipath_langchain/agent/tools/static_args.py +138 -0
  40. uipath_langchain/agent/tools/structured_tool_with_output_type.py +14 -0
  41. uipath_langchain/agent/tools/tool_factory.py +45 -0
  42. uipath_langchain/agent/tools/tool_node.py +22 -0
  43. uipath_langchain/agent/tools/utils.py +11 -0
  44. uipath_langchain/chat/__init__.py +4 -0
  45. uipath_langchain/chat/bedrock.py +187 -0
  46. uipath_langchain/chat/gemini.py +330 -0
  47. uipath_langchain/chat/mapper.py +309 -0
  48. uipath_langchain/chat/models.py +261 -38
  49. uipath_langchain/chat/openai.py +132 -0
  50. uipath_langchain/chat/supported_models.py +42 -0
  51. uipath_langchain/embeddings/embeddings.py +136 -36
  52. uipath_langchain/middlewares.py +0 -2
  53. uipath_langchain/py.typed +0 -0
  54. uipath_langchain/retrievers/context_grounding_retriever.py +7 -9
  55. uipath_langchain/runtime/__init__.py +36 -0
  56. uipath_langchain/runtime/_serialize.py +46 -0
  57. uipath_langchain/runtime/config.py +61 -0
  58. uipath_langchain/runtime/errors.py +43 -0
  59. uipath_langchain/runtime/factory.py +315 -0
  60. uipath_langchain/runtime/graph.py +159 -0
  61. uipath_langchain/runtime/runtime.py +453 -0
  62. uipath_langchain/runtime/schema.py +349 -0
  63. uipath_langchain/runtime/storage.py +115 -0
  64. uipath_langchain/vectorstores/context_grounding_vectorstore.py +90 -110
  65. {uipath_langchain-0.0.112.dist-info → uipath_langchain-0.1.24.dist-info}/METADATA +42 -20
  66. uipath_langchain-0.1.24.dist-info/RECORD +76 -0
  67. {uipath_langchain-0.0.112.dist-info → uipath_langchain-0.1.24.dist-info}/WHEEL +1 -1
  68. uipath_langchain-0.1.24.dist-info/entry_points.txt +5 -0
  69. uipath_langchain/_cli/_runtime/_context.py +0 -21
  70. uipath_langchain/_cli/_runtime/_exception.py +0 -17
  71. uipath_langchain/_cli/_runtime/_input.py +0 -136
  72. uipath_langchain/_cli/_runtime/_output.py +0 -234
  73. uipath_langchain/_cli/_runtime/_runtime.py +0 -371
  74. uipath_langchain/_cli/_utils/_graph.py +0 -202
  75. uipath_langchain/_cli/cli_run.py +0 -80
  76. uipath_langchain/tracers/AsyncUiPathTracer.py +0 -274
  77. uipath_langchain/tracers/_events.py +0 -33
  78. uipath_langchain/tracers/_instrument_traceable.py +0 -416
  79. uipath_langchain/tracers/_utils.py +0 -52
  80. uipath_langchain-0.0.112.dist-info/RECORD +0 -36
  81. uipath_langchain-0.0.112.dist-info/entry_points.txt +0 -2
  82. {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
- class GraphInput(BaseModel):
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 llm_model.ainvoke([SystemMessage(system_prompt), HumanMessage(state.topic)])
19
+ output = await llm.ainvoke(
20
+ [SystemMessage(system_prompt), HumanMessage(state.topic)]
21
+ )
24
22
  return GraphOutput(report=output.content)
25
23
 
26
- builder = StateGraph(input=GraphInput, output=GraphOutput)
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 json
2
+ import importlib.resources
3
3
  import os
4
- import uuid
5
- from typing import Any, Dict
4
+ import shutil
5
+ from collections.abc import Generator
6
+ from enum import Enum
7
+ from typing import Any
6
8
 
7
- from langgraph.graph.state import CompiledStateGraph
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 ._utils._graph import LangGraphConfig
13
+ from uipath_langchain.runtime.config import LangGraphConfig
13
14
 
14
15
  console = ConsoleLogger()
15
16
 
16
17
 
17
- def resolve_refs(schema, root=None):
18
- """Recursively resolves $ref references in a JSON schema."""
19
- if root is None:
20
- root = schema # Store the root schema to resolve $refs
21
-
22
- if isinstance(schema, dict):
23
- if "$ref" in schema:
24
- ref_path = schema["$ref"].lstrip("#/").split("/")
25
- ref_schema = root
26
- for part in ref_path:
27
- ref_schema = ref_schema.get(part, {})
28
- return resolve_refs(ref_schema, root)
29
-
30
- return {k: resolve_refs(v, root) for k, v in schema.items()}
31
-
32
- elif isinstance(schema, list):
33
- return [resolve_refs(item, root) for item in schema]
34
-
35
- return schema
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
- def process_nullable_types(
39
- schema: Dict[str, Any] | list[Any] | Any,
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 {k: process_nullable_types(v) for k, v in schema.items()}
52
- elif isinstance(schema, list):
53
- return [process_nullable_types(item) for item in schema]
54
- return schema
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
- if hasattr(graph, "input_schema"):
65
- if hasattr(graph.input_schema, "model_json_schema"):
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
- # Process the schema to handle nullable types
70
- processed_properties = process_nullable_types(
71
- unpacked_ref_def_properties.get("properties", {})
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
- schema["input"]["properties"] = processed_properties
75
- schema["input"]["required"] = unpacked_ref_def_properties.get(
76
- "required", []
77
- )
113
+ created_files = []
114
+ updated_files = []
115
+ skipped_files = []
78
116
 
79
- if hasattr(graph, "output_schema"):
80
- if hasattr(graph.output_schema, "model_json_schema"):
81
- output_schema = graph.output_schema.model_json_schema()
82
- unpacked_ref_def_properties = resolve_refs(output_schema)
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
- # Process the schema to handle nullable types
85
- processed_properties = process_nullable_types(
86
- unpacked_ref_def_properties.get("properties", {})
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
- schema["output"]["properties"] = processed_properties
90
- schema["output"]["required"] = unpacked_ref_def_properties.get(
91
- "required", []
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
- return schema
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(entrypoint: str) -> MiddlewareResult:
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
- config.load_config()
107
- entrypoints = []
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(entrypoint: str) -> MiddlewareResult:
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(entrypoint))
169
+ return asyncio.run(langgraph_init_middleware_async(options))
@@ -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.106",
35
- "langchain-anthropic>=0.3.8",
34
+ "uipath-langchain>=0.1.0",
36
35
  ]
37
- requires-python = ">=3.10"
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()`
@@ -1,7 +1,5 @@
1
1
  from ._instrument_traceable import _instrument_traceable_attributes
2
- from .AsyncUiPathTracer import AsyncUiPathTracer
3
2
 
4
3
  __all__ = [
5
- "AsyncUiPathTracer",
6
4
  "_instrument_traceable_attributes",
7
5
  ]
@@ -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
@@ -1,4 +1,3 @@
1
- from ..tracers._instrument_traceable import _instrument_traceable_attributes
2
1
  from ._request_mixin import UiPathRequestMixin
3
2
 
4
- __all__ = ["UiPathRequestMixin", "_instrument_traceable_attributes"]
3
+ __all__ = ["UiPathRequestMixin"]