langgraph-api 0.12.0.dev15__py3-none-any.whl → 0.12.0.dev17__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.
- langgraph_api/__init__.py +1 -1
- langgraph_api/api/meta.py +1 -0
- langgraph_api/grpc/ops/runs.py +7 -0
- langgraph_api/models/run.py +7 -0
- langgraph_api/schema.py +3 -0
- langgraph_api/tracing_session.py +22 -0
- {langgraph_api-0.12.0.dev15.dist-info → langgraph_api-0.12.0.dev17.dist-info}/METADATA +1 -1
- {langgraph_api-0.12.0.dev15.dist-info → langgraph_api-0.12.0.dev17.dist-info}/RECORD +18 -17
- langgraph_grpc_common/conversion/store.py +236 -2
- langgraph_grpc_common/proto/core_api_pb2.py +120 -120
- langgraph_grpc_common/proto/core_api_pb2.pyi +17 -5
- langgraph_grpc_common/proto/store_pb2.py +43 -15
- langgraph_grpc_common/proto/store_pb2.pyi +414 -8
- langgraph_grpc_common/store.py +16 -16
- openapi.json +8 -1
- {langgraph_api-0.12.0.dev15.dist-info → langgraph_api-0.12.0.dev17.dist-info}/WHEEL +0 -0
- {langgraph_api-0.12.0.dev15.dist-info → langgraph_api-0.12.0.dev17.dist-info}/entry_points.txt +0 -0
- {langgraph_api-0.12.0.dev15.dist-info → langgraph_api-0.12.0.dev17.dist-info}/licenses/LICENSE +0 -0
langgraph_api/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.12.0.
|
|
1
|
+
__version__ = "0.12.0.dev17"
|
langgraph_api/api/meta.py
CHANGED
langgraph_api/grpc/ops/runs.py
CHANGED
|
@@ -383,6 +383,9 @@ def proto_to_run(proto_run: pb.Run) -> Run:
|
|
|
383
383
|
"multitask_strategy": MULTITASK_STRATEGY_FROM_PB.get(
|
|
384
384
|
proto_run.multitask_strategy
|
|
385
385
|
),
|
|
386
|
+
"langsmith_session_name": proto_run.langsmith_session_name
|
|
387
|
+
if proto_run.HasField("langsmith_session_name")
|
|
388
|
+
else None,
|
|
386
389
|
}
|
|
387
390
|
|
|
388
391
|
|
|
@@ -588,6 +591,7 @@ class Runs(Authenticated):
|
|
|
588
591
|
if_not_exists: IfNotExists = "reject",
|
|
589
592
|
after_seconds: int = 0,
|
|
590
593
|
ctx: Any = None,
|
|
594
|
+
langsmith_session_name: str | None = None,
|
|
591
595
|
) -> AsyncIterator[Run]:
|
|
592
596
|
"""Create a run."""
|
|
593
597
|
metadata = metadata or {}
|
|
@@ -662,6 +666,9 @@ class Runs(Authenticated):
|
|
|
662
666
|
if after_seconds > 0:
|
|
663
667
|
request_kwargs["after_seconds"] = int(after_seconds)
|
|
664
668
|
|
|
669
|
+
if langsmith_session_name is not None:
|
|
670
|
+
request_kwargs["langsmith_session_name"] = langsmith_session_name
|
|
671
|
+
|
|
665
672
|
# Build encryption context for Go layer
|
|
666
673
|
enc_ctx = build_encryption_context("run")
|
|
667
674
|
if enc_ctx is not None:
|
langgraph_api/models/run.py
CHANGED
|
@@ -10,6 +10,7 @@ import structlog
|
|
|
10
10
|
from starlette.exceptions import HTTPException
|
|
11
11
|
from typing_extensions import TypedDict
|
|
12
12
|
|
|
13
|
+
from langgraph_api import config as api_config
|
|
13
14
|
from langgraph_api.auth.noop import UnauthenticatedUser
|
|
14
15
|
from langgraph_api.encryption.middleware import encrypt_request
|
|
15
16
|
from langgraph_api.encryption.shared import (
|
|
@@ -35,6 +36,7 @@ from langgraph_api.schema import (
|
|
|
35
36
|
RunCommand,
|
|
36
37
|
StreamMode,
|
|
37
38
|
)
|
|
39
|
+
from langgraph_api.tracing_session import resolve_tracing_session_name
|
|
38
40
|
from langgraph_api.utils import AsyncConnectionProto, get_auth_ctx, get_user_id
|
|
39
41
|
from langgraph_api.utils.headers import get_configurable_headers
|
|
40
42
|
from langgraph_api.utils.uuids import uuid7
|
|
@@ -300,6 +302,10 @@ async def create_valid_run(
|
|
|
300
302
|
if webhook := payload.get("webhook"):
|
|
301
303
|
await validate_webhook_url_or_raise(str(webhook))
|
|
302
304
|
|
|
305
|
+
langsmith_session_name = (
|
|
306
|
+
resolve_tracing_session_name(payload) if api_config.TRACING else None
|
|
307
|
+
)
|
|
308
|
+
|
|
303
309
|
# We can't pass payload directly because config and context have
|
|
304
310
|
# been modified above (with auth context, checkpoint info, etc.)
|
|
305
311
|
fields_to_encrypt = {
|
|
@@ -346,6 +352,7 @@ async def create_valid_run(
|
|
|
346
352
|
prevent_insert_if_inflight=prevent_insert_if_inflight,
|
|
347
353
|
after_seconds=after_seconds,
|
|
348
354
|
if_not_exists=if_not_exists,
|
|
355
|
+
langsmith_session_name=langsmith_session_name,
|
|
349
356
|
)
|
|
350
357
|
run_ = await run_coro
|
|
351
358
|
|
langgraph_api/schema.py
CHANGED
|
@@ -255,6 +255,8 @@ class Run(TypedDict):
|
|
|
255
255
|
"""The run kwargs."""
|
|
256
256
|
multitask_strategy: MultitaskStrategy
|
|
257
257
|
"""Strategy to handle concurrent runs on the same thread."""
|
|
258
|
+
langsmith_session_name: str | None
|
|
259
|
+
"""LangSmith tracing session (project) name for this run, if tracing is enabled."""
|
|
258
260
|
|
|
259
261
|
|
|
260
262
|
class RunSend(TypedDict):
|
|
@@ -359,6 +361,7 @@ RunSelectField = Literal[
|
|
|
359
361
|
"metadata",
|
|
360
362
|
"kwargs",
|
|
361
363
|
"multitask_strategy",
|
|
364
|
+
"langsmith_session_name",
|
|
362
365
|
]
|
|
363
366
|
RUN_FIELDS: set[str] = set(RunSelectField.__args__)
|
|
364
367
|
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""LangSmith tracing session helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Mapping
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from langsmith.utils import get_tracer_project
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def resolve_tracing_session_name(payload: Mapping[str, Any]) -> str:
|
|
12
|
+
"""Return the LangSmith session (project) name for a run.
|
|
13
|
+
|
|
14
|
+
Per-run ``langsmith_tracer.project_name`` overrides the deployment default
|
|
15
|
+
from ``LANGSMITH_PROJECT`` / ``LANGCHAIN_PROJECT``.
|
|
16
|
+
"""
|
|
17
|
+
ls_tracing = payload.get("langsmith_tracer")
|
|
18
|
+
if isinstance(ls_tracing, Mapping):
|
|
19
|
+
project_name = ls_tracing.get("project_name")
|
|
20
|
+
if isinstance(project_name, str) and project_name:
|
|
21
|
+
return project_name
|
|
22
|
+
return get_tracer_project()
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
langgraph_api/__init__.py,sha256=
|
|
1
|
+
langgraph_api/__init__.py,sha256=7OXdM1PuKj42WlPEpYsH7Ln8cgRnfN3ECCN17_8jZvk,29
|
|
2
2
|
langgraph_api/_factory_utils.py,sha256=5JsiJbg_YocVSryN2jwoZTg03-eyymlWMK6sKCmXwz0,5756
|
|
3
3
|
langgraph_api/asgi_transport.py,sha256=XApY3lIWBZTMbbsl8dDJzl0cLGirmAGE0SifqZUnXvs,11896
|
|
4
4
|
langgraph_api/asyncio.py,sha256=smqyAoO9nIyPPQRw1IVSCh85fwoZP4PEo-5dDhxGruc,10676
|
|
@@ -21,7 +21,7 @@ langgraph_api/patch.py,sha256=ViUknYvyQWS6y0f5XuaEoci2qB_mQv8vZl-oaUxsI6M,1448
|
|
|
21
21
|
langgraph_api/queue_entrypoint.py,sha256=-9YnY_GhmDxEiGCc3k-7UqRKK_M3dPriits2iGgYlgU,11327
|
|
22
22
|
langgraph_api/release_tags.py,sha256=BjgGj2vFcA7I0MDRXLw1sUA4jquz-DaKVS0Eq-dYSjE,9091
|
|
23
23
|
langgraph_api/route.py,sha256=_KE8A8Q-J-QfqjGlyM2Kc6n5cirmgt8xmI5-pI8kVEE,8837
|
|
24
|
-
langgraph_api/schema.py,sha256=
|
|
24
|
+
langgraph_api/schema.py,sha256=4VhH4V9RmvBqO9MMPbaKk80PUQrPFyTMjDTT8nAZZ6w,15257
|
|
25
25
|
langgraph_api/self_hosted_logs.py,sha256=FoUkPdtpt-nuEhejne8o1Q2phE9CccoHdoR_PvXPcBU,4442
|
|
26
26
|
langgraph_api/self_hosted_metrics.py,sha256=pWsQQ-2ukoFIbmVfzNOSkwCqZ5Cnts6pRSWTII44Ll4,16844
|
|
27
27
|
langgraph_api/serde.py,sha256=V3fO9bkUOlBX3okw5Qi31nlcr59fcuXMgL7DHNyarZY,8855
|
|
@@ -32,6 +32,7 @@ langgraph_api/store.py,sha256=i-jVfWzDEmPESVofh1tNWbo0HMLiLz_BSKKV4QjN-dE,5027
|
|
|
32
32
|
langgraph_api/stream.py,sha256=rJIGe8rEeS6QxQfsDKomUBRED6ZPIF-ALtx4D8Gw0JQ,33348
|
|
33
33
|
langgraph_api/stream_v2.py,sha256=KCIsPf6gs_jrIdZ8D2VTEi5c5p5aRNpVNhMP4kzjp2g,10824
|
|
34
34
|
langgraph_api/traceblock.py,sha256=GhgAtLhAQ8Z8LHtRQq3par-rfPpRBQp2Q7WBACJq1NI,677
|
|
35
|
+
langgraph_api/tracing_session.py,sha256=gmfv-BW1XRar6suVur1P4rTVKWkjElDEb2hhkBduWOs,728
|
|
35
36
|
langgraph_api/validation.py,sha256=XyeKyt7jAICmIlT_b0J0mv2YbwIbNoe4m6zEmfk9gOA,14657
|
|
36
37
|
langgraph_api/webhook.py,sha256=qXEtkE6orek2POeOQmPRsEarJgXIYp-LBrZB-OwITxc,9572
|
|
37
38
|
langgraph_api/worker.py,sha256=HGirbQ1i1hxaayORoihsQ6JMVAq3VsrC8CUxAHwY9t0,21151
|
|
@@ -42,7 +43,7 @@ langgraph_api/api/__init__.py,sha256=Zu1ew3dxYZu7cLRAjn-6HcYmtuQBdihlVFMKMJ77Y3c
|
|
|
42
43
|
langgraph_api/api/a2a.py,sha256=VPllgqfoLUQD6Eqob3RjcegjtKgLhphNGTrTqbNLoIY,95135
|
|
43
44
|
langgraph_api/api/assistants.py,sha256=4v1TpkeeSF7vFrbnOKIvh7BY4K0WamzEdMeTAzwRElE,20786
|
|
44
45
|
langgraph_api/api/event_streaming.py,sha256=nvoaKz4QGklX5YUmY9WQ3vSwhQ1Q81QeQWNR8aEXUz8,17571
|
|
45
|
-
langgraph_api/api/meta.py,sha256=
|
|
46
|
+
langgraph_api/api/meta.py,sha256=5s017GfMs-Ghq6Z5K6yW4UjbVFzCsmeXKn4UEJmfs98,4757
|
|
46
47
|
langgraph_api/api/openapi.py,sha256=Zkdlb9mjrQyHro1TtrDIWVuaBDovxx-uGWJ1fZMOg54,12604
|
|
47
48
|
langgraph_api/api/profile.py,sha256=CA1ZkHALOuP8orYTICnEhcG_JnnA2wnyjbWyeb117jA,3455
|
|
48
49
|
langgraph_api/api/runs.py,sha256=h5droLgaz_aAyILCRJIpbj2KH1PbijCeXcggOSa3Zww,35178
|
|
@@ -90,7 +91,7 @@ langgraph_api/grpc/ops/__init__.py,sha256=ACO-Cp7ygehNfOG1ucSH8iMw3hVXbLluxiqczG
|
|
|
90
91
|
langgraph_api/grpc/ops/assistants.py,sha256=00vY7Dd7BcXaVp_6UrKZIb5b4tQ8Lm5MxJcEpyVPfo0,13902
|
|
91
92
|
langgraph_api/grpc/ops/cache.py,sha256=MoPAmSuOwbt2Cud1YZJm_YJeaDHPFy1IiV4m0PDImgg,1399
|
|
92
93
|
langgraph_api/grpc/ops/crons.py,sha256=oGPW9qW-J4H9baX7Jsee1opsFnXfThHLHh_N47mDf9s,19596
|
|
93
|
-
langgraph_api/grpc/ops/runs.py,sha256=
|
|
94
|
+
langgraph_api/grpc/ops/runs.py,sha256=4-Mp-4MgDAn8uerLrOpmXuUqOdfcnzSmb3hSRlO8HhM,41798
|
|
94
95
|
langgraph_api/grpc/ops/threads.py,sha256=SJraV838nZ0m6wRrCia7sLYh8KK1WfY7x5xmcspZLek,48140
|
|
95
96
|
langgraph_api/grpc/servicers/__init__.py,sha256=l8yqTA_gMbIj0xHw5-RZYo0JbjL-EpFD8RxPMgW_-bA,422
|
|
96
97
|
langgraph_api/grpc/servicers/checkpointer.py,sha256=aeB2hzsDxbb9i-ildx-kylpDxaYtpAUSG6rphOzbDOs,10482
|
|
@@ -130,7 +131,7 @@ langgraph_api/middleware/http_logger.py,sha256=jjqLBPqoGRC1UfB2VYKPY2tkq6gT7Rm88
|
|
|
130
131
|
langgraph_api/middleware/private_network.py,sha256=eQEzWI8epBNUCiNsMu9O27ofHBQ45M0p2OZy5YdUYos,2097
|
|
131
132
|
langgraph_api/middleware/request_id.py,sha256=-p230Q5jDJAJLmSZRqQvB4dFFkJS9B4Vwg6pUgQtI24,1259
|
|
132
133
|
langgraph_api/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
133
|
-
langgraph_api/models/run.py,sha256=
|
|
134
|
+
langgraph_api/models/run.py,sha256=1jA7HywYT95ekz_gMBkR0dnzR_1gdl5vhqDaackAlwA,16082
|
|
134
135
|
langgraph_api/timing/__init__.py,sha256=lWXHCGh71I8ouF0wUznYRMKHAQ-8VQHF9gf9jkeV5dg,526
|
|
135
136
|
langgraph_api/timing/profiler.py,sha256=_n0qJYJMjCGo95RDuH5z8FIzePGNSNbnm3cvGmcgn6A,6186
|
|
136
137
|
langgraph_api/timing/timer.py,sha256=lC_xu4rlejkFPxqV6k_FD4dWtjpT9cEICBP333hAxTg,9404
|
|
@@ -161,17 +162,17 @@ langgraph_runtime/store.py,sha256=7mowndlsIroGHv3NpTSOZDJR0lCuaYMBoTnTrewjslw,11
|
|
|
161
162
|
LICENSE,sha256=ZPwVR73Biwm3sK6vR54djCrhaRiM4cAD2zvOQZV8Xis,3859
|
|
162
163
|
hatch_build.py,sha256=Nn9N-EFfVqmH5q3iCUrhqX6zc7qT1jLI3gWSBN2xgYQ,1738
|
|
163
164
|
logging.json,sha256=sBy8HDDPuucpLbLUD6e8t5fsiQjJoklZsbVHi2qQ7aQ,367
|
|
164
|
-
openapi.json,sha256=
|
|
165
|
+
openapi.json,sha256=_gcvdkH6zWjzvEZ4YPGpJ0AMzTwM1cbJlpVgJSxzM8I,219880
|
|
165
166
|
langgraph_grpc_common/__init__.py,sha256=sO7jFHekQxt6zZPhTPZAWLYLB_S8w7-VuJ7NyWFlFNo,97
|
|
166
167
|
langgraph_grpc_common/checkpointer.py,sha256=u3ERFgqnUCpCYpOSxKR_6Jvo_tR4yDvq7qU-EL61bSg,14022
|
|
167
168
|
langgraph_grpc_common/serde.py,sha256=pxIhgxtrXtwWVhAlLE2LAeSAuKk2ZHtnvAP3DRDT2Qw,6184
|
|
168
|
-
langgraph_grpc_common/store.py,sha256=
|
|
169
|
+
langgraph_grpc_common/store.py,sha256=SizRCSOoNVbCHzsNuyuzC9dQdKi4x4WOV5S7pqYWdp8,3846
|
|
169
170
|
langgraph_grpc_common/conversion/__init__.py,sha256=pLsOrRtmF9YkgmCPhcfym6lYOB7PCJTRItyo1YK_AT8,208
|
|
170
171
|
langgraph_grpc_common/conversion/_compat.py,sha256=MIEo69I7tfaPskunPGwBCKheksLwxpw-z-4O3hvO9og,4010
|
|
171
172
|
langgraph_grpc_common/conversion/checkpoint.py,sha256=Ty81cijafeal7blzzXbYFjWpu9iXVb_spFLBZ2FvmgI,12045
|
|
172
173
|
langgraph_grpc_common/conversion/config.py,sha256=HjlHRX-_GpKa1m0RWVL1PEZQCvA3w_8wKVYrtaT06do,13015
|
|
173
174
|
langgraph_grpc_common/conversion/durability.py,sha256=FWhqjzVN4JrrsAL9AwsJkvIorQGNRl14w97Va-WZRk0,991
|
|
174
|
-
langgraph_grpc_common/conversion/store.py,sha256=
|
|
175
|
+
langgraph_grpc_common/conversion/store.py,sha256=ctWDdgpJHEkkCeguWSwmISlS8OJLJLP9srwib-Hi_OE,15211
|
|
175
176
|
langgraph_grpc_common/conversion/struct.py,sha256=yk4jpPdywzn8UosAohun21lU5D_IIglOY9rkBhiG-gA,2072
|
|
176
177
|
langgraph_grpc_common/conversion/value.py,sha256=eZ9fDqKje9tKsO8A_6f0BPSI7lt94Vzuu3NNEEYLFBM,8343
|
|
177
178
|
langgraph_grpc_common/proto/__init__.py,sha256=eBejrrULiDdasi3_Hga_AJW8ZPMkjKyyAeW9Ey640Ks,1375
|
|
@@ -179,8 +180,8 @@ langgraph_grpc_common/proto/checkpointer_pb2.py,sha256=c-gaVwNHm7sUzThJDzumME2M_
|
|
|
179
180
|
langgraph_grpc_common/proto/checkpointer_pb2.pyi,sha256=rGUxQyNFifmu8OPy0S418sCWSBQY_CVckobPXtFfZfs,20763
|
|
180
181
|
langgraph_grpc_common/proto/checkpointer_pb2_grpc.py,sha256=MLhZ7M4g9gjfXZqWenk93oO2uIZSybbFoaeU8bauFPA,20176
|
|
181
182
|
langgraph_grpc_common/proto/checkpointer_pb2_grpc.pyi,sha256=8fPL90WjHnMaREG7l9qYt5fQePeT8YFWQ0qVy56QtYw,10210
|
|
182
|
-
langgraph_grpc_common/proto/core_api_pb2.py,sha256=
|
|
183
|
-
langgraph_grpc_common/proto/core_api_pb2.pyi,sha256=
|
|
183
|
+
langgraph_grpc_common/proto/core_api_pb2.py,sha256=ggBZW9ywqm2Y4lgpWURuxd0bfit_JUMb1zSZ11UMylc,51014
|
|
184
|
+
langgraph_grpc_common/proto/core_api_pb2.pyi,sha256=1AtbsmQcOzwYSODInI9mEioWcBCtcc3VNBra7FEXICw,207984
|
|
184
185
|
langgraph_grpc_common/proto/core_api_pb2_grpc.py,sha256=zIfWY664xF4r9c48Ys1OcYN4dEmtIWAwYiCzP7gsyLI,81342
|
|
185
186
|
langgraph_grpc_common/proto/core_api_pb2_grpc.pyi,sha256=XLu2-5YZHm9QyZWjWsnUWe7uN6n1-avVvsyR6N5D3fs,29325
|
|
186
187
|
langgraph_grpc_common/proto/encryption_pb2.py,sha256=W9Zs5gclBzKfwHfKDB_83tFfabR9_KK2Lq81xVYleMQ,3355
|
|
@@ -231,12 +232,12 @@ langgraph_grpc_common/proto/errors_pb2.py,sha256=JI6x-vBK1AE7DHZ5DQwN1mZWF6C4xTR
|
|
|
231
232
|
langgraph_grpc_common/proto/errors_pb2.pyi,sha256=rd3-BYUH8V-aO66taL7OOblaLgdrDtf1Vcd38GUoVVM,2181
|
|
232
233
|
langgraph_grpc_common/proto/errors_pb2_grpc.py,sha256=2-LwQ0OPGo-NtC0269q7Fw6GPBxnTLYWq3xP5Eq0_YA,886
|
|
233
234
|
langgraph_grpc_common/proto/errors_pb2_grpc.pyi,sha256=uC9Wnq6uyg488QiONpJ0ba1s_iouQCOYsjd_FDd1XUM,495
|
|
234
|
-
langgraph_grpc_common/proto/store_pb2.py,sha256=
|
|
235
|
-
langgraph_grpc_common/proto/store_pb2.pyi,sha256=
|
|
235
|
+
langgraph_grpc_common/proto/store_pb2.py,sha256=uAYM7OcdEOK12iRcPYihC2suSeCo_QdbpNSWfDIkXf8,7652
|
|
236
|
+
langgraph_grpc_common/proto/store_pb2.pyi,sha256=_dcxyxV8moR6H67qU42r2h2p_Ub-PZSHs3OoS9KMdSY,27782
|
|
236
237
|
langgraph_grpc_common/proto/store_pb2_grpc.py,sha256=wSD65QgFkYTkPTHcf7VhyI2FQ7UH_RgIf3vQW3VYlsM,3324
|
|
237
238
|
langgraph_grpc_common/proto/store_pb2_grpc.pyi,sha256=oS1h2cDoq2OjyM8xazcmc8LwRDC5oCAD0zto2QUmPQw,2027
|
|
238
|
-
langgraph_api-0.12.0.
|
|
239
|
-
langgraph_api-0.12.0.
|
|
240
|
-
langgraph_api-0.12.0.
|
|
241
|
-
langgraph_api-0.12.0.
|
|
242
|
-
langgraph_api-0.12.0.
|
|
239
|
+
langgraph_api-0.12.0.dev17.dist-info/METADATA,sha256=7Ir52CwtjuY0ASAp6p09CkQVEUvxfqmb9mQoN7o-cgE,4631
|
|
240
|
+
langgraph_api-0.12.0.dev17.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
241
|
+
langgraph_api-0.12.0.dev17.dist-info/entry_points.txt,sha256=hGedv8n7cgi41PypMfinwS_HfCwA7xJIfS0jAp8htV8,78
|
|
242
|
+
langgraph_api-0.12.0.dev17.dist-info/licenses/LICENSE,sha256=ZPwVR73Biwm3sK6vR54djCrhaRiM4cAD2zvOQZV8Xis,3859
|
|
243
|
+
langgraph_api-0.12.0.dev17.dist-info/RECORD,,
|
|
@@ -11,9 +11,13 @@ from langgraph.store.base import (
|
|
|
11
11
|
GetOp,
|
|
12
12
|
IndexConfig,
|
|
13
13
|
Item,
|
|
14
|
+
ListNamespacesOp,
|
|
15
|
+
MatchCondition,
|
|
14
16
|
Op,
|
|
15
17
|
PutOp,
|
|
16
18
|
Result,
|
|
19
|
+
SearchItem,
|
|
20
|
+
SearchOp,
|
|
17
21
|
ensure_embeddings,
|
|
18
22
|
get_text_at_path,
|
|
19
23
|
tokenize_path,
|
|
@@ -64,10 +68,92 @@ def _ttl_to_proto_minutes(ttl: float | None) -> int:
|
|
|
64
68
|
return int(ttl)
|
|
65
69
|
|
|
66
70
|
|
|
71
|
+
# Ordered comparisons compare the *text* form of the field (value->>key); $eq/$ne
|
|
72
|
+
# are handled separately as jsonb comparisons.
|
|
73
|
+
_FILTER_OP_BY_NAME = {
|
|
74
|
+
"$gt": store_pb2.FILTER_OP_GT,
|
|
75
|
+
"$gte": store_pb2.FILTER_OP_GTE,
|
|
76
|
+
"$lt": store_pb2.FILTER_OP_LT,
|
|
77
|
+
"$lte": store_pb2.FILTER_OP_LTE,
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
# pgvector distance / column types come from the index config as strings; map
|
|
81
|
+
# them to the wire enums (see store.proto DistanceType / VectorType).
|
|
82
|
+
_DISTANCE_TYPE_BY_NAME = {
|
|
83
|
+
"cosine": store_pb2.DISTANCE_TYPE_COSINE,
|
|
84
|
+
"l2": store_pb2.DISTANCE_TYPE_L2,
|
|
85
|
+
"inner_product": store_pb2.DISTANCE_TYPE_INNER_PRODUCT,
|
|
86
|
+
}
|
|
87
|
+
_VECTOR_TYPE_BY_NAME = {
|
|
88
|
+
"vector": store_pb2.VECTOR_TYPE_VECTOR,
|
|
89
|
+
"halfvec": store_pb2.VECTOR_TYPE_HALFVEC,
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
# ListNamespaces match type: langgraph uses the strings "prefix"/"suffix".
|
|
93
|
+
_MATCH_TYPE_BY_NAME = {
|
|
94
|
+
"prefix": store_pb2.MATCH_TYPE_PREFIX,
|
|
95
|
+
"suffix": store_pb2.MATCH_TYPE_SUFFIX,
|
|
96
|
+
}
|
|
97
|
+
_MATCH_TYPE_TO_NAME = {op: name for name, op in _MATCH_TYPE_BY_NAME.items()}
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _match_type_to_proto(match_type: str) -> int:
|
|
101
|
+
proto = _MATCH_TYPE_BY_NAME.get(match_type)
|
|
102
|
+
if proto is None:
|
|
103
|
+
raise ValueError(f"Unsupported list_namespaces match_type: {match_type!r}")
|
|
104
|
+
return proto
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _filter_to_conditions(
|
|
108
|
+
filter_dict: dict[str, Any],
|
|
109
|
+
) -> list[store_pb2.FilterCondition]:
|
|
110
|
+
"""Flatten a langgraph filter dict into typed FilterConditions.
|
|
111
|
+
|
|
112
|
+
A plain value is an equality match; a dict is a set of operator comparisons.
|
|
113
|
+
$eq/$ne compare as jsonb; $gt/$gte/$lt/$lte compare on the text form of the
|
|
114
|
+
value (str(value), produced here so the store never re-derives it). Mirrors
|
|
115
|
+
OSS PostgresStore._get_filter_condition, done in Python where the DSL is
|
|
116
|
+
native so the store only maps op -> SQL.
|
|
117
|
+
"""
|
|
118
|
+
conditions: list[store_pb2.FilterCondition] = []
|
|
119
|
+
for key, value in filter_dict.items():
|
|
120
|
+
if isinstance(value, dict):
|
|
121
|
+
for op_name, op_val in value.items():
|
|
122
|
+
if op_name in ("$eq", "$ne"):
|
|
123
|
+
op = (
|
|
124
|
+
store_pb2.FILTER_OP_EQ
|
|
125
|
+
if op_name == "$eq"
|
|
126
|
+
else store_pb2.FILTER_OP_NE
|
|
127
|
+
)
|
|
128
|
+
conditions.append(
|
|
129
|
+
store_pb2.FilterCondition(
|
|
130
|
+
key=key, op=op, operand=orjson.dumps(op_val).decode()
|
|
131
|
+
)
|
|
132
|
+
)
|
|
133
|
+
elif op_name in _FILTER_OP_BY_NAME:
|
|
134
|
+
conditions.append(
|
|
135
|
+
store_pb2.FilterCondition(
|
|
136
|
+
key=key, op=_FILTER_OP_BY_NAME[op_name], operand=str(op_val)
|
|
137
|
+
)
|
|
138
|
+
)
|
|
139
|
+
else:
|
|
140
|
+
raise ValueError(f"Unsupported filter operator: {op_name}")
|
|
141
|
+
else:
|
|
142
|
+
conditions.append(
|
|
143
|
+
store_pb2.FilterCondition(
|
|
144
|
+
key=key,
|
|
145
|
+
op=store_pb2.FILTER_OP_EQ,
|
|
146
|
+
operand=orjson.dumps(value).decode(),
|
|
147
|
+
)
|
|
148
|
+
)
|
|
149
|
+
return conditions
|
|
150
|
+
|
|
151
|
+
|
|
67
152
|
def op_to_proto(
|
|
68
153
|
op: Op,
|
|
69
154
|
*,
|
|
70
155
|
vectors: Sequence[store_pb2.VectorValue] | None = None,
|
|
156
|
+
search_vector: store_pb2.VectorSearch | None = None,
|
|
71
157
|
) -> store_pb2.Op:
|
|
72
158
|
if isinstance(op, GetOp):
|
|
73
159
|
return store_pb2.Op(
|
|
@@ -78,9 +164,14 @@ def op_to_proto(
|
|
|
78
164
|
)
|
|
79
165
|
)
|
|
80
166
|
if isinstance(op, PutOp):
|
|
167
|
+
# A PutOp with value=None is a delete in OSS; on the gRPC path it maps to
|
|
168
|
+
# an explicit DeleteOp so an empty value can't be mistaken for a delete.
|
|
81
169
|
if op.value is None:
|
|
82
|
-
|
|
83
|
-
|
|
170
|
+
return store_pb2.Op(
|
|
171
|
+
delete=store_pb2.DeleteOp(
|
|
172
|
+
namespace=list(op.namespace),
|
|
173
|
+
key=str(op.key),
|
|
174
|
+
)
|
|
84
175
|
)
|
|
85
176
|
put_kwargs: dict[str, Any] = {
|
|
86
177
|
"namespace": list(op.namespace),
|
|
@@ -91,6 +182,36 @@ def op_to_proto(
|
|
|
91
182
|
if vectors is not None:
|
|
92
183
|
put_kwargs["vectors"] = list(vectors)
|
|
93
184
|
return store_pb2.Op(put=store_pb2.PutOp(**put_kwargs))
|
|
185
|
+
if isinstance(op, SearchOp):
|
|
186
|
+
# The natural-language query itself is not sent: Python embeds it into
|
|
187
|
+
# `search_vector`, which is the only semantic-search signal the store needs.
|
|
188
|
+
search_kwargs: dict[str, Any] = {
|
|
189
|
+
"namespace_prefix": list(op.namespace_prefix),
|
|
190
|
+
"limit": op.limit,
|
|
191
|
+
"offset": op.offset,
|
|
192
|
+
"refreshTTL": op.refresh_ttl,
|
|
193
|
+
}
|
|
194
|
+
if op.filter:
|
|
195
|
+
search_kwargs["filter"] = _filter_to_conditions(op.filter)
|
|
196
|
+
if search_vector is not None:
|
|
197
|
+
search_kwargs["vector"] = search_vector
|
|
198
|
+
return store_pb2.Op(search=store_pb2.SearchOp(**search_kwargs))
|
|
199
|
+
if isinstance(op, ListNamespacesOp):
|
|
200
|
+
list_kwargs: dict[str, Any] = {
|
|
201
|
+
"limit": op.limit,
|
|
202
|
+
"offset": op.offset,
|
|
203
|
+
}
|
|
204
|
+
if op.match_conditions:
|
|
205
|
+
list_kwargs["match_conditions"] = [
|
|
206
|
+
store_pb2.MatchCondition(
|
|
207
|
+
match_type=_match_type_to_proto(mc.match_type),
|
|
208
|
+
path=list(mc.path),
|
|
209
|
+
)
|
|
210
|
+
for mc in op.match_conditions
|
|
211
|
+
]
|
|
212
|
+
if op.max_depth is not None:
|
|
213
|
+
list_kwargs["max_depth"] = op.max_depth
|
|
214
|
+
return store_pb2.Op(list_namespaces=store_pb2.ListNamespacesOp(**list_kwargs))
|
|
94
215
|
raise TypeError(f"unsupported op: {type(op).__name__}")
|
|
95
216
|
|
|
96
217
|
|
|
@@ -114,6 +235,35 @@ def op_from_proto(msg: store_pb2.Op) -> Op:
|
|
|
114
235
|
value=orjson.loads(p.value_json),
|
|
115
236
|
ttl=ttl,
|
|
116
237
|
)
|
|
238
|
+
if kind == "delete":
|
|
239
|
+
d = msg.delete
|
|
240
|
+
return PutOp(namespace=tuple(d.namespace), key=d.key, value=None)
|
|
241
|
+
if kind == "search":
|
|
242
|
+
s = msg.search
|
|
243
|
+
# `query` (embedded into `vector`) and `filter` (flattened to typed
|
|
244
|
+
# conditions) are one-directional encodings, not reconstructed here; the
|
|
245
|
+
# store consumes them and Python never decodes a SearchOp in production.
|
|
246
|
+
return SearchOp(
|
|
247
|
+
namespace_prefix=tuple(s.namespace_prefix),
|
|
248
|
+
limit=s.limit,
|
|
249
|
+
offset=s.offset,
|
|
250
|
+
refresh_ttl=s.refreshTTL,
|
|
251
|
+
)
|
|
252
|
+
if kind == "list_namespaces":
|
|
253
|
+
ln = msg.list_namespaces
|
|
254
|
+
return ListNamespacesOp(
|
|
255
|
+
match_conditions=tuple(
|
|
256
|
+
MatchCondition(
|
|
257
|
+
match_type=_MATCH_TYPE_TO_NAME[mc.match_type],
|
|
258
|
+
path=tuple(mc.path),
|
|
259
|
+
)
|
|
260
|
+
for mc in ln.match_conditions
|
|
261
|
+
)
|
|
262
|
+
or None,
|
|
263
|
+
max_depth=ln.max_depth if ln.HasField("max_depth") else None,
|
|
264
|
+
limit=ln.limit,
|
|
265
|
+
offset=ln.offset,
|
|
266
|
+
)
|
|
117
267
|
raise ValueError("empty Op message (no oneof set)")
|
|
118
268
|
|
|
119
269
|
|
|
@@ -128,6 +278,20 @@ def result_to_proto(result: Result, op: Op) -> store_pb2.OpResult:
|
|
|
128
278
|
return store_pb2.OpResult(item=_item_to_proto(result))
|
|
129
279
|
if isinstance(op, PutOp):
|
|
130
280
|
return store_pb2.OpResult(none=empty_pb2.Empty())
|
|
281
|
+
if isinstance(op, SearchOp):
|
|
282
|
+
items = cast("list[SearchItem]", result or [])
|
|
283
|
+
return store_pb2.OpResult(
|
|
284
|
+
search=store_pb2.SearchResult(
|
|
285
|
+
items=[_search_item_to_proto(item) for item in items]
|
|
286
|
+
)
|
|
287
|
+
)
|
|
288
|
+
if isinstance(op, ListNamespacesOp):
|
|
289
|
+
namespaces = cast("list[tuple[str, ...]]", result or [])
|
|
290
|
+
return store_pb2.OpResult(
|
|
291
|
+
list_namespaces=store_pb2.ListNamespacesResult(
|
|
292
|
+
namespaces=[store_pb2.Namespace(parts=list(ns)) for ns in namespaces]
|
|
293
|
+
)
|
|
294
|
+
)
|
|
131
295
|
raise TypeError(f"unsupported op for result encoding: {type(op).__name__}")
|
|
132
296
|
|
|
133
297
|
|
|
@@ -137,6 +301,10 @@ def result_from_proto(msg: store_pb2.OpResult) -> Result:
|
|
|
137
301
|
return None
|
|
138
302
|
if kind == "item":
|
|
139
303
|
return _item_from_proto(msg.item)
|
|
304
|
+
if kind == "search":
|
|
305
|
+
return [_search_item_from_proto(item) for item in msg.search.items]
|
|
306
|
+
if kind == "list_namespaces":
|
|
307
|
+
return [tuple(ns.parts) for ns in msg.list_namespaces.namespaces]
|
|
140
308
|
raise ValueError("empty OpResult message (no oneof set)")
|
|
141
309
|
|
|
142
310
|
|
|
@@ -160,6 +328,30 @@ def _item_from_proto(msg: store_pb2.Item) -> Item:
|
|
|
160
328
|
)
|
|
161
329
|
|
|
162
330
|
|
|
331
|
+
def _search_item_to_proto(item: SearchItem) -> store_pb2.SearchItem:
|
|
332
|
+
kwargs: dict[str, Any] = {
|
|
333
|
+
"namespace": list(item.namespace),
|
|
334
|
+
"key": item.key,
|
|
335
|
+
"value_json": orjson.dumps(item.value),
|
|
336
|
+
"created_at": _format_dt(item.created_at),
|
|
337
|
+
"updated_at": _format_dt(item.updated_at),
|
|
338
|
+
}
|
|
339
|
+
if item.score is not None:
|
|
340
|
+
kwargs["score"] = item.score
|
|
341
|
+
return store_pb2.SearchItem(**kwargs)
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def _search_item_from_proto(msg: store_pb2.SearchItem) -> SearchItem:
|
|
345
|
+
return SearchItem(
|
|
346
|
+
namespace=tuple(msg.namespace),
|
|
347
|
+
key=msg.key,
|
|
348
|
+
value=orjson.loads(msg.value_json),
|
|
349
|
+
created_at=datetime.fromisoformat(msg.created_at),
|
|
350
|
+
updated_at=datetime.fromisoformat(msg.updated_at),
|
|
351
|
+
score=msg.score if msg.HasField("score") else None,
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
|
|
163
355
|
def _format_dt(dt: datetime) -> str:
|
|
164
356
|
return dt.isoformat()
|
|
165
357
|
|
|
@@ -200,6 +392,48 @@ async def vectors_for_put_op(
|
|
|
200
392
|
]
|
|
201
393
|
|
|
202
394
|
|
|
395
|
+
async def query_embedding_for_search_op(
|
|
396
|
+
op: SearchOp,
|
|
397
|
+
index_config: IndexConfig,
|
|
398
|
+
embeddings: Embeddings,
|
|
399
|
+
) -> store_pb2.VectorSearch | None:
|
|
400
|
+
"""Embed a SearchOp's natural-language query for semantic search.
|
|
401
|
+
|
|
402
|
+
Returns None when the op has no query (a plain filter/pagination search).
|
|
403
|
+
Distance params are read from the index config and sent alongside the
|
|
404
|
+
embedding so the Go store stays stateless.
|
|
405
|
+
"""
|
|
406
|
+
if not op.query:
|
|
407
|
+
return None
|
|
408
|
+
|
|
409
|
+
vectors = await embeddings.aembed_documents([op.query])
|
|
410
|
+
if not vectors:
|
|
411
|
+
return None
|
|
412
|
+
|
|
413
|
+
cfg = cast("dict[str, Any]", index_config)
|
|
414
|
+
distance_type = cfg.get("distance_type", "cosine")
|
|
415
|
+
vectors_per_doc = cfg.get("__estimated_num_vectors", 1)
|
|
416
|
+
vector_type = cfg.get("ann_index_config", {}).get("vector_type", "vector")
|
|
417
|
+
|
|
418
|
+
if distance_type not in _DISTANCE_TYPE_BY_NAME:
|
|
419
|
+
raise ValueError(
|
|
420
|
+
f"gRPC store semantic search does not support distance_type={distance_type!r} "
|
|
421
|
+
"(only 'cosine', 'l2', 'inner_product')"
|
|
422
|
+
)
|
|
423
|
+
if vector_type not in _VECTOR_TYPE_BY_NAME:
|
|
424
|
+
raise NotImplementedError(
|
|
425
|
+
f"gRPC store semantic search does not support vector_type={vector_type!r} "
|
|
426
|
+
"(only 'vector' and 'halfvec')"
|
|
427
|
+
)
|
|
428
|
+
|
|
429
|
+
return store_pb2.VectorSearch(
|
|
430
|
+
query_embedding=vectors[0],
|
|
431
|
+
distance_type=_DISTANCE_TYPE_BY_NAME[distance_type],
|
|
432
|
+
vectors_per_doc=vectors_per_doc,
|
|
433
|
+
vector_type=_VECTOR_TYPE_BY_NAME[vector_type],
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
|
|
203
437
|
def normalize_index_config(
|
|
204
438
|
index: IndexConfig | None,
|
|
205
439
|
) -> tuple[Embeddings | None, IndexConfig | None]:
|