yamlgraph 0.3.9__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.
- examples/__init__.py +1 -0
- examples/codegen/__init__.py +5 -0
- examples/codegen/models/__init__.py +13 -0
- examples/codegen/models/schemas.py +76 -0
- examples/codegen/tests/__init__.py +1 -0
- examples/codegen/tests/test_ai_helpers.py +235 -0
- examples/codegen/tests/test_ast_analysis.py +174 -0
- examples/codegen/tests/test_code_analysis.py +134 -0
- examples/codegen/tests/test_code_context.py +301 -0
- examples/codegen/tests/test_code_nav.py +89 -0
- examples/codegen/tests/test_dependency_tools.py +119 -0
- examples/codegen/tests/test_example_tools.py +185 -0
- examples/codegen/tests/test_git_tools.py +112 -0
- examples/codegen/tests/test_impl_agent_schemas.py +193 -0
- examples/codegen/tests/test_impl_agent_v4_graph.py +94 -0
- examples/codegen/tests/test_jedi_analysis.py +226 -0
- examples/codegen/tests/test_meta_tools.py +250 -0
- examples/codegen/tests/test_plan_discovery_prompt.py +98 -0
- examples/codegen/tests/test_syntax_tools.py +85 -0
- examples/codegen/tests/test_synthesize_prompt.py +94 -0
- examples/codegen/tests/test_template_tools.py +244 -0
- examples/codegen/tools/__init__.py +80 -0
- examples/codegen/tools/ai_helpers.py +420 -0
- examples/codegen/tools/ast_analysis.py +92 -0
- examples/codegen/tools/code_context.py +180 -0
- examples/codegen/tools/code_nav.py +52 -0
- examples/codegen/tools/dependency_tools.py +120 -0
- examples/codegen/tools/example_tools.py +188 -0
- examples/codegen/tools/git_tools.py +151 -0
- examples/codegen/tools/impl_executor.py +614 -0
- examples/codegen/tools/jedi_analysis.py +311 -0
- examples/codegen/tools/meta_tools.py +202 -0
- examples/codegen/tools/syntax_tools.py +26 -0
- examples/codegen/tools/template_tools.py +356 -0
- examples/fastapi_interview.py +167 -0
- examples/npc/api/__init__.py +1 -0
- examples/npc/api/app.py +100 -0
- examples/npc/api/routes/__init__.py +5 -0
- examples/npc/api/routes/encounter.py +182 -0
- examples/npc/api/session.py +330 -0
- examples/npc/demo.py +387 -0
- examples/npc/nodes/__init__.py +5 -0
- examples/npc/nodes/image_node.py +92 -0
- examples/npc/run_encounter.py +230 -0
- examples/shared/__init__.py +0 -0
- examples/shared/replicate_tool.py +238 -0
- examples/storyboard/__init__.py +1 -0
- examples/storyboard/generate_videos.py +335 -0
- examples/storyboard/nodes/__init__.py +12 -0
- examples/storyboard/nodes/animated_character_node.py +248 -0
- examples/storyboard/nodes/animated_image_node.py +138 -0
- examples/storyboard/nodes/character_node.py +162 -0
- examples/storyboard/nodes/image_node.py +118 -0
- examples/storyboard/nodes/replicate_tool.py +49 -0
- examples/storyboard/retry_images.py +118 -0
- scripts/demo_async_executor.py +212 -0
- scripts/demo_interview_e2e.py +200 -0
- scripts/demo_streaming.py +140 -0
- scripts/run_interview_demo.py +94 -0
- scripts/test_interrupt_fix.py +26 -0
- tests/__init__.py +1 -0
- tests/conftest.py +178 -0
- tests/integration/__init__.py +1 -0
- tests/integration/test_animated_storyboard.py +63 -0
- tests/integration/test_cli_commands.py +242 -0
- tests/integration/test_colocated_prompts.py +139 -0
- tests/integration/test_map_demo.py +50 -0
- tests/integration/test_memory_demo.py +283 -0
- tests/integration/test_npc_api/__init__.py +1 -0
- tests/integration/test_npc_api/test_routes.py +357 -0
- tests/integration/test_npc_api/test_session.py +216 -0
- tests/integration/test_pipeline_flow.py +105 -0
- tests/integration/test_providers.py +163 -0
- tests/integration/test_resume.py +75 -0
- tests/integration/test_subgraph_integration.py +295 -0
- tests/integration/test_subgraph_interrupt.py +106 -0
- tests/unit/__init__.py +1 -0
- tests/unit/test_agent_nodes.py +355 -0
- tests/unit/test_async_executor.py +346 -0
- tests/unit/test_checkpointer.py +212 -0
- tests/unit/test_checkpointer_factory.py +212 -0
- tests/unit/test_cli.py +121 -0
- tests/unit/test_cli_package.py +81 -0
- tests/unit/test_compile_graph_map.py +132 -0
- tests/unit/test_conditions_routing.py +253 -0
- tests/unit/test_config.py +93 -0
- tests/unit/test_conversation_memory.py +276 -0
- tests/unit/test_database.py +145 -0
- tests/unit/test_deprecation.py +104 -0
- tests/unit/test_executor.py +172 -0
- tests/unit/test_executor_async.py +179 -0
- tests/unit/test_export.py +149 -0
- tests/unit/test_expressions.py +178 -0
- tests/unit/test_feature_brainstorm.py +194 -0
- tests/unit/test_format_prompt.py +145 -0
- tests/unit/test_generic_report.py +200 -0
- tests/unit/test_graph_commands.py +327 -0
- tests/unit/test_graph_linter.py +627 -0
- tests/unit/test_graph_loader.py +357 -0
- tests/unit/test_graph_schema.py +193 -0
- tests/unit/test_inline_schema.py +151 -0
- tests/unit/test_interrupt_node.py +182 -0
- tests/unit/test_issues.py +164 -0
- tests/unit/test_jinja2_prompts.py +85 -0
- tests/unit/test_json_extract.py +134 -0
- tests/unit/test_langsmith.py +600 -0
- tests/unit/test_langsmith_tools.py +204 -0
- tests/unit/test_llm_factory.py +109 -0
- tests/unit/test_llm_factory_async.py +118 -0
- tests/unit/test_loops.py +403 -0
- tests/unit/test_map_node.py +144 -0
- tests/unit/test_no_backward_compat.py +56 -0
- tests/unit/test_node_factory.py +348 -0
- tests/unit/test_passthrough_node.py +126 -0
- tests/unit/test_prompts.py +324 -0
- tests/unit/test_python_nodes.py +198 -0
- tests/unit/test_reliability.py +298 -0
- tests/unit/test_result_export.py +234 -0
- tests/unit/test_router.py +296 -0
- tests/unit/test_sanitize.py +99 -0
- tests/unit/test_schema_loader.py +295 -0
- tests/unit/test_shell_tools.py +229 -0
- tests/unit/test_state_builder.py +331 -0
- tests/unit/test_state_builder_map.py +104 -0
- tests/unit/test_state_config.py +197 -0
- tests/unit/test_streaming.py +307 -0
- tests/unit/test_subgraph.py +596 -0
- tests/unit/test_template.py +190 -0
- tests/unit/test_tool_call_integration.py +164 -0
- tests/unit/test_tool_call_node.py +178 -0
- tests/unit/test_tool_nodes.py +129 -0
- tests/unit/test_websearch.py +234 -0
- yamlgraph/__init__.py +35 -0
- yamlgraph/builder.py +110 -0
- yamlgraph/cli/__init__.py +159 -0
- yamlgraph/cli/__main__.py +6 -0
- yamlgraph/cli/commands.py +231 -0
- yamlgraph/cli/deprecation.py +92 -0
- yamlgraph/cli/graph_commands.py +541 -0
- yamlgraph/cli/validators.py +37 -0
- yamlgraph/config.py +67 -0
- yamlgraph/constants.py +70 -0
- yamlgraph/error_handlers.py +227 -0
- yamlgraph/executor.py +290 -0
- yamlgraph/executor_async.py +288 -0
- yamlgraph/graph_loader.py +451 -0
- yamlgraph/map_compiler.py +150 -0
- yamlgraph/models/__init__.py +36 -0
- yamlgraph/models/graph_schema.py +181 -0
- yamlgraph/models/schemas.py +124 -0
- yamlgraph/models/state_builder.py +236 -0
- yamlgraph/node_factory.py +768 -0
- yamlgraph/routing.py +87 -0
- yamlgraph/schema_loader.py +240 -0
- yamlgraph/storage/__init__.py +20 -0
- yamlgraph/storage/checkpointer.py +72 -0
- yamlgraph/storage/checkpointer_factory.py +123 -0
- yamlgraph/storage/database.py +320 -0
- yamlgraph/storage/export.py +269 -0
- yamlgraph/tools/__init__.py +1 -0
- yamlgraph/tools/agent.py +320 -0
- yamlgraph/tools/graph_linter.py +388 -0
- yamlgraph/tools/langsmith_tools.py +125 -0
- yamlgraph/tools/nodes.py +126 -0
- yamlgraph/tools/python_tool.py +179 -0
- yamlgraph/tools/shell.py +205 -0
- yamlgraph/tools/websearch.py +242 -0
- yamlgraph/utils/__init__.py +48 -0
- yamlgraph/utils/conditions.py +157 -0
- yamlgraph/utils/expressions.py +245 -0
- yamlgraph/utils/json_extract.py +104 -0
- yamlgraph/utils/langsmith.py +416 -0
- yamlgraph/utils/llm_factory.py +118 -0
- yamlgraph/utils/llm_factory_async.py +105 -0
- yamlgraph/utils/logging.py +104 -0
- yamlgraph/utils/prompts.py +171 -0
- yamlgraph/utils/sanitize.py +98 -0
- yamlgraph/utils/template.py +102 -0
- yamlgraph/utils/validators.py +181 -0
- yamlgraph-0.3.9.dist-info/METADATA +1105 -0
- yamlgraph-0.3.9.dist-info/RECORD +185 -0
- yamlgraph-0.3.9.dist-info/WHEEL +5 -0
- yamlgraph-0.3.9.dist-info/entry_points.txt +2 -0
- yamlgraph-0.3.9.dist-info/licenses/LICENSE +33 -0
- yamlgraph-0.3.9.dist-info/top_level.txt +4 -0
|
@@ -0,0 +1,600 @@
|
|
|
1
|
+
"""Unit tests for LangSmith utilities.
|
|
2
|
+
|
|
3
|
+
Tests for:
|
|
4
|
+
- share_run() - Create public share links
|
|
5
|
+
- read_run_shared_link() - Get existing share links
|
|
6
|
+
- get_client() - Client creation with env var handling
|
|
7
|
+
- is_tracing_enabled() - Tracing detection
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
from unittest.mock import MagicMock, patch
|
|
12
|
+
|
|
13
|
+
from yamlgraph.utils.langsmith import (
|
|
14
|
+
get_client,
|
|
15
|
+
get_latest_run_id,
|
|
16
|
+
get_project_name,
|
|
17
|
+
is_tracing_enabled,
|
|
18
|
+
read_run_shared_link,
|
|
19
|
+
share_run,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
# =============================================================================
|
|
23
|
+
# is_tracing_enabled() tests
|
|
24
|
+
# =============================================================================
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class TestIsTracingEnabled:
|
|
28
|
+
"""Tests for is_tracing_enabled()."""
|
|
29
|
+
|
|
30
|
+
def test_enabled_with_langchain_tracing_v2_true(self):
|
|
31
|
+
"""LANGCHAIN_TRACING_V2=true enables tracing."""
|
|
32
|
+
with patch.dict(os.environ, {"LANGCHAIN_TRACING_V2": "true"}, clear=False):
|
|
33
|
+
# Need to remove LANGSMITH_TRACING if set
|
|
34
|
+
env = dict(os.environ)
|
|
35
|
+
env.pop("LANGSMITH_TRACING", None)
|
|
36
|
+
with patch.dict(os.environ, env, clear=True):
|
|
37
|
+
os.environ["LANGCHAIN_TRACING_V2"] = "true"
|
|
38
|
+
assert is_tracing_enabled() is True
|
|
39
|
+
|
|
40
|
+
def test_enabled_with_langsmith_tracing_true(self):
|
|
41
|
+
"""LANGSMITH_TRACING=true enables tracing."""
|
|
42
|
+
with patch.dict(os.environ, {"LANGSMITH_TRACING": "true"}, clear=True):
|
|
43
|
+
assert is_tracing_enabled() is True
|
|
44
|
+
|
|
45
|
+
def test_disabled_when_no_env_vars(self):
|
|
46
|
+
"""No tracing vars means disabled."""
|
|
47
|
+
with patch.dict(os.environ, {}, clear=True):
|
|
48
|
+
assert is_tracing_enabled() is False
|
|
49
|
+
|
|
50
|
+
def test_disabled_with_false_value(self):
|
|
51
|
+
"""Explicit false value disables tracing."""
|
|
52
|
+
with patch.dict(os.environ, {"LANGCHAIN_TRACING_V2": "false"}, clear=True):
|
|
53
|
+
assert is_tracing_enabled() is False
|
|
54
|
+
|
|
55
|
+
def test_case_insensitive(self):
|
|
56
|
+
"""TRUE, True, true all work."""
|
|
57
|
+
with patch.dict(os.environ, {"LANGSMITH_TRACING": "TRUE"}, clear=True):
|
|
58
|
+
assert is_tracing_enabled() is True
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
# =============================================================================
|
|
62
|
+
# get_project_name() tests
|
|
63
|
+
# =============================================================================
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class TestGetProjectName:
|
|
67
|
+
"""Tests for get_project_name()."""
|
|
68
|
+
|
|
69
|
+
def test_langchain_project(self):
|
|
70
|
+
"""Returns LANGCHAIN_PROJECT when set."""
|
|
71
|
+
with patch.dict(os.environ, {"LANGCHAIN_PROJECT": "my-project"}, clear=True):
|
|
72
|
+
assert get_project_name() == "my-project"
|
|
73
|
+
|
|
74
|
+
def test_langsmith_project(self):
|
|
75
|
+
"""Returns LANGSMITH_PROJECT when set."""
|
|
76
|
+
with patch.dict(os.environ, {"LANGSMITH_PROJECT": "other-project"}, clear=True):
|
|
77
|
+
assert get_project_name() == "other-project"
|
|
78
|
+
|
|
79
|
+
def test_langchain_takes_precedence(self):
|
|
80
|
+
"""LANGCHAIN_PROJECT takes precedence over LANGSMITH_PROJECT."""
|
|
81
|
+
with patch.dict(
|
|
82
|
+
os.environ,
|
|
83
|
+
{"LANGCHAIN_PROJECT": "first", "LANGSMITH_PROJECT": "second"},
|
|
84
|
+
clear=True,
|
|
85
|
+
):
|
|
86
|
+
assert get_project_name() == "first"
|
|
87
|
+
|
|
88
|
+
def test_default_value(self):
|
|
89
|
+
"""Returns default when no env vars."""
|
|
90
|
+
with patch.dict(os.environ, {}, clear=True):
|
|
91
|
+
assert get_project_name() == "yamlgraph"
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
# =============================================================================
|
|
95
|
+
# get_client() tests
|
|
96
|
+
# =============================================================================
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class TestGetClient:
|
|
100
|
+
"""Tests for get_client()."""
|
|
101
|
+
|
|
102
|
+
def test_returns_none_without_api_key(self):
|
|
103
|
+
"""No API key means no client."""
|
|
104
|
+
with patch.dict(os.environ, {}, clear=True):
|
|
105
|
+
assert get_client() is None
|
|
106
|
+
|
|
107
|
+
def test_creates_client_with_langchain_key(self):
|
|
108
|
+
"""Creates client with LANGCHAIN_API_KEY."""
|
|
109
|
+
with patch.dict(
|
|
110
|
+
os.environ,
|
|
111
|
+
{"LANGCHAIN_API_KEY": "lsv2_test_key"},
|
|
112
|
+
clear=True,
|
|
113
|
+
), patch("langsmith.Client") as mock_client:
|
|
114
|
+
result = get_client()
|
|
115
|
+
mock_client.assert_called_once()
|
|
116
|
+
assert result is not None
|
|
117
|
+
|
|
118
|
+
def test_creates_client_with_langsmith_key(self):
|
|
119
|
+
"""Creates client with LANGSMITH_API_KEY."""
|
|
120
|
+
with patch.dict(
|
|
121
|
+
os.environ,
|
|
122
|
+
{"LANGSMITH_API_KEY": "lsv2_test_key"},
|
|
123
|
+
clear=True,
|
|
124
|
+
), patch("langsmith.Client") as mock_client:
|
|
125
|
+
result = get_client()
|
|
126
|
+
mock_client.assert_called_once()
|
|
127
|
+
assert result is not None
|
|
128
|
+
|
|
129
|
+
def test_uses_custom_endpoint(self):
|
|
130
|
+
"""Uses LANGSMITH_ENDPOINT if set."""
|
|
131
|
+
with patch.dict(
|
|
132
|
+
os.environ,
|
|
133
|
+
{
|
|
134
|
+
"LANGSMITH_API_KEY": "key",
|
|
135
|
+
"LANGSMITH_ENDPOINT": "https://eu.smith.langchain.com",
|
|
136
|
+
},
|
|
137
|
+
clear=True,
|
|
138
|
+
), patch("langsmith.Client") as mock_client:
|
|
139
|
+
get_client()
|
|
140
|
+
mock_client.assert_called_with(
|
|
141
|
+
api_url="https://eu.smith.langchain.com",
|
|
142
|
+
api_key="key",
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
def test_returns_none_on_import_error(self):
|
|
146
|
+
"""Returns None if langsmith not installed."""
|
|
147
|
+
# Verify graceful handling when Client constructor fails
|
|
148
|
+
with (
|
|
149
|
+
patch.dict(os.environ, {"LANGSMITH_API_KEY": "key"}, clear=True),
|
|
150
|
+
patch("langsmith.Client", side_effect=ImportError("No module")),
|
|
151
|
+
):
|
|
152
|
+
# Should catch ImportError and return None
|
|
153
|
+
result = get_client()
|
|
154
|
+
assert result is None
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
# =============================================================================
|
|
158
|
+
# share_run() tests
|
|
159
|
+
# =============================================================================
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class TestShareRun:
|
|
163
|
+
"""Tests for share_run()."""
|
|
164
|
+
|
|
165
|
+
def test_returns_none_when_no_client(self):
|
|
166
|
+
"""Returns None when client unavailable."""
|
|
167
|
+
with patch("yamlgraph.utils.langsmith.get_client", return_value=None):
|
|
168
|
+
result = share_run("test-run-id")
|
|
169
|
+
assert result is None
|
|
170
|
+
|
|
171
|
+
def test_shares_provided_run_id(self):
|
|
172
|
+
"""Shares the provided run ID."""
|
|
173
|
+
mock_client = MagicMock()
|
|
174
|
+
mock_client.share_run.return_value = "https://smith.langchain.com/public/abc123"
|
|
175
|
+
|
|
176
|
+
with patch("yamlgraph.utils.langsmith.get_client", return_value=mock_client):
|
|
177
|
+
result = share_run("my-run-id")
|
|
178
|
+
|
|
179
|
+
mock_client.share_run.assert_called_once_with("my-run-id")
|
|
180
|
+
assert result == "https://smith.langchain.com/public/abc123"
|
|
181
|
+
|
|
182
|
+
def test_uses_latest_run_when_no_id(self):
|
|
183
|
+
"""Gets latest run ID when not provided."""
|
|
184
|
+
mock_client = MagicMock()
|
|
185
|
+
mock_client.share_run.return_value = "https://share.url"
|
|
186
|
+
|
|
187
|
+
with (
|
|
188
|
+
patch("yamlgraph.utils.langsmith.get_client", return_value=mock_client),
|
|
189
|
+
patch(
|
|
190
|
+
"yamlgraph.utils.langsmith.get_latest_run_id",
|
|
191
|
+
return_value="latest-id",
|
|
192
|
+
),
|
|
193
|
+
):
|
|
194
|
+
result = share_run()
|
|
195
|
+
|
|
196
|
+
mock_client.share_run.assert_called_once_with("latest-id")
|
|
197
|
+
assert result == "https://share.url"
|
|
198
|
+
|
|
199
|
+
def test_returns_none_when_no_latest_run(self):
|
|
200
|
+
"""Returns None when no latest run found."""
|
|
201
|
+
mock_client = MagicMock()
|
|
202
|
+
|
|
203
|
+
with (
|
|
204
|
+
patch("yamlgraph.utils.langsmith.get_client", return_value=mock_client),
|
|
205
|
+
patch(
|
|
206
|
+
"yamlgraph.utils.langsmith.get_latest_run_id",
|
|
207
|
+
return_value=None,
|
|
208
|
+
),
|
|
209
|
+
):
|
|
210
|
+
result = share_run()
|
|
211
|
+
assert result is None
|
|
212
|
+
|
|
213
|
+
def test_handles_exception_gracefully(self):
|
|
214
|
+
"""Returns None on error (logs warning to stderr)."""
|
|
215
|
+
mock_client = MagicMock()
|
|
216
|
+
mock_client.share_run.side_effect = Exception("API error")
|
|
217
|
+
|
|
218
|
+
with patch("yamlgraph.utils.langsmith.get_client", return_value=mock_client):
|
|
219
|
+
result = share_run("test-id")
|
|
220
|
+
assert result is None
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
# =============================================================================
|
|
224
|
+
# read_run_shared_link() tests
|
|
225
|
+
# =============================================================================
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
class TestReadRunSharedLink:
|
|
229
|
+
"""Tests for read_run_shared_link()."""
|
|
230
|
+
|
|
231
|
+
def test_returns_none_when_no_client(self):
|
|
232
|
+
"""Returns None when client unavailable."""
|
|
233
|
+
with patch("yamlgraph.utils.langsmith.get_client", return_value=None):
|
|
234
|
+
result = read_run_shared_link("test-run-id")
|
|
235
|
+
assert result is None
|
|
236
|
+
|
|
237
|
+
def test_returns_existing_link(self):
|
|
238
|
+
"""Returns existing share link."""
|
|
239
|
+
mock_client = MagicMock()
|
|
240
|
+
mock_client.read_run_shared_link.return_value = "https://existing.url"
|
|
241
|
+
|
|
242
|
+
with patch("yamlgraph.utils.langsmith.get_client", return_value=mock_client):
|
|
243
|
+
result = read_run_shared_link("my-run-id")
|
|
244
|
+
|
|
245
|
+
mock_client.read_run_shared_link.assert_called_once_with("my-run-id")
|
|
246
|
+
assert result == "https://existing.url"
|
|
247
|
+
|
|
248
|
+
def test_returns_none_when_not_shared(self):
|
|
249
|
+
"""Returns None when run not shared (exception)."""
|
|
250
|
+
mock_client = MagicMock()
|
|
251
|
+
mock_client.read_run_shared_link.side_effect = Exception("Not found")
|
|
252
|
+
|
|
253
|
+
with patch("yamlgraph.utils.langsmith.get_client", return_value=mock_client):
|
|
254
|
+
result = read_run_shared_link("test-id")
|
|
255
|
+
assert result is None
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
# =============================================================================
|
|
259
|
+
# get_latest_run_id() tests
|
|
260
|
+
# =============================================================================
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
class TestGetLatestRunId:
|
|
264
|
+
"""Tests for get_latest_run_id()."""
|
|
265
|
+
|
|
266
|
+
def test_returns_none_when_no_client(self):
|
|
267
|
+
"""Returns None when client unavailable."""
|
|
268
|
+
with patch("yamlgraph.utils.langsmith.get_client", return_value=None):
|
|
269
|
+
result = get_latest_run_id()
|
|
270
|
+
assert result is None
|
|
271
|
+
|
|
272
|
+
def test_returns_latest_run_id(self):
|
|
273
|
+
"""Returns ID of most recent run."""
|
|
274
|
+
mock_run = MagicMock()
|
|
275
|
+
mock_run.id = "abc-123"
|
|
276
|
+
|
|
277
|
+
mock_client = MagicMock()
|
|
278
|
+
mock_client.list_runs.return_value = [mock_run]
|
|
279
|
+
|
|
280
|
+
with (
|
|
281
|
+
patch("yamlgraph.utils.langsmith.get_client", return_value=mock_client),
|
|
282
|
+
patch(
|
|
283
|
+
"yamlgraph.utils.langsmith.get_project_name",
|
|
284
|
+
return_value="test-project",
|
|
285
|
+
),
|
|
286
|
+
):
|
|
287
|
+
result = get_latest_run_id()
|
|
288
|
+
|
|
289
|
+
mock_client.list_runs.assert_called_once_with(
|
|
290
|
+
project_name="test-project", limit=1
|
|
291
|
+
)
|
|
292
|
+
assert result == "abc-123"
|
|
293
|
+
|
|
294
|
+
def test_returns_none_when_no_runs(self):
|
|
295
|
+
"""Returns None when no runs found."""
|
|
296
|
+
mock_client = MagicMock()
|
|
297
|
+
mock_client.list_runs.return_value = []
|
|
298
|
+
|
|
299
|
+
with patch("yamlgraph.utils.langsmith.get_client", return_value=mock_client):
|
|
300
|
+
result = get_latest_run_id()
|
|
301
|
+
assert result is None
|
|
302
|
+
|
|
303
|
+
def test_uses_provided_project_name(self):
|
|
304
|
+
"""Uses provided project name."""
|
|
305
|
+
mock_run = MagicMock()
|
|
306
|
+
mock_run.id = "run-id"
|
|
307
|
+
mock_client = MagicMock()
|
|
308
|
+
mock_client.list_runs.return_value = [mock_run]
|
|
309
|
+
|
|
310
|
+
with patch("yamlgraph.utils.langsmith.get_client", return_value=mock_client):
|
|
311
|
+
get_latest_run_id(project_name="custom-project")
|
|
312
|
+
|
|
313
|
+
mock_client.list_runs.assert_called_once_with(
|
|
314
|
+
project_name="custom-project", limit=1
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
def test_handles_exception_gracefully(self):
|
|
318
|
+
"""Returns None on error (logs warning to stderr)."""
|
|
319
|
+
mock_client = MagicMock()
|
|
320
|
+
mock_client.list_runs.side_effect = Exception("API error")
|
|
321
|
+
|
|
322
|
+
with patch("yamlgraph.utils.langsmith.get_client", return_value=mock_client):
|
|
323
|
+
result = get_latest_run_id()
|
|
324
|
+
assert result is None
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
# =============================================================================
|
|
328
|
+
# get_run_details() tests
|
|
329
|
+
# =============================================================================
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
class TestGetRunDetails:
|
|
333
|
+
"""Tests for get_run_details()."""
|
|
334
|
+
|
|
335
|
+
def test_returns_none_when_no_client(self):
|
|
336
|
+
"""Returns None when client unavailable."""
|
|
337
|
+
from yamlgraph.utils.langsmith import get_run_details
|
|
338
|
+
|
|
339
|
+
with patch("yamlgraph.utils.langsmith.get_client", return_value=None):
|
|
340
|
+
result = get_run_details("test-run-id")
|
|
341
|
+
assert result is None
|
|
342
|
+
|
|
343
|
+
def test_returns_none_when_no_run_id_and_no_latest(self):
|
|
344
|
+
"""Returns None when no run ID provided and no latest run."""
|
|
345
|
+
from yamlgraph.utils.langsmith import get_run_details
|
|
346
|
+
|
|
347
|
+
mock_client = MagicMock()
|
|
348
|
+
with (
|
|
349
|
+
patch("yamlgraph.utils.langsmith.get_client", return_value=mock_client),
|
|
350
|
+
patch(
|
|
351
|
+
"yamlgraph.utils.langsmith.get_latest_run_id", return_value=None
|
|
352
|
+
),
|
|
353
|
+
):
|
|
354
|
+
result = get_run_details()
|
|
355
|
+
assert result is None
|
|
356
|
+
|
|
357
|
+
def test_returns_run_details(self):
|
|
358
|
+
"""Returns detailed run information."""
|
|
359
|
+
from datetime import datetime
|
|
360
|
+
|
|
361
|
+
from yamlgraph.utils.langsmith import get_run_details
|
|
362
|
+
|
|
363
|
+
mock_run = MagicMock()
|
|
364
|
+
mock_run.id = "run-123"
|
|
365
|
+
mock_run.name = "test_pipeline"
|
|
366
|
+
mock_run.status = "success"
|
|
367
|
+
mock_run.error = None
|
|
368
|
+
mock_run.start_time = datetime(2026, 1, 18, 10, 0, 0)
|
|
369
|
+
mock_run.end_time = datetime(2026, 1, 18, 10, 1, 0)
|
|
370
|
+
mock_run.inputs = {"topic": "AI"}
|
|
371
|
+
mock_run.outputs = {"result": "done"}
|
|
372
|
+
mock_run.run_type = "chain"
|
|
373
|
+
|
|
374
|
+
mock_client = MagicMock()
|
|
375
|
+
mock_client.read_run.return_value = mock_run
|
|
376
|
+
|
|
377
|
+
with patch("yamlgraph.utils.langsmith.get_client", return_value=mock_client):
|
|
378
|
+
result = get_run_details("run-123")
|
|
379
|
+
|
|
380
|
+
assert result["id"] == "run-123"
|
|
381
|
+
assert result["name"] == "test_pipeline"
|
|
382
|
+
assert result["status"] == "success"
|
|
383
|
+
assert result["error"] is None
|
|
384
|
+
assert result["inputs"] == {"topic": "AI"}
|
|
385
|
+
assert result["outputs"] == {"result": "done"}
|
|
386
|
+
assert result["run_type"] == "chain"
|
|
387
|
+
|
|
388
|
+
def test_uses_latest_run_when_no_id(self):
|
|
389
|
+
"""Uses latest run ID when not provided."""
|
|
390
|
+
from yamlgraph.utils.langsmith import get_run_details
|
|
391
|
+
|
|
392
|
+
mock_run = MagicMock()
|
|
393
|
+
mock_run.id = "latest-run"
|
|
394
|
+
mock_run.name = "latest"
|
|
395
|
+
mock_run.status = "success"
|
|
396
|
+
mock_run.error = None
|
|
397
|
+
mock_run.start_time = None
|
|
398
|
+
mock_run.end_time = None
|
|
399
|
+
mock_run.inputs = {}
|
|
400
|
+
mock_run.outputs = {}
|
|
401
|
+
mock_run.run_type = "chain"
|
|
402
|
+
|
|
403
|
+
mock_client = MagicMock()
|
|
404
|
+
mock_client.read_run.return_value = mock_run
|
|
405
|
+
|
|
406
|
+
with (
|
|
407
|
+
patch("yamlgraph.utils.langsmith.get_client", return_value=mock_client),
|
|
408
|
+
patch(
|
|
409
|
+
"yamlgraph.utils.langsmith.get_latest_run_id",
|
|
410
|
+
return_value="latest-run",
|
|
411
|
+
),
|
|
412
|
+
):
|
|
413
|
+
result = get_run_details()
|
|
414
|
+
|
|
415
|
+
mock_client.read_run.assert_called_once_with("latest-run")
|
|
416
|
+
assert result["id"] == "latest-run"
|
|
417
|
+
|
|
418
|
+
def test_handles_exception_gracefully(self):
|
|
419
|
+
"""Returns None on error."""
|
|
420
|
+
from yamlgraph.utils.langsmith import get_run_details
|
|
421
|
+
|
|
422
|
+
mock_client = MagicMock()
|
|
423
|
+
mock_client.read_run.side_effect = Exception("API error")
|
|
424
|
+
|
|
425
|
+
with patch("yamlgraph.utils.langsmith.get_client", return_value=mock_client):
|
|
426
|
+
result = get_run_details("test-id")
|
|
427
|
+
assert result is None
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
# =============================================================================
|
|
431
|
+
# get_run_errors() tests
|
|
432
|
+
# =============================================================================
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
class TestGetRunErrors:
|
|
436
|
+
"""Tests for get_run_errors()."""
|
|
437
|
+
|
|
438
|
+
def test_returns_empty_list_when_no_client(self):
|
|
439
|
+
"""Returns empty list when client unavailable."""
|
|
440
|
+
from yamlgraph.utils.langsmith import get_run_errors
|
|
441
|
+
|
|
442
|
+
with patch("yamlgraph.utils.langsmith.get_client", return_value=None):
|
|
443
|
+
result = get_run_errors("test-run-id")
|
|
444
|
+
assert result == []
|
|
445
|
+
|
|
446
|
+
def test_returns_empty_list_when_no_run_id(self):
|
|
447
|
+
"""Returns empty list when no run ID and no latest."""
|
|
448
|
+
from yamlgraph.utils.langsmith import get_run_errors
|
|
449
|
+
|
|
450
|
+
mock_client = MagicMock()
|
|
451
|
+
with (
|
|
452
|
+
patch("yamlgraph.utils.langsmith.get_client", return_value=mock_client),
|
|
453
|
+
patch(
|
|
454
|
+
"yamlgraph.utils.langsmith.get_latest_run_id", return_value=None
|
|
455
|
+
),
|
|
456
|
+
):
|
|
457
|
+
result = get_run_errors()
|
|
458
|
+
assert result == []
|
|
459
|
+
|
|
460
|
+
def test_returns_parent_run_error(self):
|
|
461
|
+
"""Returns error from parent run."""
|
|
462
|
+
from yamlgraph.utils.langsmith import get_run_errors
|
|
463
|
+
|
|
464
|
+
mock_run = MagicMock()
|
|
465
|
+
mock_run.name = "parent_node"
|
|
466
|
+
mock_run.error = "Parent failed"
|
|
467
|
+
mock_run.run_type = "chain"
|
|
468
|
+
|
|
469
|
+
mock_client = MagicMock()
|
|
470
|
+
mock_client.read_run.return_value = mock_run
|
|
471
|
+
mock_client.list_runs.return_value = []
|
|
472
|
+
|
|
473
|
+
with patch("yamlgraph.utils.langsmith.get_client", return_value=mock_client):
|
|
474
|
+
result = get_run_errors("run-123")
|
|
475
|
+
|
|
476
|
+
assert len(result) == 1
|
|
477
|
+
assert result[0]["node"] == "parent_node"
|
|
478
|
+
assert result[0]["error"] == "Parent failed"
|
|
479
|
+
|
|
480
|
+
def test_returns_child_run_errors(self):
|
|
481
|
+
"""Returns errors from child runs."""
|
|
482
|
+
from yamlgraph.utils.langsmith import get_run_errors
|
|
483
|
+
|
|
484
|
+
mock_parent = MagicMock()
|
|
485
|
+
mock_parent.error = None
|
|
486
|
+
|
|
487
|
+
mock_child1 = MagicMock()
|
|
488
|
+
mock_child1.name = "generate"
|
|
489
|
+
mock_child1.error = "Generate failed"
|
|
490
|
+
mock_child1.run_type = "llm"
|
|
491
|
+
|
|
492
|
+
mock_child2 = MagicMock()
|
|
493
|
+
mock_child2.name = "analyze"
|
|
494
|
+
mock_child2.error = "Analyze failed"
|
|
495
|
+
mock_child2.run_type = "llm"
|
|
496
|
+
|
|
497
|
+
mock_client = MagicMock()
|
|
498
|
+
mock_client.read_run.return_value = mock_parent
|
|
499
|
+
mock_client.list_runs.return_value = [mock_child1, mock_child2]
|
|
500
|
+
|
|
501
|
+
with patch("yamlgraph.utils.langsmith.get_client", return_value=mock_client):
|
|
502
|
+
result = get_run_errors("run-123")
|
|
503
|
+
|
|
504
|
+
assert len(result) == 2
|
|
505
|
+
assert result[0]["node"] == "generate"
|
|
506
|
+
assert result[1]["node"] == "analyze"
|
|
507
|
+
|
|
508
|
+
def test_handles_exception_gracefully(self):
|
|
509
|
+
"""Returns empty list on error."""
|
|
510
|
+
from yamlgraph.utils.langsmith import get_run_errors
|
|
511
|
+
|
|
512
|
+
mock_client = MagicMock()
|
|
513
|
+
mock_client.read_run.side_effect = Exception("API error")
|
|
514
|
+
|
|
515
|
+
with patch("yamlgraph.utils.langsmith.get_client", return_value=mock_client):
|
|
516
|
+
result = get_run_errors("test-id")
|
|
517
|
+
assert result == []
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
# =============================================================================
|
|
521
|
+
# get_failed_runs() tests
|
|
522
|
+
# =============================================================================
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
class TestGetFailedRuns:
|
|
526
|
+
"""Tests for get_failed_runs()."""
|
|
527
|
+
|
|
528
|
+
def test_returns_empty_list_when_no_client(self):
|
|
529
|
+
"""Returns empty list when client unavailable."""
|
|
530
|
+
from yamlgraph.utils.langsmith import get_failed_runs
|
|
531
|
+
|
|
532
|
+
with patch("yamlgraph.utils.langsmith.get_client", return_value=None):
|
|
533
|
+
result = get_failed_runs()
|
|
534
|
+
assert result == []
|
|
535
|
+
|
|
536
|
+
def test_returns_failed_runs(self):
|
|
537
|
+
"""Returns list of failed runs."""
|
|
538
|
+
from datetime import datetime
|
|
539
|
+
|
|
540
|
+
from yamlgraph.utils.langsmith import get_failed_runs
|
|
541
|
+
|
|
542
|
+
mock_run1 = MagicMock()
|
|
543
|
+
mock_run1.id = "run-1"
|
|
544
|
+
mock_run1.name = "pipeline_1"
|
|
545
|
+
mock_run1.error = "Error 1"
|
|
546
|
+
mock_run1.start_time = datetime(2026, 1, 18, 10, 0, 0)
|
|
547
|
+
|
|
548
|
+
mock_run2 = MagicMock()
|
|
549
|
+
mock_run2.id = "run-2"
|
|
550
|
+
mock_run2.name = "pipeline_2"
|
|
551
|
+
mock_run2.error = "Error 2"
|
|
552
|
+
mock_run2.start_time = datetime(2026, 1, 18, 11, 0, 0)
|
|
553
|
+
|
|
554
|
+
mock_client = MagicMock()
|
|
555
|
+
mock_client.list_runs.return_value = [mock_run1, mock_run2]
|
|
556
|
+
|
|
557
|
+
with (
|
|
558
|
+
patch("yamlgraph.utils.langsmith.get_client", return_value=mock_client),
|
|
559
|
+
patch(
|
|
560
|
+
"yamlgraph.utils.langsmith.get_project_name",
|
|
561
|
+
return_value="test-project",
|
|
562
|
+
),
|
|
563
|
+
):
|
|
564
|
+
result = get_failed_runs(limit=5)
|
|
565
|
+
|
|
566
|
+
mock_client.list_runs.assert_called_once_with(
|
|
567
|
+
project_name="test-project",
|
|
568
|
+
error=True,
|
|
569
|
+
limit=5,
|
|
570
|
+
)
|
|
571
|
+
assert len(result) == 2
|
|
572
|
+
assert result[0]["id"] == "run-1"
|
|
573
|
+
assert result[0]["error"] == "Error 1"
|
|
574
|
+
|
|
575
|
+
def test_uses_provided_project_name(self):
|
|
576
|
+
"""Uses provided project name."""
|
|
577
|
+
from yamlgraph.utils.langsmith import get_failed_runs
|
|
578
|
+
|
|
579
|
+
mock_client = MagicMock()
|
|
580
|
+
mock_client.list_runs.return_value = []
|
|
581
|
+
|
|
582
|
+
with patch("yamlgraph.utils.langsmith.get_client", return_value=mock_client):
|
|
583
|
+
get_failed_runs(project_name="custom-project", limit=3)
|
|
584
|
+
|
|
585
|
+
mock_client.list_runs.assert_called_once_with(
|
|
586
|
+
project_name="custom-project",
|
|
587
|
+
error=True,
|
|
588
|
+
limit=3,
|
|
589
|
+
)
|
|
590
|
+
|
|
591
|
+
def test_handles_exception_gracefully(self):
|
|
592
|
+
"""Returns empty list on error."""
|
|
593
|
+
from yamlgraph.utils.langsmith import get_failed_runs
|
|
594
|
+
|
|
595
|
+
mock_client = MagicMock()
|
|
596
|
+
mock_client.list_runs.side_effect = Exception("API error")
|
|
597
|
+
|
|
598
|
+
with patch("yamlgraph.utils.langsmith.get_client", return_value=mock_client):
|
|
599
|
+
result = get_failed_runs()
|
|
600
|
+
assert result == []
|