prefactor-langchain 0.2.2__tar.gz → 0.2.4__tar.gz
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.
- {prefactor_langchain-0.2.2 → prefactor_langchain-0.2.4}/.gitignore +1 -0
- {prefactor_langchain-0.2.2 → prefactor_langchain-0.2.4}/PKG-INFO +64 -3
- {prefactor_langchain-0.2.2 → prefactor_langchain-0.2.4}/README.md +62 -1
- {prefactor_langchain-0.2.2 → prefactor_langchain-0.2.4}/examples/simple_agent.py +1 -1
- {prefactor_langchain-0.2.2 → prefactor_langchain-0.2.4}/pyproject.toml +2 -2
- {prefactor_langchain-0.2.2 → prefactor_langchain-0.2.4}/src/prefactor_langchain/__init__.py +9 -1
- {prefactor_langchain-0.2.2 → prefactor_langchain-0.2.4}/src/prefactor_langchain/middleware.py +108 -16
- prefactor_langchain-0.2.4/src/prefactor_langchain/schemas.py +687 -0
- {prefactor_langchain-0.2.2 → prefactor_langchain-0.2.4}/tests/test_middleware.py +241 -0
- prefactor_langchain-0.2.4/tests/test_schemas.py +257 -0
- prefactor_langchain-0.2.2/src/prefactor_langchain/schemas.py +0 -294
- {prefactor_langchain-0.2.2 → prefactor_langchain-0.2.4}/examples/.env.example +0 -0
- {prefactor_langchain-0.2.2 → prefactor_langchain-0.2.4}/examples/README.md +0 -0
- {prefactor_langchain-0.2.2 → prefactor_langchain-0.2.4}/src/prefactor_langchain/metadata_extractor.py +0 -0
- {prefactor_langchain-0.2.2 → prefactor_langchain-0.2.4}/src/prefactor_langchain/spans.py +0 -0
- {prefactor_langchain-0.2.2 → prefactor_langchain-0.2.4}/tests/test_metadata_extractor.py +0 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: prefactor-langchain
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: LangChain integration for Prefactor observability
|
|
5
5
|
Author-email: Prefactor Pty Ltd <josh@prefactor.tech>
|
|
6
6
|
License: MIT
|
|
7
7
|
Requires-Python: <4.0.0,>=3.11.0
|
|
8
8
|
Requires-Dist: langchain-core>=1.0.0
|
|
9
|
-
Requires-Dist: prefactor-core>=0.2.
|
|
9
|
+
Requires-Dist: prefactor-core>=0.2.2
|
|
10
10
|
Description-Content-Type: text/markdown
|
|
11
11
|
|
|
12
12
|
# prefactor-langchain
|
|
@@ -24,13 +24,26 @@ pip install prefactor-langchain
|
|
|
24
24
|
### Factory pattern (quickest setup)
|
|
25
25
|
|
|
26
26
|
```python
|
|
27
|
-
from prefactor_langchain import PrefactorMiddleware
|
|
27
|
+
from prefactor_langchain import LangChainToolSchemaConfig, PrefactorMiddleware
|
|
28
28
|
|
|
29
29
|
middleware = PrefactorMiddleware.from_config(
|
|
30
30
|
api_url="https://api.prefactor.ai",
|
|
31
31
|
api_token="your-api-token",
|
|
32
32
|
agent_id="my-agent",
|
|
33
33
|
agent_name="My Agent", # optional
|
|
34
|
+
tool_schemas={
|
|
35
|
+
"send_email": LangChainToolSchemaConfig(
|
|
36
|
+
span_type="send-email",
|
|
37
|
+
input_schema={
|
|
38
|
+
"type": "object",
|
|
39
|
+
"properties": {
|
|
40
|
+
"to": {"type": "string", "format": "email"},
|
|
41
|
+
"subject": {"type": "string"},
|
|
42
|
+
},
|
|
43
|
+
"required": ["to", "subject"],
|
|
44
|
+
},
|
|
45
|
+
)
|
|
46
|
+
},
|
|
34
47
|
)
|
|
35
48
|
|
|
36
49
|
# Use with LangChain's create_agent()
|
|
@@ -38,6 +51,7 @@ middleware = PrefactorMiddleware.from_config(
|
|
|
38
51
|
# - Agent execution (langchain:agent)
|
|
39
52
|
# - LLM calls (langchain:llm)
|
|
40
53
|
# - Tool executions (langchain:tool)
|
|
54
|
+
# - Tool-specific executions (for example langchain:tool:send-email)
|
|
41
55
|
|
|
42
56
|
result = agent.invoke({"messages": [...]})
|
|
43
57
|
|
|
@@ -73,6 +87,47 @@ await middleware.close() # closes the agent instance only
|
|
|
73
87
|
await client.close()
|
|
74
88
|
```
|
|
75
89
|
|
|
90
|
+
### SchemaRegistry composition
|
|
91
|
+
|
|
92
|
+
Use a shared ``SchemaRegistry`` when you want custom workflow span types and
|
|
93
|
+
LangChain tool schemas to be published together.
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
from prefactor_core import SchemaRegistry
|
|
97
|
+
from prefactor_langchain import (
|
|
98
|
+
LangChainToolSchemaConfig,
|
|
99
|
+
PrefactorMiddleware,
|
|
100
|
+
register_langchain_schemas,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
registry = SchemaRegistry()
|
|
104
|
+
registry.register_type(
|
|
105
|
+
name="workflow:run",
|
|
106
|
+
params_schema={"type": "object"},
|
|
107
|
+
result_schema={"type": "object"},
|
|
108
|
+
)
|
|
109
|
+
register_langchain_schemas(
|
|
110
|
+
registry,
|
|
111
|
+
tool_schemas={
|
|
112
|
+
"send_email": LangChainToolSchemaConfig(
|
|
113
|
+
span_type="send-email",
|
|
114
|
+
input_schema={
|
|
115
|
+
"type": "object",
|
|
116
|
+
"properties": {"to": {"type": "string"}},
|
|
117
|
+
"required": ["to"],
|
|
118
|
+
},
|
|
119
|
+
)
|
|
120
|
+
},
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
middleware = PrefactorMiddleware.from_config(
|
|
124
|
+
api_url="https://api.prefactor.ai",
|
|
125
|
+
api_token="your-api-token",
|
|
126
|
+
agent_id="my-agent",
|
|
127
|
+
schema_registry=registry,
|
|
128
|
+
)
|
|
129
|
+
```
|
|
130
|
+
|
|
76
131
|
### Pre-configured instance (spans outside the agent)
|
|
77
132
|
|
|
78
133
|
Pass an `AgentInstanceHandle` you created yourself when you also need to
|
|
@@ -111,6 +166,12 @@ await instance.finish()
|
|
|
111
166
|
await client.close()
|
|
112
167
|
```
|
|
113
168
|
|
|
169
|
+
If you pass ``tool_schemas=...`` with a pre-created instance, the middleware
|
|
170
|
+
uses those mappings to emit the right tool span types at runtime. The instance's
|
|
171
|
+
already-registered schema version is not mutated, so you must register matching
|
|
172
|
+
tool schemas before creating the instance if you want those per-tool span types
|
|
173
|
+
to appear in the backend schema version.
|
|
174
|
+
|
|
114
175
|
## Span Types
|
|
115
176
|
|
|
116
177
|
This package creates LangChain-specific spans with the `langchain:*` namespace:
|
|
@@ -13,13 +13,26 @@ pip install prefactor-langchain
|
|
|
13
13
|
### Factory pattern (quickest setup)
|
|
14
14
|
|
|
15
15
|
```python
|
|
16
|
-
from prefactor_langchain import PrefactorMiddleware
|
|
16
|
+
from prefactor_langchain import LangChainToolSchemaConfig, PrefactorMiddleware
|
|
17
17
|
|
|
18
18
|
middleware = PrefactorMiddleware.from_config(
|
|
19
19
|
api_url="https://api.prefactor.ai",
|
|
20
20
|
api_token="your-api-token",
|
|
21
21
|
agent_id="my-agent",
|
|
22
22
|
agent_name="My Agent", # optional
|
|
23
|
+
tool_schemas={
|
|
24
|
+
"send_email": LangChainToolSchemaConfig(
|
|
25
|
+
span_type="send-email",
|
|
26
|
+
input_schema={
|
|
27
|
+
"type": "object",
|
|
28
|
+
"properties": {
|
|
29
|
+
"to": {"type": "string", "format": "email"},
|
|
30
|
+
"subject": {"type": "string"},
|
|
31
|
+
},
|
|
32
|
+
"required": ["to", "subject"],
|
|
33
|
+
},
|
|
34
|
+
)
|
|
35
|
+
},
|
|
23
36
|
)
|
|
24
37
|
|
|
25
38
|
# Use with LangChain's create_agent()
|
|
@@ -27,6 +40,7 @@ middleware = PrefactorMiddleware.from_config(
|
|
|
27
40
|
# - Agent execution (langchain:agent)
|
|
28
41
|
# - LLM calls (langchain:llm)
|
|
29
42
|
# - Tool executions (langchain:tool)
|
|
43
|
+
# - Tool-specific executions (for example langchain:tool:send-email)
|
|
30
44
|
|
|
31
45
|
result = agent.invoke({"messages": [...]})
|
|
32
46
|
|
|
@@ -62,6 +76,47 @@ await middleware.close() # closes the agent instance only
|
|
|
62
76
|
await client.close()
|
|
63
77
|
```
|
|
64
78
|
|
|
79
|
+
### SchemaRegistry composition
|
|
80
|
+
|
|
81
|
+
Use a shared ``SchemaRegistry`` when you want custom workflow span types and
|
|
82
|
+
LangChain tool schemas to be published together.
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
from prefactor_core import SchemaRegistry
|
|
86
|
+
from prefactor_langchain import (
|
|
87
|
+
LangChainToolSchemaConfig,
|
|
88
|
+
PrefactorMiddleware,
|
|
89
|
+
register_langchain_schemas,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
registry = SchemaRegistry()
|
|
93
|
+
registry.register_type(
|
|
94
|
+
name="workflow:run",
|
|
95
|
+
params_schema={"type": "object"},
|
|
96
|
+
result_schema={"type": "object"},
|
|
97
|
+
)
|
|
98
|
+
register_langchain_schemas(
|
|
99
|
+
registry,
|
|
100
|
+
tool_schemas={
|
|
101
|
+
"send_email": LangChainToolSchemaConfig(
|
|
102
|
+
span_type="send-email",
|
|
103
|
+
input_schema={
|
|
104
|
+
"type": "object",
|
|
105
|
+
"properties": {"to": {"type": "string"}},
|
|
106
|
+
"required": ["to"],
|
|
107
|
+
},
|
|
108
|
+
)
|
|
109
|
+
},
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
middleware = PrefactorMiddleware.from_config(
|
|
113
|
+
api_url="https://api.prefactor.ai",
|
|
114
|
+
api_token="your-api-token",
|
|
115
|
+
agent_id="my-agent",
|
|
116
|
+
schema_registry=registry,
|
|
117
|
+
)
|
|
118
|
+
```
|
|
119
|
+
|
|
65
120
|
### Pre-configured instance (spans outside the agent)
|
|
66
121
|
|
|
67
122
|
Pass an `AgentInstanceHandle` you created yourself when you also need to
|
|
@@ -100,6 +155,12 @@ await instance.finish()
|
|
|
100
155
|
await client.close()
|
|
101
156
|
```
|
|
102
157
|
|
|
158
|
+
If you pass ``tool_schemas=...`` with a pre-created instance, the middleware
|
|
159
|
+
uses those mappings to emit the right tool span types at runtime. The instance's
|
|
160
|
+
already-registered schema version is not mutated, so you must register matching
|
|
161
|
+
tool schemas before creating the instance if you want those per-tool span types
|
|
162
|
+
to appear in the backend schema version.
|
|
163
|
+
|
|
103
164
|
## Span Types
|
|
104
165
|
|
|
105
166
|
This package creates LangChain-specific spans with the `langchain:*` namespace:
|
|
@@ -50,7 +50,7 @@ def _safe_eval(node: ast.expr) -> float:
|
|
|
50
50
|
if isinstance(node, ast.Constant):
|
|
51
51
|
return node.n # type: ignore[return-value]
|
|
52
52
|
if isinstance(node, ast.BinOp):
|
|
53
|
-
return _OPS[type(node.op)](_safe_eval(node.left), _safe_eval(node.right))
|
|
53
|
+
return _OPS[type(node.op)](_safe_eval(node.left), _safe_eval(node.right)) # type: ignore[invalid-argument-type]
|
|
54
54
|
if isinstance(node, ast.UnaryOp) and isinstance(node.op, ast.USub):
|
|
55
55
|
return -_safe_eval(node.operand)
|
|
56
56
|
raise ValueError(f"Unsupported expression: {ast.dump(node)}")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "prefactor-langchain"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.4"
|
|
4
4
|
description = "LangChain integration for Prefactor observability"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = { text = "MIT" }
|
|
@@ -9,7 +9,7 @@ authors = [
|
|
|
9
9
|
]
|
|
10
10
|
requires-python = ">=3.11.0, <4.0.0"
|
|
11
11
|
dependencies = [
|
|
12
|
-
"prefactor-core>=0.2.
|
|
12
|
+
"prefactor-core>=0.2.2",
|
|
13
13
|
"langchain-core>=1.0.0",
|
|
14
14
|
]
|
|
15
15
|
|
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
"""Prefactor LangChain - LangChain integration for Prefactor observability."""
|
|
2
2
|
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
3
5
|
from prefactor_langchain.metadata_extractor import (
|
|
4
6
|
extract_error_info,
|
|
5
7
|
extract_token_usage,
|
|
6
8
|
)
|
|
7
9
|
from prefactor_langchain.middleware import PrefactorMiddleware
|
|
8
10
|
from prefactor_langchain.schemas import (
|
|
11
|
+
DEFAULT_LANGCHAIN_AGENT_SCHEMA,
|
|
9
12
|
LANGCHAIN_AGENT_SCHEMA,
|
|
10
13
|
LANGCHAIN_LLM_SCHEMA,
|
|
11
14
|
LANGCHAIN_TOOL_SCHEMA,
|
|
15
|
+
LangChainToolSchemaConfig,
|
|
16
|
+
compile_langchain_agent_schema,
|
|
12
17
|
register_langchain_schemas,
|
|
13
18
|
)
|
|
14
19
|
from prefactor_langchain.spans import (
|
|
@@ -20,7 +25,7 @@ from prefactor_langchain.spans import (
|
|
|
20
25
|
ToolSpan,
|
|
21
26
|
)
|
|
22
27
|
|
|
23
|
-
__version__ = "0.2.
|
|
28
|
+
__version__ = "0.2.4"
|
|
24
29
|
|
|
25
30
|
__all__ = [
|
|
26
31
|
# Middleware
|
|
@@ -30,6 +35,9 @@ __all__ = [
|
|
|
30
35
|
"extract_error_info",
|
|
31
36
|
# Schemas
|
|
32
37
|
"register_langchain_schemas",
|
|
38
|
+
"compile_langchain_agent_schema",
|
|
39
|
+
"LangChainToolSchemaConfig",
|
|
40
|
+
"DEFAULT_LANGCHAIN_AGENT_SCHEMA",
|
|
33
41
|
"LANGCHAIN_AGENT_SCHEMA",
|
|
34
42
|
"LANGCHAIN_LLM_SCHEMA",
|
|
35
43
|
"LANGCHAIN_TOOL_SCHEMA",
|
{prefactor_langchain-0.2.2 → prefactor_langchain-0.2.4}/src/prefactor_langchain/middleware.py
RENAMED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
"""LangChain middleware for automatic tracing via prefactor-core."""
|
|
2
2
|
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
3
5
|
import asyncio
|
|
4
6
|
import hashlib
|
|
5
7
|
import json
|
|
6
8
|
import logging
|
|
9
|
+
from collections.abc import Mapping
|
|
7
10
|
from typing import TYPE_CHECKING, Any, AsyncContextManager, Callable
|
|
8
11
|
|
|
9
12
|
from langchain.agents.middleware import AgentMiddleware
|
|
@@ -20,7 +23,7 @@ if TYPE_CHECKING:
|
|
|
20
23
|
pass
|
|
21
24
|
|
|
22
25
|
from .metadata_extractor import extract_token_usage
|
|
23
|
-
from .schemas import register_langchain_schemas
|
|
26
|
+
from .schemas import LangChainToolSchemaConfig, register_langchain_schemas
|
|
24
27
|
from .spans import AgentSpan, LLMSpan, ToolSpan
|
|
25
28
|
|
|
26
29
|
logger = logging.getLogger("prefactor_langchain.middleware")
|
|
@@ -111,6 +114,8 @@ class PrefactorMiddleware(AgentMiddleware):
|
|
|
111
114
|
agent_id: str = "langchain-agent",
|
|
112
115
|
agent_name: str | None = None,
|
|
113
116
|
instance: AgentInstanceHandle | None = None,
|
|
117
|
+
tool_schemas: Mapping[str, LangChainToolSchemaConfig | Mapping[str, Any]]
|
|
118
|
+
| None = None,
|
|
114
119
|
):
|
|
115
120
|
"""Initialize the middleware with a pre-configured client or instance.
|
|
116
121
|
|
|
@@ -148,6 +153,9 @@ class PrefactorMiddleware(AgentMiddleware):
|
|
|
148
153
|
self._current_parent_span_id: str | None = None
|
|
149
154
|
self._loop: asyncio.AbstractEventLoop | None = None
|
|
150
155
|
self._pending_emit_futures: list[asyncio.Task[None]] = []
|
|
156
|
+
self._tool_span_types = (
|
|
157
|
+
self._register_tool_schemas(None, tool_schemas) if tool_schemas else {}
|
|
158
|
+
)
|
|
151
159
|
return
|
|
152
160
|
|
|
153
161
|
if client is None:
|
|
@@ -179,6 +187,10 @@ class PrefactorMiddleware(AgentMiddleware):
|
|
|
179
187
|
self._current_parent_span_id: str | None = None
|
|
180
188
|
self._loop: asyncio.AbstractEventLoop | None = None
|
|
181
189
|
self._pending_emit_futures: list[asyncio.Task[None]] = []
|
|
190
|
+
self._tool_span_types = {}
|
|
191
|
+
|
|
192
|
+
if tool_schemas:
|
|
193
|
+
self._tool_span_types = self._register_tool_schemas(client, tool_schemas)
|
|
182
194
|
|
|
183
195
|
@classmethod
|
|
184
196
|
def from_config(
|
|
@@ -189,6 +201,8 @@ class PrefactorMiddleware(AgentMiddleware):
|
|
|
189
201
|
agent_name: str | None = None,
|
|
190
202
|
schema_registry: SchemaRegistry | None = None,
|
|
191
203
|
include_langchain_schemas: bool = True,
|
|
204
|
+
tool_schemas: Mapping[str, LangChainToolSchemaConfig | Mapping[str, Any]]
|
|
205
|
+
| None = None,
|
|
192
206
|
) -> "PrefactorMiddleware":
|
|
193
207
|
"""Factory method to create middleware from configuration.
|
|
194
208
|
|
|
@@ -203,6 +217,8 @@ class PrefactorMiddleware(AgentMiddleware):
|
|
|
203
217
|
schema_registry: Optional SchemaRegistry for registering span schemas.
|
|
204
218
|
include_langchain_schemas: If True and schema_registry is provided,
|
|
205
219
|
automatically register LangChain-specific schemas.
|
|
220
|
+
tool_schemas: Optional per-tool schema configuration for tool-specific
|
|
221
|
+
span types.
|
|
206
222
|
|
|
207
223
|
Returns:
|
|
208
224
|
A configured PrefactorMiddleware instance with lazy initialization.
|
|
@@ -223,8 +239,12 @@ class PrefactorMiddleware(AgentMiddleware):
|
|
|
223
239
|
|
|
224
240
|
# Create or augment schema registry
|
|
225
241
|
registry = schema_registry or SchemaRegistry()
|
|
226
|
-
|
|
227
|
-
|
|
242
|
+
tool_span_types: dict[str, str] = {}
|
|
243
|
+
if include_langchain_schemas or tool_schemas:
|
|
244
|
+
tool_span_types = register_langchain_schemas(
|
|
245
|
+
registry,
|
|
246
|
+
tool_schemas=tool_schemas,
|
|
247
|
+
)
|
|
228
248
|
|
|
229
249
|
config = PrefactorCoreConfig(
|
|
230
250
|
http_config=http_config,
|
|
@@ -245,9 +265,32 @@ class PrefactorMiddleware(AgentMiddleware):
|
|
|
245
265
|
middleware._current_parent_span_id = None
|
|
246
266
|
middleware._loop = None
|
|
247
267
|
middleware._pending_emit_futures = []
|
|
268
|
+
middleware._tool_span_types = tool_span_types
|
|
248
269
|
logger.debug("PrefactorMiddleware created via from_config()")
|
|
249
270
|
return middleware
|
|
250
271
|
|
|
272
|
+
def _register_tool_schemas(
|
|
273
|
+
self,
|
|
274
|
+
client: PrefactorCoreClient | None,
|
|
275
|
+
tool_schemas: Mapping[str, LangChainToolSchemaConfig | Mapping[str, Any]],
|
|
276
|
+
) -> dict[str, str]:
|
|
277
|
+
"""Register tool schemas into a client registry when available."""
|
|
278
|
+
if client is None:
|
|
279
|
+
return register_langchain_schemas(
|
|
280
|
+
SchemaRegistry(),
|
|
281
|
+
tool_schemas=tool_schemas,
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
registry = client._config.schema_registry
|
|
285
|
+
if registry is None:
|
|
286
|
+
registry = SchemaRegistry()
|
|
287
|
+
client._config.schema_registry = registry
|
|
288
|
+
|
|
289
|
+
return register_langchain_schemas(
|
|
290
|
+
registry,
|
|
291
|
+
tool_schemas=tool_schemas,
|
|
292
|
+
)
|
|
293
|
+
|
|
251
294
|
async def _ensure_initialized(self) -> AgentInstanceHandle:
|
|
252
295
|
"""Ensure the agent instance is initialized (lazily created if needed).
|
|
253
296
|
|
|
@@ -419,6 +462,18 @@ class PrefactorMiddleware(AgentMiddleware):
|
|
|
419
462
|
"tool_name": tool_call.get("name", "unknown"),
|
|
420
463
|
"arguments": tool_call.get("args", {}),
|
|
421
464
|
}
|
|
465
|
+
if hasattr(request, "tool") and hasattr(request.tool, "name"):
|
|
466
|
+
return {
|
|
467
|
+
"tool_name": getattr(request.tool, "name", "unknown"),
|
|
468
|
+
"arguments": getattr(request, "args", {}),
|
|
469
|
+
}
|
|
470
|
+
if hasattr(request, "name"):
|
|
471
|
+
return {
|
|
472
|
+
"tool_name": request.name,
|
|
473
|
+
"arguments": getattr(
|
|
474
|
+
request, "input", getattr(request, "args", {})
|
|
475
|
+
),
|
|
476
|
+
}
|
|
422
477
|
return {}
|
|
423
478
|
except Exception as e:
|
|
424
479
|
logger.error("Error extracting tool inputs: %s", e, exc_info=True)
|
|
@@ -460,12 +515,14 @@ class PrefactorMiddleware(AgentMiddleware):
|
|
|
460
515
|
result["error"] = span_data.error.to_dict()
|
|
461
516
|
return result
|
|
462
517
|
|
|
463
|
-
def _build_tool_params(self, span_data: Any) -> dict[str, Any]:
|
|
518
|
+
def _build_tool_params(self, span_data: Any, schema_name: str) -> dict[str, Any]:
|
|
464
519
|
"""Build the payload (params) dict for a tool span."""
|
|
465
520
|
params: dict[str, Any] = {}
|
|
466
521
|
if span_data.tool_name:
|
|
467
522
|
params["tool_name"] = span_data.tool_name
|
|
468
|
-
if
|
|
523
|
+
if schema_name != "langchain:tool":
|
|
524
|
+
params["inputs"] = span_data.arguments
|
|
525
|
+
elif span_data.inputs:
|
|
469
526
|
params["inputs"] = span_data.inputs
|
|
470
527
|
return params
|
|
471
528
|
|
|
@@ -526,7 +583,15 @@ class PrefactorMiddleware(AgentMiddleware):
|
|
|
526
583
|
if loop is None or loop.is_closed():
|
|
527
584
|
return ""
|
|
528
585
|
|
|
529
|
-
|
|
586
|
+
try:
|
|
587
|
+
running_loop = asyncio.get_running_loop()
|
|
588
|
+
except RuntimeError:
|
|
589
|
+
running_loop = None
|
|
590
|
+
|
|
591
|
+
if running_loop is not None and running_loop is loop:
|
|
592
|
+
raise RuntimeError(
|
|
593
|
+
"_create_agent_span_sync must be called from a non-event-loop thread"
|
|
594
|
+
)
|
|
530
595
|
|
|
531
596
|
# Use whatever parent was set by set_parent_span() from the async side.
|
|
532
597
|
# We cannot read the SpanContextStack here because run_in_executor does
|
|
@@ -546,7 +611,7 @@ class PrefactorMiddleware(AgentMiddleware):
|
|
|
546
611
|
self._current_parent_span_id = span_id
|
|
547
612
|
return span_id
|
|
548
613
|
|
|
549
|
-
def _emit_child_span(self, span_data: Any) -> None:
|
|
614
|
+
def _emit_child_span(self, span_data: Any, schema_name: str | None = None) -> None:
|
|
550
615
|
"""Schedule a child span (LLM/tool) emission with parent tracking.
|
|
551
616
|
|
|
552
617
|
Uses the current parent span ID from _current_parent_span_id.
|
|
@@ -558,15 +623,15 @@ class PrefactorMiddleware(AgentMiddleware):
|
|
|
558
623
|
return
|
|
559
624
|
|
|
560
625
|
instance = self._instance
|
|
561
|
-
schema_name
|
|
626
|
+
schema_name = schema_name or span_data.type
|
|
562
627
|
parent_span_id = self._current_parent_span_id
|
|
563
628
|
pending = self._pending_emit_futures
|
|
564
629
|
|
|
565
630
|
if schema_name == "langchain:llm":
|
|
566
631
|
params = self._build_llm_params(span_data)
|
|
567
632
|
result = self._build_llm_result(span_data)
|
|
568
|
-
elif schema_name
|
|
569
|
-
params = self._build_tool_params(span_data)
|
|
633
|
+
elif schema_name.startswith("langchain:tool"):
|
|
634
|
+
params = self._build_tool_params(span_data, schema_name)
|
|
570
635
|
result = self._build_tool_result(span_data)
|
|
571
636
|
else:
|
|
572
637
|
return
|
|
@@ -664,6 +729,17 @@ class PrefactorMiddleware(AgentMiddleware):
|
|
|
664
729
|
async def _finish() -> None:
|
|
665
730
|
await instance.finish_span(span_id, result_payload=result)
|
|
666
731
|
|
|
732
|
+
try:
|
|
733
|
+
running_loop = asyncio.get_running_loop()
|
|
734
|
+
except RuntimeError:
|
|
735
|
+
running_loop = None
|
|
736
|
+
|
|
737
|
+
if running_loop is not None and running_loop is loop:
|
|
738
|
+
raise RuntimeError(
|
|
739
|
+
"after_agent must be called from a non-event-loop thread; "
|
|
740
|
+
"use aafter_agent for async execution"
|
|
741
|
+
)
|
|
742
|
+
|
|
667
743
|
future = asyncio.run_coroutine_threadsafe(_finish(), loop)
|
|
668
744
|
future.result(timeout=5.0)
|
|
669
745
|
|
|
@@ -718,8 +794,16 @@ class PrefactorMiddleware(AgentMiddleware):
|
|
|
718
794
|
# SpanContextStack.peek() here returns the outer workflow span
|
|
719
795
|
# because we are executing in the async event loop context.
|
|
720
796
|
self._agent_span_cm = instance.span("langchain:agent")
|
|
721
|
-
|
|
722
|
-
|
|
797
|
+
try:
|
|
798
|
+
self._agent_span_context = await self._agent_span_cm.__aenter__()
|
|
799
|
+
await self._agent_span_context.start(params)
|
|
800
|
+
except BaseException:
|
|
801
|
+
import sys
|
|
802
|
+
|
|
803
|
+
await self._agent_span_cm.__aexit__(*sys.exc_info())
|
|
804
|
+
self._agent_span_cm = None
|
|
805
|
+
self._agent_span_context = None
|
|
806
|
+
raise
|
|
723
807
|
|
|
724
808
|
self._agent_span_id = self._agent_span_context.id
|
|
725
809
|
logger.debug("Created agent span %s (async)", self._agent_span_id)
|
|
@@ -872,7 +956,9 @@ class PrefactorMiddleware(AgentMiddleware):
|
|
|
872
956
|
type="langchain:tool",
|
|
873
957
|
inputs=inputs,
|
|
874
958
|
tool_name=tool_name,
|
|
959
|
+
arguments=inputs.get("arguments", {}),
|
|
875
960
|
)
|
|
961
|
+
schema_name = self._resolve_tool_schema_name(tool_name)
|
|
876
962
|
|
|
877
963
|
try:
|
|
878
964
|
response = handler(request)
|
|
@@ -880,13 +966,13 @@ class PrefactorMiddleware(AgentMiddleware):
|
|
|
880
966
|
outputs = self._extract_tool_output(response)
|
|
881
967
|
span_data.complete(outputs=outputs)
|
|
882
968
|
|
|
883
|
-
self._emit_child_span(span_data)
|
|
969
|
+
self._emit_child_span(span_data, schema_name=schema_name)
|
|
884
970
|
logger.debug("Tool call completed (%s)", tool_name)
|
|
885
971
|
return response
|
|
886
972
|
|
|
887
973
|
except Exception as e:
|
|
888
974
|
span_data.fail(e)
|
|
889
|
-
self._emit_child_span(span_data)
|
|
975
|
+
self._emit_child_span(span_data, schema_name=schema_name)
|
|
890
976
|
logger.error("Tool call failed: %s", e, exc_info=True)
|
|
891
977
|
raise
|
|
892
978
|
|
|
@@ -914,10 +1000,12 @@ class PrefactorMiddleware(AgentMiddleware):
|
|
|
914
1000
|
type="langchain:tool",
|
|
915
1001
|
inputs=inputs,
|
|
916
1002
|
tool_name=tool_name,
|
|
1003
|
+
arguments=inputs.get("arguments", {}),
|
|
917
1004
|
)
|
|
1005
|
+
schema_name = self._resolve_tool_schema_name(tool_name)
|
|
918
1006
|
|
|
919
|
-
async with instance.span(
|
|
920
|
-
await ctx.start(self._build_tool_params(span_data))
|
|
1007
|
+
async with instance.span(schema_name) as ctx:
|
|
1008
|
+
await ctx.start(self._build_tool_params(span_data, schema_name))
|
|
921
1009
|
try:
|
|
922
1010
|
response = await handler(request)
|
|
923
1011
|
|
|
@@ -932,3 +1020,7 @@ class PrefactorMiddleware(AgentMiddleware):
|
|
|
932
1020
|
await ctx.fail(self._build_tool_result(span_data))
|
|
933
1021
|
logger.error("Tool call failed: %s", e, exc_info=True)
|
|
934
1022
|
raise
|
|
1023
|
+
|
|
1024
|
+
def _resolve_tool_schema_name(self, tool_name: str) -> str:
|
|
1025
|
+
"""Resolve a tool's configured span type, if one exists."""
|
|
1026
|
+
return self._tool_span_types.get(tool_name, "langchain:tool")
|