uipath-langchain 0.0.141__py3-none-any.whl → 0.0.143__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.
Potentially problematic release.
This version of uipath-langchain might be problematic. Click here for more details.
- uipath_langchain/_cli/cli_debug.py +5 -2
- uipath_langchain/_cli/cli_eval.py +5 -3
- uipath_langchain/_cli/cli_init.py +134 -2
- uipath_langchain/_cli/cli_run.py +15 -3
- uipath_langchain/_resources/AGENTS.md +21 -0
- uipath_langchain/_resources/REQUIRED_STRUCTURE.md +92 -0
- uipath_langchain/_tracing/__init__.py +3 -2
- {uipath_langchain-0.0.141.dist-info → uipath_langchain-0.0.143.dist-info}/METADATA +2 -2
- {uipath_langchain-0.0.141.dist-info → uipath_langchain-0.0.143.dist-info}/RECORD +12 -11
- uipath_langchain/_tracing/_oteladapter.py +0 -234
- {uipath_langchain-0.0.141.dist-info → uipath_langchain-0.0.143.dist-info}/WHEEL +0 -0
- {uipath_langchain-0.0.141.dist-info → uipath_langchain-0.0.143.dist-info}/entry_points.txt +0 -0
- {uipath_langchain-0.0.141.dist-info → uipath_langchain-0.0.143.dist-info}/licenses/LICENSE +0 -0
|
@@ -12,8 +12,9 @@ from uipath._cli._runtime._contracts import (
|
|
|
12
12
|
UiPathRuntimeFactory,
|
|
13
13
|
)
|
|
14
14
|
from uipath._cli.middlewares import MiddlewareResult
|
|
15
|
+
from uipath.tracing import LlmOpsHttpExporter
|
|
15
16
|
|
|
16
|
-
from .._tracing import
|
|
17
|
+
from .._tracing import _instrument_traceable_attributes
|
|
17
18
|
from ._runtime._exception import LangGraphRuntimeError
|
|
18
19
|
from ._runtime._runtime import ( # type: ignore[attr-defined]
|
|
19
20
|
LangGraphRuntimeContext,
|
|
@@ -61,7 +62,9 @@ def langgraph_debug_middleware(
|
|
|
61
62
|
)
|
|
62
63
|
|
|
63
64
|
if context.job_id:
|
|
64
|
-
runtime_factory.add_span_exporter(
|
|
65
|
+
runtime_factory.add_span_exporter(
|
|
66
|
+
LlmOpsHttpExporter(extra_process_spans=True)
|
|
67
|
+
)
|
|
65
68
|
|
|
66
69
|
runtime_factory.add_instrumentor(LangChainInstrumentor, get_current_span)
|
|
67
70
|
|
|
@@ -15,12 +15,12 @@ from uipath._cli._utils._eval_set import EvalHelpers
|
|
|
15
15
|
from uipath._cli.middlewares import MiddlewareResult
|
|
16
16
|
from uipath._events._event_bus import EventBus
|
|
17
17
|
from uipath.eval._helpers import auto_discover_entrypoint
|
|
18
|
+
from uipath.tracing import LlmOpsHttpExporter
|
|
18
19
|
|
|
19
20
|
from uipath_langchain._cli._runtime._context import LangGraphRuntimeContext
|
|
20
21
|
from uipath_langchain._cli._runtime._runtime import LangGraphScriptRuntime
|
|
21
22
|
from uipath_langchain._cli._utils._graph import LangGraphConfig
|
|
22
23
|
from uipath_langchain._tracing import (
|
|
23
|
-
LangChainExporter,
|
|
24
24
|
_instrument_traceable_attributes,
|
|
25
25
|
)
|
|
26
26
|
|
|
@@ -41,7 +41,7 @@ def langgraph_eval_middleware(
|
|
|
41
41
|
|
|
42
42
|
if kwargs.get("register_progress_reporter", False):
|
|
43
43
|
progress_reporter = StudioWebProgressReporter(
|
|
44
|
-
spans_exporter=
|
|
44
|
+
spans_exporter=LlmOpsHttpExporter(extra_process_spans=True)
|
|
45
45
|
)
|
|
46
46
|
asyncio.run(progress_reporter.subscribe_to_eval_runtime_events(event_bus))
|
|
47
47
|
console_reporter = ConsoleProgressReporter()
|
|
@@ -76,7 +76,9 @@ def langgraph_eval_middleware(
|
|
|
76
76
|
)
|
|
77
77
|
|
|
78
78
|
if eval_context.job_id:
|
|
79
|
-
runtime_factory.add_span_exporter(
|
|
79
|
+
runtime_factory.add_span_exporter(
|
|
80
|
+
LlmOpsHttpExporter(extra_process_spans=True)
|
|
81
|
+
)
|
|
80
82
|
|
|
81
83
|
runtime_factory.add_instrumentor(LangChainInstrumentor, get_current_span)
|
|
82
84
|
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
import importlib.resources
|
|
2
3
|
import json
|
|
3
4
|
import os
|
|
5
|
+
import shutil
|
|
4
6
|
import uuid
|
|
7
|
+
from collections.abc import Generator
|
|
8
|
+
from enum import Enum
|
|
5
9
|
from typing import Any, Callable, Dict, overload
|
|
6
10
|
|
|
11
|
+
import click
|
|
7
12
|
from langgraph.graph.state import CompiledStateGraph
|
|
8
13
|
from uipath._cli._utils._console import ConsoleLogger
|
|
9
14
|
from uipath._cli._utils._parse_ast import generate_bindings_json # type: ignore
|
|
@@ -14,6 +19,14 @@ from ._utils._graph import LangGraphConfig
|
|
|
14
19
|
console = ConsoleLogger()
|
|
15
20
|
|
|
16
21
|
|
|
22
|
+
class FileOperationStatus(str, Enum):
|
|
23
|
+
"""Status of a file operation."""
|
|
24
|
+
|
|
25
|
+
CREATED = "created"
|
|
26
|
+
UPDATED = "updated"
|
|
27
|
+
SKIPPED = "skipped"
|
|
28
|
+
|
|
29
|
+
|
|
17
30
|
def resolve_refs(schema, root=None):
|
|
18
31
|
"""Recursively resolves $ref references in a JSON schema."""
|
|
19
32
|
if root is None:
|
|
@@ -96,6 +109,120 @@ def generate_schema_from_graph(
|
|
|
96
109
|
return schema
|
|
97
110
|
|
|
98
111
|
|
|
112
|
+
def generate_agent_md_file(
|
|
113
|
+
target_directory: str,
|
|
114
|
+
file_name: str,
|
|
115
|
+
resource_name: str,
|
|
116
|
+
no_agents_md_override: bool,
|
|
117
|
+
) -> tuple[str, FileOperationStatus] | None:
|
|
118
|
+
"""Generate an agent-specific file from the packaged resource.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
target_directory: The directory where the file should be created.
|
|
122
|
+
file_name: The name of the file should be created.
|
|
123
|
+
resource_name: The name of the resource folder where should be the file.
|
|
124
|
+
no_agents_md_override: Whether to override existing files.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
A tuple of (file_name, status) where status is a FileOperationStatus:
|
|
128
|
+
- CREATED: File was created
|
|
129
|
+
- UPDATED: File was overwritten
|
|
130
|
+
- SKIPPED: File exists and no_agents_md_override is True
|
|
131
|
+
Returns None if an error occurred.
|
|
132
|
+
"""
|
|
133
|
+
target_path = os.path.join(target_directory, file_name)
|
|
134
|
+
will_override = os.path.exists(target_path)
|
|
135
|
+
|
|
136
|
+
if will_override and no_agents_md_override:
|
|
137
|
+
return file_name, FileOperationStatus.SKIPPED
|
|
138
|
+
try:
|
|
139
|
+
source_path = importlib.resources.files(resource_name).joinpath(file_name)
|
|
140
|
+
|
|
141
|
+
with importlib.resources.as_file(source_path) as s_path:
|
|
142
|
+
shutil.copy(s_path, target_path)
|
|
143
|
+
|
|
144
|
+
return (
|
|
145
|
+
file_name,
|
|
146
|
+
FileOperationStatus.UPDATED
|
|
147
|
+
if will_override
|
|
148
|
+
else FileOperationStatus.CREATED,
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
except Exception as e:
|
|
152
|
+
console.warning(f"Could not create {file_name}: {e}")
|
|
153
|
+
return None
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def generate_specific_agents_md_files(
|
|
157
|
+
target_directory: str, no_agents_md_override: bool
|
|
158
|
+
) -> Generator[tuple[str, FileOperationStatus], None, None]:
|
|
159
|
+
"""Generate agent-specific files from the packaged resource.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
target_directory: The directory where the files should be created.
|
|
163
|
+
no_agents_md_override: Whether to override existing files.
|
|
164
|
+
|
|
165
|
+
Yields:
|
|
166
|
+
Tuple of (file_name, status) for each file operation, where status is a FileOperationStatus:
|
|
167
|
+
- CREATED: File was created
|
|
168
|
+
- UPDATED: File was overwritten
|
|
169
|
+
- SKIPPED: File exists and was not overwritten
|
|
170
|
+
"""
|
|
171
|
+
agent_dir = os.path.join(target_directory, ".agent")
|
|
172
|
+
os.makedirs(agent_dir, exist_ok=True)
|
|
173
|
+
|
|
174
|
+
file_configs = [
|
|
175
|
+
(target_directory, "CLAUDE.md", "uipath._resources"),
|
|
176
|
+
(agent_dir, "CLI_REFERENCE.md", "uipath._resources"),
|
|
177
|
+
(agent_dir, "SDK_REFERENCE.md", "uipath._resources"),
|
|
178
|
+
(target_directory, "AGENTS.md", "uipath_langchain._resources"),
|
|
179
|
+
(agent_dir, "REQUIRED_STRUCTURE.md", "uipath_langchain._resources"),
|
|
180
|
+
]
|
|
181
|
+
|
|
182
|
+
for directory, file_name, resource_name in file_configs:
|
|
183
|
+
result = generate_agent_md_file(
|
|
184
|
+
directory, file_name, resource_name, no_agents_md_override
|
|
185
|
+
)
|
|
186
|
+
if result:
|
|
187
|
+
yield result
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def generate_agents_md_files(options: dict[str, Any]) -> None:
|
|
191
|
+
"""Generate agent MD files and log categorized summary.
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
options: Options dictionary
|
|
195
|
+
"""
|
|
196
|
+
current_directory = os.getcwd()
|
|
197
|
+
no_agents_md_override = options.get("no_agents_md_override", False)
|
|
198
|
+
|
|
199
|
+
created_files = []
|
|
200
|
+
updated_files = []
|
|
201
|
+
skipped_files = []
|
|
202
|
+
|
|
203
|
+
for file_name, status in generate_specific_agents_md_files(
|
|
204
|
+
current_directory, no_agents_md_override
|
|
205
|
+
):
|
|
206
|
+
if status == FileOperationStatus.CREATED:
|
|
207
|
+
created_files.append(file_name)
|
|
208
|
+
elif status == FileOperationStatus.UPDATED:
|
|
209
|
+
updated_files.append(file_name)
|
|
210
|
+
elif status == FileOperationStatus.SKIPPED:
|
|
211
|
+
skipped_files.append(file_name)
|
|
212
|
+
|
|
213
|
+
if created_files:
|
|
214
|
+
files_str = ", ".join(click.style(f, fg="cyan") for f in created_files)
|
|
215
|
+
console.success(f"Created: {files_str}")
|
|
216
|
+
|
|
217
|
+
if updated_files:
|
|
218
|
+
files_str = ", ".join(click.style(f, fg="cyan") for f in updated_files)
|
|
219
|
+
console.success(f"Updated: {files_str}")
|
|
220
|
+
|
|
221
|
+
if skipped_files:
|
|
222
|
+
files_str = ", ".join(click.style(f, fg="yellow") for f in skipped_files)
|
|
223
|
+
console.info(f"Skipped (already exist): {files_str}")
|
|
224
|
+
|
|
225
|
+
|
|
99
226
|
async def langgraph_init_middleware_async(
|
|
100
227
|
entrypoint: str,
|
|
101
228
|
options: dict[str, Any] | None = None,
|
|
@@ -185,7 +312,9 @@ async def langgraph_init_middleware_async(
|
|
|
185
312
|
try:
|
|
186
313
|
with open(mermaid_file_path, "w") as f:
|
|
187
314
|
f.write(mermaid_content)
|
|
188
|
-
console.success(
|
|
315
|
+
console.success(
|
|
316
|
+
f"Created {click.style(mermaid_file_path, fg='cyan')} file."
|
|
317
|
+
)
|
|
189
318
|
except Exception as write_error:
|
|
190
319
|
console.error(
|
|
191
320
|
f"Error writing mermaid file for '{graph_name}': {str(write_error)}"
|
|
@@ -194,7 +323,10 @@ async def langgraph_init_middleware_async(
|
|
|
194
323
|
should_continue=False,
|
|
195
324
|
should_include_stacktrace=True,
|
|
196
325
|
)
|
|
197
|
-
console.success(f"
|
|
326
|
+
console.success(f"Created {click.style(config_path, fg='cyan')} file.")
|
|
327
|
+
|
|
328
|
+
generate_agents_md_files(options)
|
|
329
|
+
|
|
198
330
|
return MiddlewareResult(should_continue=False)
|
|
199
331
|
|
|
200
332
|
except Exception as e:
|
uipath_langchain/_cli/cli_run.py
CHANGED
|
@@ -13,8 +13,11 @@ from uipath._cli._runtime._contracts import (
|
|
|
13
13
|
)
|
|
14
14
|
from uipath._cli.middlewares import MiddlewareResult
|
|
15
15
|
from uipath._events._events import UiPathAgentStateEvent
|
|
16
|
+
from uipath.tracing import JsonLinesFileExporter, LlmOpsHttpExporter
|
|
16
17
|
|
|
17
|
-
from .._tracing import
|
|
18
|
+
from .._tracing import (
|
|
19
|
+
_instrument_traceable_attributes,
|
|
20
|
+
)
|
|
18
21
|
from ._runtime._exception import LangGraphRuntimeError
|
|
19
22
|
from ._runtime._runtime import ( # type: ignore[attr-defined]
|
|
20
23
|
LangGraphRuntimeContext,
|
|
@@ -24,7 +27,11 @@ from ._utils._graph import LangGraphConfig
|
|
|
24
27
|
|
|
25
28
|
|
|
26
29
|
def langgraph_run_middleware(
|
|
27
|
-
entrypoint: Optional[str],
|
|
30
|
+
entrypoint: Optional[str],
|
|
31
|
+
input: Optional[str],
|
|
32
|
+
resume: bool,
|
|
33
|
+
trace_file: Optional[str] = None,
|
|
34
|
+
**kwargs,
|
|
28
35
|
) -> MiddlewareResult:
|
|
29
36
|
"""Middleware to handle LangGraph execution"""
|
|
30
37
|
config = LangGraphConfig()
|
|
@@ -61,8 +68,13 @@ def langgraph_run_middleware(
|
|
|
61
68
|
|
|
62
69
|
runtime_factory.add_instrumentor(LangChainInstrumentor, get_current_span)
|
|
63
70
|
|
|
71
|
+
if trace_file:
|
|
72
|
+
runtime_factory.add_span_exporter(JsonLinesFileExporter(trace_file))
|
|
73
|
+
|
|
64
74
|
if context.job_id:
|
|
65
|
-
runtime_factory.add_span_exporter(
|
|
75
|
+
runtime_factory.add_span_exporter(
|
|
76
|
+
LlmOpsHttpExporter(extra_process_spans=True)
|
|
77
|
+
)
|
|
66
78
|
await runtime_factory.execute(context)
|
|
67
79
|
else:
|
|
68
80
|
debug_bridge: UiPathDebugBridge = ConsoleDebugBridge()
|
|
@@ -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,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: uipath-langchain
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.143
|
|
4
4
|
Summary: UiPath Langchain
|
|
5
5
|
Project-URL: Homepage, https://uipath.com
|
|
6
6
|
Project-URL: Repository, https://github.com/UiPath/uipath-langchain-python
|
|
@@ -26,7 +26,7 @@ Requires-Dist: openai>=1.65.5
|
|
|
26
26
|
Requires-Dist: openinference-instrumentation-langchain>=0.1.50
|
|
27
27
|
Requires-Dist: pydantic-settings>=2.6.0
|
|
28
28
|
Requires-Dist: python-dotenv>=1.0.1
|
|
29
|
-
Requires-Dist: uipath<2.2.0,>=2.1.
|
|
29
|
+
Requires-Dist: uipath<2.2.0,>=2.1.103
|
|
30
30
|
Provides-Extra: langchain
|
|
31
31
|
Description-Content-Type: text/markdown
|
|
32
32
|
|
|
@@ -2,12 +2,12 @@ uipath_langchain/__init__.py,sha256=VBrvQn7d3nuOdN7zEnV2_S-uhmkjgEIlXiFVeZxZakQ,
|
|
|
2
2
|
uipath_langchain/middlewares.py,sha256=x3U_tmDIyMXPLzq6n-oNRAnpAF6pKa9wfkPYwE-oUfo,848
|
|
3
3
|
uipath_langchain/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
4
|
uipath_langchain/_cli/__init__.py,sha256=juqd9PbXs4yg45zMJ7BHAOPQjb7sgEbWE9InBtGZhfo,24
|
|
5
|
-
uipath_langchain/_cli/cli_debug.py,sha256=
|
|
5
|
+
uipath_langchain/_cli/cli_debug.py,sha256=zaB-W3_29FsCqF-YZ3EsayyxC957tg4tOjdcdX8ew-M,3311
|
|
6
6
|
uipath_langchain/_cli/cli_dev.py,sha256=l3XFHrh-0OUFJq3zLMKuzedJAluGQBIZQTHP1KWOmpw,1725
|
|
7
|
-
uipath_langchain/_cli/cli_eval.py,sha256=
|
|
8
|
-
uipath_langchain/_cli/cli_init.py,sha256=
|
|
7
|
+
uipath_langchain/_cli/cli_eval.py,sha256=yzxOz-JOMMl1fejZNVQYlBSo-yUIxArtfp2EW1Ow6j4,3753
|
|
8
|
+
uipath_langchain/_cli/cli_init.py,sha256=B-Ht1lz4HNlpYELZU7DLNhSrhGJbsaCdU9UMO2iHUgM,12654
|
|
9
9
|
uipath_langchain/_cli/cli_new.py,sha256=KKLxCzz7cDQ__rRr_a496IHWlSQXhmrBNgmKHnXAnTY,2336
|
|
10
|
-
uipath_langchain/_cli/cli_run.py,sha256
|
|
10
|
+
uipath_langchain/_cli/cli_run.py,sha256=DIsAKsbQ8gTRz44q9ZV3jBjrbM8bhS6lEQ3dd4joDFU,3712
|
|
11
11
|
uipath_langchain/_cli/_runtime/_context.py,sha256=mjmGEogKiO8tUV878BgV9rFIeA9MCmEH6hgs5W_dm4g,328
|
|
12
12
|
uipath_langchain/_cli/_runtime/_conversation.py,sha256=ayghRqhyLeVUZg1WHnpeOYtPNhRwDOl4z8OSYiJkWSU,11529
|
|
13
13
|
uipath_langchain/_cli/_runtime/_exception.py,sha256=USKkLYkG-dzjX3fEiMMOHnVUpiXJs_xF0OQXCCOvbYM,546
|
|
@@ -18,9 +18,10 @@ uipath_langchain/_cli/_runtime/_runtime.py,sha256=ImDBluxga_WbIm9lnljjm7jGl1tZBP
|
|
|
18
18
|
uipath_langchain/_cli/_templates/langgraph.json.template,sha256=eeh391Gta_hoRgaNaZ58nW1LNvCVXA7hlAH6l7Veous,107
|
|
19
19
|
uipath_langchain/_cli/_templates/main.py.template,sha256=GpSblGH2hwS9ibqQmX2iB2nsmOA5zDfEEF4ChLiMxbQ,875
|
|
20
20
|
uipath_langchain/_cli/_utils/_graph.py,sha256=nMJWy8FmaD9rqPUY2lHc5uVpUzbXD1RO12uJnhe0kdo,6803
|
|
21
|
-
uipath_langchain/
|
|
21
|
+
uipath_langchain/_resources/AGENTS.md,sha256=5VmIfaQ6H91VxInnxFmJklURXeWIIQpGQTYBEmvvoVA,1060
|
|
22
|
+
uipath_langchain/_resources/REQUIRED_STRUCTURE.md,sha256=BRmWWFtM0qNXj5uumALVxq9h6pifJDGh5NzuyctuH1Q,2569
|
|
23
|
+
uipath_langchain/_tracing/__init__.py,sha256=C2dRvQ2ynxCmyICgE-rJHimWKEcFRME_o9gfX84Mb3Y,123
|
|
22
24
|
uipath_langchain/_tracing/_instrument_traceable.py,sha256=8f9FyAKWE6kH1N8ErbpwqZHAzNjGwbLjQn7jdX5yAgA,4343
|
|
23
|
-
uipath_langchain/_tracing/_oteladapter.py,sha256=PD0gsC39ZNvrm0gsfnt1ti6DEy56sBA9sIoxaAbHFFM,8887
|
|
24
25
|
uipath_langchain/_tracing/_utils.py,sha256=r_fiSk3HDDAcePY_UbbEYiSbNqzn5gFeMPYBDvGrFx0,902
|
|
25
26
|
uipath_langchain/_utils/__init__.py,sha256=-w-4TD9ZnJDCpj4VIPXhJciukrmDJJbmnOFnhAkAaEU,81
|
|
26
27
|
uipath_langchain/_utils/_request_mixin.py,sha256=sYvvn3_fUJxtF893xFpVGwJx2YoEbw1m5gp_U_lWjR8,20092
|
|
@@ -36,8 +37,8 @@ uipath_langchain/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3
|
|
|
36
37
|
uipath_langchain/tools/preconfigured.py,sha256=SyvrLrM1kezZxVVytgScVO8nBfVYfFGobWjY7erzsYU,7490
|
|
37
38
|
uipath_langchain/vectorstores/__init__.py,sha256=w8qs1P548ud1aIcVA_QhBgf_jZDrRMK5Lono78yA8cs,114
|
|
38
39
|
uipath_langchain/vectorstores/context_grounding_vectorstore.py,sha256=TncIXG-YsUlO0R5ZYzWsM-Dj1SVCZbzmo2LraVxXelc,9559
|
|
39
|
-
uipath_langchain-0.0.
|
|
40
|
-
uipath_langchain-0.0.
|
|
41
|
-
uipath_langchain-0.0.
|
|
42
|
-
uipath_langchain-0.0.
|
|
43
|
-
uipath_langchain-0.0.
|
|
40
|
+
uipath_langchain-0.0.143.dist-info/METADATA,sha256=lh7djtB3vKXf2xB6b8BT9IksQGlun2fj2UiNFIver-E,4276
|
|
41
|
+
uipath_langchain-0.0.143.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
42
|
+
uipath_langchain-0.0.143.dist-info/entry_points.txt,sha256=FUtzqGOEntlJKMJIXhQUfT7ZTbQmGhke1iCmDWZaQZI,81
|
|
43
|
+
uipath_langchain-0.0.143.dist-info/licenses/LICENSE,sha256=JDpt-uotAkHFmxpwxi6gwx6HQ25e-lG4U_Gzcvgp7JY,1063
|
|
44
|
+
uipath_langchain-0.0.143.dist-info/RECORD,,
|
|
@@ -1,234 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
import logging
|
|
3
|
-
from typing import Any, Dict, List, Optional
|
|
4
|
-
|
|
5
|
-
from opentelemetry.sdk.trace.export import SpanExportResult
|
|
6
|
-
from uipath.tracing import LlmOpsHttpExporter
|
|
7
|
-
|
|
8
|
-
logger = logging.getLogger(__name__)
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def _safe_parse_json(s: Any) -> Any:
|
|
12
|
-
"""Safely parse a JSON string, returning the original if not a string or on error."""
|
|
13
|
-
if not isinstance(s, str):
|
|
14
|
-
return s
|
|
15
|
-
try:
|
|
16
|
-
return json.loads(s)
|
|
17
|
-
except (json.JSONDecodeError, TypeError):
|
|
18
|
-
return s
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def _get_llm_messages(attributes: Dict[str, Any], prefix: str) -> List[Dict[str, Any]]:
|
|
22
|
-
"""Extracts and reconstructs LLM messages from flattened attributes."""
|
|
23
|
-
messages: dict[int, dict[str, Any]] = {}
|
|
24
|
-
message_prefix = f"{prefix}."
|
|
25
|
-
|
|
26
|
-
for key, value in attributes.items():
|
|
27
|
-
if key.startswith(message_prefix):
|
|
28
|
-
parts = key[len(message_prefix) :].split(".")
|
|
29
|
-
if len(parts) >= 2 and parts[0].isdigit():
|
|
30
|
-
index = int(parts[0])
|
|
31
|
-
if index not in messages:
|
|
32
|
-
messages[index] = {}
|
|
33
|
-
current: Any = messages[index]
|
|
34
|
-
|
|
35
|
-
for i, part in enumerate(parts[1:-1]):
|
|
36
|
-
key_part: str | int = part
|
|
37
|
-
if part.isdigit() and (
|
|
38
|
-
i + 2 < len(parts) and parts[i + 2].isdigit()
|
|
39
|
-
):
|
|
40
|
-
key_part = int(part)
|
|
41
|
-
|
|
42
|
-
if isinstance(current, dict):
|
|
43
|
-
if key_part not in current:
|
|
44
|
-
current[key_part] = {}
|
|
45
|
-
current = current[key_part]
|
|
46
|
-
elif isinstance(current, list) and isinstance(key_part, int):
|
|
47
|
-
if key_part >= len(current):
|
|
48
|
-
current.append({})
|
|
49
|
-
current = current[key_part]
|
|
50
|
-
|
|
51
|
-
current[parts[-1]] = value
|
|
52
|
-
|
|
53
|
-
# Convert dict to list, ordered by index
|
|
54
|
-
return [messages[i] for i in sorted(messages.keys())]
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
class LangChainExporter(LlmOpsHttpExporter):
|
|
58
|
-
# Mapping of old attribute names to new attribute names or (new name, function)
|
|
59
|
-
ATTRIBUTE_MAPPING: dict[str, str | tuple[str, Any]] = {
|
|
60
|
-
"input.value": ("input", _safe_parse_json),
|
|
61
|
-
"output.value": ("output", _safe_parse_json),
|
|
62
|
-
"llm.model_name": "model",
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
# Mapping of span types
|
|
66
|
-
SPAN_TYPE_MAPPING: dict[str, str] = {
|
|
67
|
-
"LLM": "completion",
|
|
68
|
-
"TOOL": "toolCall",
|
|
69
|
-
# Add more mappings as needed
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
class Status:
|
|
73
|
-
SUCCESS = 1
|
|
74
|
-
ERROR = 2
|
|
75
|
-
INTERRUPTED = 3
|
|
76
|
-
|
|
77
|
-
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
78
|
-
super().__init__(*args, **kwargs)
|
|
79
|
-
|
|
80
|
-
def _map_llm_call_attributes(self, attributes: Dict[str, Any]) -> Dict[str, Any]:
|
|
81
|
-
"""Maps attributes for LLM calls, handling flattened keys."""
|
|
82
|
-
result = attributes.copy() # Keep original attributes including basic mappings
|
|
83
|
-
|
|
84
|
-
# Token Usage
|
|
85
|
-
token_keys = {
|
|
86
|
-
"llm.token_count.prompt": "promptTokens",
|
|
87
|
-
"llm.token_count.completion": "completionTokens",
|
|
88
|
-
"llm.token_count.total": "totalTokens",
|
|
89
|
-
}
|
|
90
|
-
usage = {
|
|
91
|
-
new_key: attributes.get(old_key)
|
|
92
|
-
for old_key, new_key in token_keys.items()
|
|
93
|
-
if old_key in attributes
|
|
94
|
-
}
|
|
95
|
-
if usage:
|
|
96
|
-
result["usage"] = usage
|
|
97
|
-
|
|
98
|
-
# Input/Output Messages
|
|
99
|
-
result["input"] = _get_llm_messages(attributes, "llm.input_messages")
|
|
100
|
-
output_messages = _get_llm_messages(attributes, "llm.output_messages")
|
|
101
|
-
result["output"] = output_messages
|
|
102
|
-
|
|
103
|
-
# Invocation Parameters
|
|
104
|
-
invocation_params = _safe_parse_json(
|
|
105
|
-
attributes.get("llm.invocation_parameters", "{}")
|
|
106
|
-
)
|
|
107
|
-
if isinstance(invocation_params, dict):
|
|
108
|
-
result["model"] = invocation_params.get("model", result.get("model"))
|
|
109
|
-
settings: dict[str, Any] = {}
|
|
110
|
-
if "max_tokens" in invocation_params:
|
|
111
|
-
settings["maxTokens"] = invocation_params["max_tokens"]
|
|
112
|
-
if "temperature" in invocation_params:
|
|
113
|
-
settings["temperature"] = invocation_params["temperature"]
|
|
114
|
-
if settings:
|
|
115
|
-
result["settings"] = settings
|
|
116
|
-
|
|
117
|
-
# Tool Calls
|
|
118
|
-
tool_calls: list[dict[str, Any]] = []
|
|
119
|
-
for msg in output_messages:
|
|
120
|
-
# Ensure msg is a dictionary before proceeding
|
|
121
|
-
if not isinstance(msg, dict):
|
|
122
|
-
continue
|
|
123
|
-
msg_tool_calls = msg.get("message", {}).get("tool_calls", [])
|
|
124
|
-
|
|
125
|
-
# Ensure msg_tool_calls is a list
|
|
126
|
-
if not isinstance(msg_tool_calls, list):
|
|
127
|
-
continue
|
|
128
|
-
|
|
129
|
-
for tc in msg_tool_calls:
|
|
130
|
-
if not isinstance(tc, dict):
|
|
131
|
-
continue
|
|
132
|
-
tool_call_data = tc.get("tool_call", {})
|
|
133
|
-
if not isinstance(tool_call_data, dict):
|
|
134
|
-
continue
|
|
135
|
-
tool_calls.append(
|
|
136
|
-
{
|
|
137
|
-
"id": tool_call_data.get("id"),
|
|
138
|
-
"name": tool_call_data.get("function", {}).get("name"),
|
|
139
|
-
"arguments": _safe_parse_json(
|
|
140
|
-
tool_call_data.get("function", {}).get("arguments", "{}")
|
|
141
|
-
),
|
|
142
|
-
}
|
|
143
|
-
)
|
|
144
|
-
if tool_calls:
|
|
145
|
-
result["toolCalls"] = tool_calls
|
|
146
|
-
|
|
147
|
-
return result
|
|
148
|
-
|
|
149
|
-
def _map_tool_call_attributes(self, attributes: Dict[str, Any]) -> Dict[str, Any]:
|
|
150
|
-
"""Maps attributes for tool calls."""
|
|
151
|
-
result = attributes.copy() # Keep original attributes
|
|
152
|
-
|
|
153
|
-
result["type"] = "toolCall"
|
|
154
|
-
result["callId"] = attributes.get("call_id") or attributes.get("id")
|
|
155
|
-
result["toolName"] = attributes.get("tool.name")
|
|
156
|
-
result["arguments"] = _safe_parse_json(
|
|
157
|
-
attributes.get("input", attributes.get("input.value", "{}"))
|
|
158
|
-
)
|
|
159
|
-
result["toolType"] = "Integration"
|
|
160
|
-
result["result"] = _safe_parse_json(
|
|
161
|
-
attributes.get("output", attributes.get("output.value"))
|
|
162
|
-
)
|
|
163
|
-
result["error"] = None
|
|
164
|
-
|
|
165
|
-
return result
|
|
166
|
-
|
|
167
|
-
def _determine_status(self, error: Optional[str]) -> int:
|
|
168
|
-
if error:
|
|
169
|
-
if error and error.startswith("GraphInterrupt("):
|
|
170
|
-
return self.Status.INTERRUPTED
|
|
171
|
-
return self.Status.ERROR
|
|
172
|
-
return self.Status.SUCCESS
|
|
173
|
-
|
|
174
|
-
def _process_span_attributes(self, span_data: Dict[str, Any]) -> Dict[str, Any]:
|
|
175
|
-
"""Extracts, transforms, and maps attributes for a span."""
|
|
176
|
-
if "Attributes" not in span_data:
|
|
177
|
-
return span_data
|
|
178
|
-
|
|
179
|
-
attributes_val = span_data["Attributes"]
|
|
180
|
-
if isinstance(attributes_val, str):
|
|
181
|
-
try:
|
|
182
|
-
attributes: Dict[str, Any] = json.loads(attributes_val)
|
|
183
|
-
except json.JSONDecodeError as e:
|
|
184
|
-
logger.warning(f"Failed to parse attributes JSON: {e}")
|
|
185
|
-
return span_data
|
|
186
|
-
elif isinstance(attributes_val, dict):
|
|
187
|
-
attributes = attributes_val
|
|
188
|
-
else:
|
|
189
|
-
return span_data
|
|
190
|
-
|
|
191
|
-
# Determine SpanType
|
|
192
|
-
if "openinference.span.kind" in attributes:
|
|
193
|
-
span_type = attributes["openinference.span.kind"]
|
|
194
|
-
span_data["SpanType"] = self.SPAN_TYPE_MAPPING.get(span_type, span_type)
|
|
195
|
-
|
|
196
|
-
# Apply basic attribute mapping
|
|
197
|
-
for old_key, mapping in self.ATTRIBUTE_MAPPING.items():
|
|
198
|
-
if old_key in attributes:
|
|
199
|
-
if isinstance(mapping, tuple):
|
|
200
|
-
new_key, func = mapping
|
|
201
|
-
attributes[new_key] = func(attributes[old_key])
|
|
202
|
-
else:
|
|
203
|
-
new_key = mapping
|
|
204
|
-
attributes[new_key] = attributes[old_key]
|
|
205
|
-
|
|
206
|
-
# Apply detailed mapping based on SpanType
|
|
207
|
-
span_type = span_data.get("SpanType")
|
|
208
|
-
if span_type == "completion":
|
|
209
|
-
processed_attributes = self._map_llm_call_attributes(attributes)
|
|
210
|
-
elif span_type == "toolCall":
|
|
211
|
-
processed_attributes = self._map_tool_call_attributes(attributes)
|
|
212
|
-
else:
|
|
213
|
-
processed_attributes = attributes.copy()
|
|
214
|
-
|
|
215
|
-
span_data["Attributes"] = json.dumps(processed_attributes)
|
|
216
|
-
|
|
217
|
-
# Determine status based on error information
|
|
218
|
-
error = attributes.get("error") or attributes.get("exception.message")
|
|
219
|
-
status = self._determine_status(error)
|
|
220
|
-
span_data["Status"] = status
|
|
221
|
-
|
|
222
|
-
return span_data
|
|
223
|
-
|
|
224
|
-
def _send_with_retries(
|
|
225
|
-
self, url: str, payload: List[Dict[str, Any]], max_retries: int = 4
|
|
226
|
-
) -> SpanExportResult:
|
|
227
|
-
# Transform attributes in each span's payload before sending
|
|
228
|
-
transformed_payload = [self._process_span_attributes(span) for span in payload]
|
|
229
|
-
|
|
230
|
-
return super()._send_with_retries(
|
|
231
|
-
url=url,
|
|
232
|
-
payload=transformed_payload,
|
|
233
|
-
max_retries=max_retries,
|
|
234
|
-
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|