langgraph-api 0.0.4__tar.gz → 0.0.6__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.
Potentially problematic release.
This version of langgraph-api might be problematic. Click here for more details.
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/PKG-INFO +2 -2
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/api/assistants.py +3 -3
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/api/store.py +2 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/cli.py +80 -20
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/graph.py +145 -4
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/js/package.json +1 -1
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/js/yarn.lock +4 -4
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/server.py +0 -1
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/stream.py +1 -1
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_storage/database.py +7 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_storage/ops.py +7 -3
- langgraph_api-0.0.6/langgraph_storage/store.py +62 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/openapi.json +15 -6
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/pyproject.toml +2 -2
- langgraph_api-0.0.4/langgraph_storage/store.py +0 -44
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/LICENSE +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/README.md +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/__init__.py +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/api/__init__.py +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/api/meta.py +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/api/openapi.py +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/api/runs.py +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/api/threads.py +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/asyncio.py +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/auth/__init__.py +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/auth/langsmith/__init__.py +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/auth/langsmith/backend.py +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/auth/langsmith/client.py +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/auth/middleware.py +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/auth/noop.py +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/config.py +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/cron_scheduler.py +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/errors.py +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/http.py +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/http_logger.py +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/js/.gitignore +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/js/build.mts +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/js/client.mts +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/js/global.d.ts +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/js/remote.py +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/js/server_sent_events.py +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/js/src/graph.mts +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/js/src/hooks.mjs +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/js/src/parser/parser.mts +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/js/src/parser/parser.worker.mjs +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/js/src/schema/types.mts +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/js/src/schema/types.template.mts +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/js/src/utils/importMap.mts +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/js/src/utils/pythonSchemas.mts +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/js/src/utils/serde.mts +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/js/tests/api.test.mts +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/js/tests/compose-postgres.yml +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/js/tests/graphs/.gitignore +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/js/tests/graphs/agent.mts +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/js/tests/graphs/error.mts +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/js/tests/graphs/langgraph.json +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/js/tests/graphs/nested.mts +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/js/tests/graphs/package.json +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/js/tests/graphs/weather.mts +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/js/tests/graphs/yarn.lock +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/js/tests/parser.test.mts +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/js/tests/utils.mts +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/lifespan.py +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/logging.py +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/metadata.py +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/models/__init__.py +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/models/run.py +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/patch.py +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/queue.py +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/route.py +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/schema.py +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/serde.py +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/sse.py +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/state.py +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/utils.py +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_api/validation.py +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_license/__init__.py +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_license/middleware.py +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_license/validation.py +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_storage/__init__.py +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_storage/checkpoint.py +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_storage/queue.py +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_storage/retry.py +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/langgraph_storage/ttl_dict.py +0 -0
- {langgraph_api-0.0.4 → langgraph_api-0.0.6}/logging.json +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: langgraph-api
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.6
|
|
4
4
|
Summary:
|
|
5
5
|
License: Elastic-2.0
|
|
6
6
|
Author: Nuno Campos
|
|
@@ -15,7 +15,7 @@ Requires-Dist: httpx (>=0.27.0)
|
|
|
15
15
|
Requires-Dist: jsonschema-rs (>=0.25.0,<0.26.0)
|
|
16
16
|
Requires-Dist: langchain-core (>=0.2.38,<0.4.0)
|
|
17
17
|
Requires-Dist: langgraph (>=0.2.52,<0.3.0)
|
|
18
|
-
Requires-Dist: langgraph-checkpoint (>=2.0.
|
|
18
|
+
Requires-Dist: langgraph-checkpoint (>=2.0.7,<3.0)
|
|
19
19
|
Requires-Dist: langsmith (>=0.1.63,<0.2.0)
|
|
20
20
|
Requires-Dist: orjson (>=3.10.1)
|
|
21
21
|
Requires-Dist: pyjwt (>=2.9.0,<3.0.0)
|
|
@@ -121,7 +121,7 @@ async def get_assistant_graph(
|
|
|
121
121
|
assistant_ = await Assistants.get(conn, assistant_id)
|
|
122
122
|
assistant = await fetchone(assistant_)
|
|
123
123
|
config = await ajson_loads(assistant["config"])
|
|
124
|
-
graph = get_graph(assistant["graph_id"], config)
|
|
124
|
+
graph = await get_graph(assistant["graph_id"], config)
|
|
125
125
|
|
|
126
126
|
xray: bool | int = False
|
|
127
127
|
xray_query = request.query_params.get("xray")
|
|
@@ -156,7 +156,7 @@ async def get_assistant_subgraphs(
|
|
|
156
156
|
assistant_ = await Assistants.get(conn, assistant_id)
|
|
157
157
|
assistant = await fetchone(assistant_)
|
|
158
158
|
config = await ajson_loads(assistant["config"])
|
|
159
|
-
graph = get_graph(assistant["graph_id"], config)
|
|
159
|
+
graph = await get_graph(assistant["graph_id"], config)
|
|
160
160
|
namespace = request.path_params.get("namespace")
|
|
161
161
|
|
|
162
162
|
if isinstance(graph, RemotePregel):
|
|
@@ -191,7 +191,7 @@ async def get_assistant_schemas(
|
|
|
191
191
|
assistant_ = await Assistants.get(conn, assistant_id)
|
|
192
192
|
assistant = await fetchone(assistant_)
|
|
193
193
|
config = await ajson_loads(assistant["config"])
|
|
194
|
-
graph = get_graph(assistant["graph_id"], config)
|
|
194
|
+
graph = await get_graph(assistant["graph_id"], config)
|
|
195
195
|
|
|
196
196
|
if isinstance(graph, RemotePregel):
|
|
197
197
|
schemas = await graph.fetch_state_schema()
|
|
@@ -73,12 +73,14 @@ async def search_items(request: ApiRequest):
|
|
|
73
73
|
filter = payload.get("filter")
|
|
74
74
|
limit = payload.get("limit") or 10
|
|
75
75
|
offset = payload.get("offset") or 0
|
|
76
|
+
query = payload.get("query")
|
|
76
77
|
async with connect() as conn:
|
|
77
78
|
items = await Store(conn).asearch(
|
|
78
79
|
namespace_prefix,
|
|
79
80
|
filter=filter,
|
|
80
81
|
limit=limit,
|
|
81
82
|
offset=offset,
|
|
83
|
+
query=query,
|
|
82
84
|
)
|
|
83
85
|
return ApiResponse({"items": [item.dict() for item in items]})
|
|
84
86
|
|
|
@@ -5,6 +5,7 @@ import os
|
|
|
5
5
|
import pathlib
|
|
6
6
|
import threading
|
|
7
7
|
from collections.abc import Mapping, Sequence
|
|
8
|
+
from typing import Any, TypedDict
|
|
8
9
|
|
|
9
10
|
logging.basicConfig(level=logging.INFO)
|
|
10
11
|
logger = logging.getLogger(__name__)
|
|
@@ -17,7 +18,6 @@ def _get_org_id() -> str | None:
|
|
|
17
18
|
# Yes, the organizationId is actually the workspace iD
|
|
18
19
|
# which is actually the tenantID which we actually get via
|
|
19
20
|
# the sessions endpoint
|
|
20
|
-
|
|
21
21
|
if not tracing_is_enabled():
|
|
22
22
|
return
|
|
23
23
|
client = Client()
|
|
@@ -60,6 +60,40 @@ def patch_environment(**kwargs):
|
|
|
60
60
|
os.environ[key] = value
|
|
61
61
|
|
|
62
62
|
|
|
63
|
+
class IndexConfig(TypedDict, total=False):
|
|
64
|
+
"""Configuration for indexing documents for semantic search in the store."""
|
|
65
|
+
|
|
66
|
+
dims: int
|
|
67
|
+
"""Number of dimensions in the embedding vectors.
|
|
68
|
+
|
|
69
|
+
Common embedding models have the following dimensions:
|
|
70
|
+
- OpenAI text-embedding-3-large: 256, 1024, or 3072
|
|
71
|
+
- OpenAI text-embedding-3-small: 512 or 1536
|
|
72
|
+
- OpenAI text-embedding-ada-002: 1536
|
|
73
|
+
- Cohere embed-english-v3.0: 1024
|
|
74
|
+
- Cohere embed-english-light-v3.0: 384
|
|
75
|
+
- Cohere embed-multilingual-v3.0: 1024
|
|
76
|
+
- Cohere embed-multilingual-light-v3.0: 384
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
embed: str
|
|
80
|
+
"""Either a path to an embedding model (./path/to/file.py:embedding_model)
|
|
81
|
+
or a name of an embedding model (openai:text-embedding-3-small)
|
|
82
|
+
|
|
83
|
+
Note: LangChain is required to use the model format specification.
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
fields: list[str] | None
|
|
87
|
+
"""Fields to extract text from for embedding generation.
|
|
88
|
+
|
|
89
|
+
Defaults to the root ["$"], which embeds the json object as a whole.
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class StoreConfig(TypedDict, total=False):
|
|
94
|
+
index: IndexConfig
|
|
95
|
+
|
|
96
|
+
|
|
63
97
|
def run_server(
|
|
64
98
|
host: str = "127.0.0.1",
|
|
65
99
|
port: int = 2024,
|
|
@@ -69,9 +103,12 @@ def run_server(
|
|
|
69
103
|
env_file: str | None = None,
|
|
70
104
|
open_browser: bool = False,
|
|
71
105
|
debug_port: int | None = None,
|
|
106
|
+
wait_for_client: bool = False,
|
|
72
107
|
env: str | pathlib.Path | Mapping[str, str] | None = None,
|
|
73
108
|
reload_includes: Sequence[str] | None = None,
|
|
74
109
|
reload_excludes: Sequence[str] | None = None,
|
|
110
|
+
store: StoreConfig | None = None,
|
|
111
|
+
**kwargs: Any,
|
|
75
112
|
):
|
|
76
113
|
"""Run the LangGraph API server."""
|
|
77
114
|
import uvicorn
|
|
@@ -108,8 +145,9 @@ def run_server(
|
|
|
108
145
|
logger.info(" - Host: 0.0.0.0")
|
|
109
146
|
logger.info(f" - Port: {debug_port}")
|
|
110
147
|
logger.info("3. Start the debugger to connect to the server.")
|
|
111
|
-
|
|
112
|
-
|
|
148
|
+
if wait_for_client:
|
|
149
|
+
debugpy.wait_for_client()
|
|
150
|
+
logger.info("Debugger attached. Starting server...")
|
|
113
151
|
|
|
114
152
|
local_url = f"http://{host}:{port}"
|
|
115
153
|
studio_url = f"https://smith.langchain.com/studio/?baseUrl={local_url}"
|
|
@@ -119,22 +157,37 @@ def run_server(
|
|
|
119
157
|
import time
|
|
120
158
|
import urllib.request
|
|
121
159
|
import webbrowser
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
160
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
161
|
+
|
|
162
|
+
thread_logger = logging.getLogger("browser_opener")
|
|
163
|
+
if not thread_logger.handlers:
|
|
164
|
+
handler = logging.StreamHandler()
|
|
165
|
+
handler.setFormatter(logging.Formatter("%(message)s"))
|
|
166
|
+
thread_logger.addHandler(handler)
|
|
167
|
+
|
|
168
|
+
with ThreadPoolExecutor(max_workers=1) as executor:
|
|
169
|
+
org_id_future = executor.submit(_get_org_id)
|
|
170
|
+
|
|
171
|
+
while True:
|
|
172
|
+
try:
|
|
173
|
+
with urllib.request.urlopen(f"{local_url}/ok") as response:
|
|
174
|
+
if response.status == 200:
|
|
175
|
+
try:
|
|
176
|
+
org_id = org_id_future.result(timeout=3.0)
|
|
177
|
+
if org_id:
|
|
178
|
+
studio_url = f"https://smith.langchain.com/studio/?baseUrl={local_url}&organizationId={org_id}"
|
|
179
|
+
except TimeoutError as e:
|
|
180
|
+
thread_logger.debug(
|
|
181
|
+
f"Failed to get organization ID: {str(e)}"
|
|
182
|
+
)
|
|
183
|
+
pass
|
|
184
|
+
thread_logger.info("🎨 Opening Studio in your browser...")
|
|
185
|
+
thread_logger.info("URL: " + studio_url)
|
|
186
|
+
webbrowser.open(studio_url)
|
|
187
|
+
return
|
|
188
|
+
except urllib.error.URLError:
|
|
189
|
+
pass
|
|
190
|
+
time.sleep(0.1)
|
|
138
191
|
|
|
139
192
|
welcome = f"""
|
|
140
193
|
|
|
@@ -158,13 +211,13 @@ For production use, please use LangGraph Cloud.
|
|
|
158
211
|
DATABASE_URI=":memory:",
|
|
159
212
|
REDIS_URI="fake",
|
|
160
213
|
N_JOBS_PER_WORKER=str(n_jobs_per_worker if n_jobs_per_worker else 1),
|
|
214
|
+
LANGGRAPH_STORE=json.dumps(store) if store else None,
|
|
161
215
|
LANGSERVE_GRAPHS=json.dumps(graphs) if graphs else None,
|
|
162
216
|
LANGSMITH_LANGGRAPH_API_VARIANT="local_dev",
|
|
163
217
|
**(env_vars or {}),
|
|
164
218
|
):
|
|
165
219
|
if open_browser:
|
|
166
220
|
threading.Thread(target=_open_browser, daemon=True).start()
|
|
167
|
-
|
|
168
221
|
uvicorn.run(
|
|
169
222
|
"langgraph_api.server:app",
|
|
170
223
|
host=host,
|
|
@@ -188,6 +241,7 @@ For production use, please use LangGraph Cloud.
|
|
|
188
241
|
},
|
|
189
242
|
"root": {"handlers": ["console"]},
|
|
190
243
|
},
|
|
244
|
+
**kwargs,
|
|
191
245
|
)
|
|
192
246
|
|
|
193
247
|
|
|
@@ -218,6 +272,11 @@ def main():
|
|
|
218
272
|
parser.add_argument(
|
|
219
273
|
"--debug-port", type=int, help="Port for debugger to listen on (default: none)"
|
|
220
274
|
)
|
|
275
|
+
parser.add_argument(
|
|
276
|
+
"--wait-for-client",
|
|
277
|
+
action="store_true",
|
|
278
|
+
help="Whether to break and wait for a debugger to attach",
|
|
279
|
+
)
|
|
221
280
|
|
|
222
281
|
args = parser.parse_args()
|
|
223
282
|
|
|
@@ -233,6 +292,7 @@ def main():
|
|
|
233
292
|
n_jobs_per_worker=args.n_jobs_per_worker,
|
|
234
293
|
open_browser=not args.no_browser,
|
|
235
294
|
debug_port=args.debug_port,
|
|
295
|
+
wait_for_client=args.wait_for_client,
|
|
236
296
|
env=config_data.get("env", None),
|
|
237
297
|
)
|
|
238
298
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
import functools
|
|
2
3
|
import glob
|
|
3
4
|
import importlib.util
|
|
4
5
|
import inspect
|
|
@@ -8,7 +9,7 @@ import sys
|
|
|
8
9
|
from collections.abc import Callable
|
|
9
10
|
from itertools import filterfalse
|
|
10
11
|
from random import choice
|
|
11
|
-
from typing import NamedTuple
|
|
12
|
+
from typing import TYPE_CHECKING, NamedTuple
|
|
12
13
|
from uuid import UUID, uuid5
|
|
13
14
|
|
|
14
15
|
import structlog
|
|
@@ -22,6 +23,9 @@ from starlette.exceptions import HTTPException
|
|
|
22
23
|
from langgraph_api.js.remote import RemotePregel
|
|
23
24
|
from langgraph_api.schema import Config
|
|
24
25
|
|
|
26
|
+
if TYPE_CHECKING:
|
|
27
|
+
from langchain_core.embeddings import Embeddings
|
|
28
|
+
|
|
25
29
|
logger = structlog.stdlib.get_logger(__name__)
|
|
26
30
|
|
|
27
31
|
GraphFactoryFromConfig = Callable[[Config], Pregel | Graph]
|
|
@@ -55,7 +59,7 @@ async def register_graph(graph_id: str, graph: GraphValue, config: dict | None)
|
|
|
55
59
|
)
|
|
56
60
|
|
|
57
61
|
|
|
58
|
-
def get_graph(
|
|
62
|
+
async def get_graph(
|
|
59
63
|
graph_id: str,
|
|
60
64
|
config: Config,
|
|
61
65
|
*,
|
|
@@ -67,6 +71,8 @@ def get_graph(
|
|
|
67
71
|
value = GRAPHS[graph_id]
|
|
68
72
|
if graph_id in FACTORY_ACCEPTS_CONFIG:
|
|
69
73
|
value = value(config) if FACTORY_ACCEPTS_CONFIG[graph_id] else value()
|
|
74
|
+
if asyncio.iscoroutine(value):
|
|
75
|
+
value = await value
|
|
70
76
|
if isinstance(value, Graph):
|
|
71
77
|
value = value.compile()
|
|
72
78
|
if not isinstance(value, Pregel) and not isinstance(value, RemotePregel):
|
|
@@ -269,7 +275,7 @@ def _graph_from_spec(spec: GraphSpec) -> GraphValue:
|
|
|
269
275
|
sys.modules[modname] = module
|
|
270
276
|
modspec.loader.exec_module(module)
|
|
271
277
|
except ImportError as e:
|
|
272
|
-
e.add_note(f"Could not import python module for graph
|
|
278
|
+
e.add_note(f"Could not import python module for graph:\n{spec}")
|
|
273
279
|
if os.environ.get("LANGSMITH_LANGGRAPH_API_VARIANT") == "local_dev":
|
|
274
280
|
e.add_note(
|
|
275
281
|
"This error likely means you haven't installed your project and its dependencies yet. Before running the server, install your project:\n\n"
|
|
@@ -279,11 +285,44 @@ def _graph_from_spec(spec: GraphSpec) -> GraphValue:
|
|
|
279
285
|
"python -m pip install -e .\n\n"
|
|
280
286
|
"Make sure to run this command from your project's root directory (where your setup.py or pyproject.toml is located)"
|
|
281
287
|
)
|
|
288
|
+
raise
|
|
289
|
+
except FileNotFoundError as e:
|
|
290
|
+
e.add_note(f"Could not find python file for graph: {spec}")
|
|
291
|
+
raise
|
|
282
292
|
else:
|
|
283
293
|
raise ValueError("Graph specification must have a path or module")
|
|
284
294
|
|
|
285
295
|
if spec.variable:
|
|
286
|
-
|
|
296
|
+
try:
|
|
297
|
+
graph: GraphValue = module.__dict__[spec.variable]
|
|
298
|
+
except KeyError as e:
|
|
299
|
+
available = [k for k in module.__dict__ if not k.startswith("__")]
|
|
300
|
+
suggestion = ""
|
|
301
|
+
if available:
|
|
302
|
+
likely = [
|
|
303
|
+
k
|
|
304
|
+
for k in available
|
|
305
|
+
if isinstance(module.__dict__[k], Graph | Pregel)
|
|
306
|
+
]
|
|
307
|
+
if likely:
|
|
308
|
+
prefix = spec.module or spec.path
|
|
309
|
+
likely_ = "\n".join(
|
|
310
|
+
[f"\t- {prefix}:{k}" if prefix else k for k in likely]
|
|
311
|
+
)
|
|
312
|
+
suggestion = (
|
|
313
|
+
f"\nDid you mean to use one of the following?\n{likely_}"
|
|
314
|
+
)
|
|
315
|
+
elif available:
|
|
316
|
+
suggestion = (
|
|
317
|
+
f"\nFound the following exports: {', '.join(available)}"
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
raise ValueError(
|
|
321
|
+
f"Could not find graph '{spec.variable}' in '{spec.path}'. "
|
|
322
|
+
f"Please check that:\n"
|
|
323
|
+
f"1. The file exports a variable named '{spec.variable}'\n"
|
|
324
|
+
f"2. The variable name in your config matches the export name{suggestion}"
|
|
325
|
+
) from e
|
|
287
326
|
if callable(graph):
|
|
288
327
|
sig = inspect.signature(graph)
|
|
289
328
|
if not sig.parameters:
|
|
@@ -322,3 +361,105 @@ def _graph_from_spec(spec: GraphSpec) -> GraphValue:
|
|
|
322
361
|
)
|
|
323
362
|
|
|
324
363
|
return graph
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def _load(path: str, variable: str) -> None:
|
|
367
|
+
modname = "".join(choice("abcdefghijklmnopqrstuvwxyz") for _ in range(24))
|
|
368
|
+
modspec = importlib.util.spec_from_file_location(modname, path)
|
|
369
|
+
if modspec is None:
|
|
370
|
+
raise ValueError(f"Could not find python file for embeddings: {path}")
|
|
371
|
+
module = importlib.util.module_from_spec(modspec)
|
|
372
|
+
sys.modules[modname] = module
|
|
373
|
+
modspec.loader.exec_module(module)
|
|
374
|
+
return module.__dict__[variable]
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
@functools.lru_cache
|
|
378
|
+
def _get_init_embeddings() -> Callable[[str, ...], "Embeddings"] | None:
|
|
379
|
+
try:
|
|
380
|
+
from langchain.embeddings import init_embeddings
|
|
381
|
+
|
|
382
|
+
return init_embeddings
|
|
383
|
+
except ImportError:
|
|
384
|
+
return None
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def resolve_embeddings(index_config: dict) -> "Embeddings":
|
|
388
|
+
"""Return embeddings from config.
|
|
389
|
+
|
|
390
|
+
Args:
|
|
391
|
+
index_config: Configuration for the vector store index
|
|
392
|
+
Must contain an "embed" key specifying either:
|
|
393
|
+
- A path to a Python file and function (e.g. "./embeddings.py:get_embeddings")
|
|
394
|
+
- A LangChain embeddings identifier (e.g. "openai:text-embedding-3-small")
|
|
395
|
+
|
|
396
|
+
Returns:
|
|
397
|
+
Embeddings: A LangChain embeddings instance
|
|
398
|
+
|
|
399
|
+
Raises:
|
|
400
|
+
ValueError: If embeddings cannot be loaded from the config
|
|
401
|
+
"""
|
|
402
|
+
from langchain_core.embeddings import Embeddings
|
|
403
|
+
from langgraph.store.base import ensure_embeddings
|
|
404
|
+
|
|
405
|
+
embed: str = index_config["embed"]
|
|
406
|
+
if ".py:" in embed:
|
|
407
|
+
module_name, function = embed.rsplit(":", 1)
|
|
408
|
+
module_name = module_name.rstrip(":")
|
|
409
|
+
|
|
410
|
+
try:
|
|
411
|
+
if "/" in module_name:
|
|
412
|
+
# Load from file path
|
|
413
|
+
modname = "".join(
|
|
414
|
+
choice("abcdefghijklmnopqrstuvwxyz") for _ in range(24)
|
|
415
|
+
)
|
|
416
|
+
modspec = importlib.util.spec_from_file_location(modname, module_name)
|
|
417
|
+
if modspec is None:
|
|
418
|
+
raise ValueError(f"Could not find embeddings file: {module_name}")
|
|
419
|
+
module = importlib.util.module_from_spec(modspec)
|
|
420
|
+
sys.modules[modname] = module
|
|
421
|
+
modspec.loader.exec_module(module)
|
|
422
|
+
else:
|
|
423
|
+
# Load from Python module
|
|
424
|
+
module = importlib.import_module(module_name)
|
|
425
|
+
|
|
426
|
+
embedding_fn = getattr(module, function, None)
|
|
427
|
+
if embedding_fn is None:
|
|
428
|
+
raise ValueError(
|
|
429
|
+
f"Could not find embeddings function '{function}' in module: {module_name}"
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
if isinstance(embedding_fn, Embeddings):
|
|
433
|
+
return embedding_fn
|
|
434
|
+
elif not callable(embedding_fn):
|
|
435
|
+
raise ValueError(
|
|
436
|
+
f"Embeddings function '{function}' in module: {module_name} is not callable"
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
return ensure_embeddings(embedding_fn)
|
|
440
|
+
|
|
441
|
+
except ImportError as e:
|
|
442
|
+
e.add_note(f"Could not import embeddings module:\n{module_name}\n\n")
|
|
443
|
+
if os.environ.get("LANGSMITH_LANGGRAPH_API_VARIANT") == "local_dev":
|
|
444
|
+
e.add_note(
|
|
445
|
+
"If you're in development mode, make sure you've installed your project "
|
|
446
|
+
"and its dependencies:\n"
|
|
447
|
+
"- For requirements.txt: pip install -r requirements.txt\n"
|
|
448
|
+
"- For pyproject.toml: pip install -e .\n"
|
|
449
|
+
)
|
|
450
|
+
raise
|
|
451
|
+
except FileNotFoundError as e:
|
|
452
|
+
raise ValueError(f"Could not find embeddings file: {module_name}") from e
|
|
453
|
+
|
|
454
|
+
else:
|
|
455
|
+
# Load from LangChain embeddings
|
|
456
|
+
init_embeddings = _get_init_embeddings()
|
|
457
|
+
if init_embeddings is None:
|
|
458
|
+
raise ValueError(
|
|
459
|
+
f"Could not load LangChain embeddings '{embed}'. "
|
|
460
|
+
"Loading embeddings by provider:identifier requires the langchain package (>=0.3.9). "
|
|
461
|
+
"Install it with: pip install 'langchain>=0.3.9'"
|
|
462
|
+
" or specify 'embed' as a path to a "
|
|
463
|
+
"variable in a Python file instead."
|
|
464
|
+
)
|
|
465
|
+
return init_embeddings(embed)
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"@hono/node-server": "^1.12.0",
|
|
11
11
|
"@hono/zod-validator": "^0.2.2",
|
|
12
12
|
"@langchain/core": "^0.3.17",
|
|
13
|
-
"@langchain/langgraph": "^0.2.
|
|
13
|
+
"@langchain/langgraph": "^0.2.23",
|
|
14
14
|
"@types/json-schema": "^7.0.15",
|
|
15
15
|
"@typescript/vfs": "^1.6.0",
|
|
16
16
|
"dedent": "^1.5.3",
|
|
@@ -345,10 +345,10 @@
|
|
|
345
345
|
p-retry "4"
|
|
346
346
|
uuid "^9.0.0"
|
|
347
347
|
|
|
348
|
-
"@langchain/langgraph@^0.2.
|
|
349
|
-
version "0.2.
|
|
350
|
-
resolved "https://registry.yarnpkg.com/@langchain/langgraph/-/langgraph-0.2.
|
|
351
|
-
integrity sha512-
|
|
348
|
+
"@langchain/langgraph@^0.2.23":
|
|
349
|
+
version "0.2.23"
|
|
350
|
+
resolved "https://registry.yarnpkg.com/@langchain/langgraph/-/langgraph-0.2.23.tgz#b892ae642c2ac49a44599ce18ab1897b14554f9f"
|
|
351
|
+
integrity sha512-aUD3G2cUSmrOb6xtJkd0AUctmHJXuyNPIzMR86JMEN591AtkHTVe2OhTxo15S8PPAQxJ7HrCJyMFd0EQECRASw==
|
|
352
352
|
dependencies:
|
|
353
353
|
"@langchain/langgraph-checkpoint" "~0.0.12"
|
|
354
354
|
"@langchain/langgraph-sdk" "~0.0.21"
|
|
@@ -100,7 +100,7 @@ async def astream_state(
|
|
|
100
100
|
subgraphs = kwargs.get("subgraphs", False)
|
|
101
101
|
temporary = kwargs.pop("temporary", False)
|
|
102
102
|
config = kwargs.pop("config")
|
|
103
|
-
graph = get_graph(
|
|
103
|
+
graph = await get_graph(
|
|
104
104
|
config["configurable"]["graph_id"],
|
|
105
105
|
config,
|
|
106
106
|
store=None if not conn else Store(conn, pipe=pipe),
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
import json
|
|
2
3
|
import os
|
|
3
4
|
import threading
|
|
4
5
|
import uuid
|
|
@@ -13,6 +14,7 @@ import structlog
|
|
|
13
14
|
from langgraph.checkpoint.memory import PersistentDict
|
|
14
15
|
|
|
15
16
|
from langgraph_api.utils import AsyncConnectionProto
|
|
17
|
+
from langgraph_storage import store
|
|
16
18
|
from langgraph_storage.queue import start_queue, stop_queue
|
|
17
19
|
|
|
18
20
|
logger = structlog.stdlib.get_logger(__name__)
|
|
@@ -154,6 +156,11 @@ async def connect(*, __test__: bool = False) -> AsyncIterator[AsyncConnectionPro
|
|
|
154
156
|
|
|
155
157
|
|
|
156
158
|
async def start_pool() -> None:
|
|
159
|
+
if store._STORE_CONFIG is None:
|
|
160
|
+
if (config_val := os.getenv("LANGGRAPH_STORE")) and config_val.strip():
|
|
161
|
+
config_ = json.loads(config_val.strip())
|
|
162
|
+
store.set_store_config(config_)
|
|
163
|
+
|
|
157
164
|
if not os.path.exists(".langgraph_api"):
|
|
158
165
|
os.mkdir(".langgraph_api")
|
|
159
166
|
if os.path.exists(OPS_FILENAME):
|
|
@@ -719,7 +719,9 @@ class Threads:
|
|
|
719
719
|
if graph_id := metadata.get("graph_id"):
|
|
720
720
|
# format latest checkpoint for response
|
|
721
721
|
checkpointer.latest_iter = checkpoint
|
|
722
|
-
graph = get_graph(
|
|
722
|
+
graph = await get_graph(
|
|
723
|
+
graph_id, thread_config, checkpointer=checkpointer
|
|
724
|
+
)
|
|
723
725
|
result = await graph.aget_state(config, subgraphs=subgraphs)
|
|
724
726
|
if (
|
|
725
727
|
result.metadata is not None
|
|
@@ -766,7 +768,9 @@ class Threads:
|
|
|
766
768
|
config["configurable"].setdefault("graph_id", graph_id)
|
|
767
769
|
|
|
768
770
|
checkpointer.latest_iter = checkpoint
|
|
769
|
-
graph = get_graph(
|
|
771
|
+
graph = await get_graph(
|
|
772
|
+
graph_id, thread_config, checkpointer=checkpointer
|
|
773
|
+
)
|
|
770
774
|
update_config = config.copy()
|
|
771
775
|
update_config["configurable"] = {
|
|
772
776
|
**config["configurable"],
|
|
@@ -820,7 +824,7 @@ class Threads:
|
|
|
820
824
|
thread_config = thread["config"]
|
|
821
825
|
# If graph_id exists, get state history
|
|
822
826
|
if graph_id := thread_metadata.get("graph_id"):
|
|
823
|
-
graph = get_graph(
|
|
827
|
+
graph = await get_graph(
|
|
824
828
|
graph_id, thread_config, checkpointer=Checkpointer(conn)
|
|
825
829
|
)
|
|
826
830
|
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from collections import defaultdict
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from langgraph.checkpoint.memory import PersistentDict
|
|
6
|
+
from langgraph.store.memory import InMemoryStore
|
|
7
|
+
|
|
8
|
+
from langgraph_api.graph import resolve_embeddings
|
|
9
|
+
|
|
10
|
+
_STORE_CONFIG = None
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class DiskBackedInMemStore(InMemoryStore):
|
|
14
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
15
|
+
super().__init__(*args, **kwargs)
|
|
16
|
+
self._data = PersistentDict(dict, filename=_STORE_FILE)
|
|
17
|
+
self._vectors = PersistentDict(lambda: defaultdict(dict), filename=_VECTOR_FILE)
|
|
18
|
+
self._load_data(self._data, which="data")
|
|
19
|
+
self._load_data(self._vectors, which="vectors")
|
|
20
|
+
|
|
21
|
+
def _load_data(self, container: PersistentDict, which: str) -> None:
|
|
22
|
+
if not container.filename:
|
|
23
|
+
return
|
|
24
|
+
try:
|
|
25
|
+
container.load()
|
|
26
|
+
except FileNotFoundError:
|
|
27
|
+
# It's okay if the file doesn't exist yet
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
except (EOFError, ValueError) as e:
|
|
31
|
+
raise RuntimeError(
|
|
32
|
+
f"Failed to load store {which} from {container.filename}. "
|
|
33
|
+
"This may be due to changes in the stored data structure. "
|
|
34
|
+
"Consider clearing the local store by running: rm -rf .langgraph_api"
|
|
35
|
+
) from e
|
|
36
|
+
except Exception as e:
|
|
37
|
+
raise RuntimeError(
|
|
38
|
+
f"Unexpected error loading store {which} from {container.filename}: {str(e)}"
|
|
39
|
+
) from e
|
|
40
|
+
|
|
41
|
+
def close(self) -> None:
|
|
42
|
+
self._data.close()
|
|
43
|
+
self._vectors.close()
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
_STORE_FILE = os.path.join(".langgraph_api", "store.pckl")
|
|
47
|
+
_VECTOR_FILE = os.path.join(".langgraph_api", "store.vectors.pckl")
|
|
48
|
+
os.makedirs(".langgraph_api", exist_ok=True)
|
|
49
|
+
STORE = DiskBackedInMemStore()
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def set_store_config(config) -> None:
|
|
53
|
+
global _STORE_CONFIG, STORE
|
|
54
|
+
_STORE_CONFIG = config.copy()
|
|
55
|
+
_STORE_CONFIG["index"]["embed"] = resolve_embeddings(_STORE_CONFIG.get("index", {}))
|
|
56
|
+
# Re-create the store
|
|
57
|
+
STORE.close()
|
|
58
|
+
STORE = DiskBackedInMemStore(index=_STORE_CONFIG.get("index", {}))
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def Store(*args: Any, **kwargs: Any) -> DiskBackedInMemStore:
|
|
62
|
+
return STORE
|
|
@@ -1557,8 +1557,11 @@
|
|
|
1557
1557
|
"200": {
|
|
1558
1558
|
"description": "Success",
|
|
1559
1559
|
"content": {
|
|
1560
|
-
"
|
|
1561
|
-
"schema": {
|
|
1560
|
+
"text/event-stream": {
|
|
1561
|
+
"schema": {
|
|
1562
|
+
"type": "string",
|
|
1563
|
+
"description": "The server will send a stream of events in SSE format.\n\n**Example event**:\n\nid: 1\n\nevent: message\n\ndata: {}"
|
|
1564
|
+
}
|
|
1562
1565
|
}
|
|
1563
1566
|
}
|
|
1564
1567
|
},
|
|
@@ -1905,8 +1908,11 @@
|
|
|
1905
1908
|
"200": {
|
|
1906
1909
|
"description": "Success",
|
|
1907
1910
|
"content": {
|
|
1908
|
-
"
|
|
1909
|
-
"schema": {
|
|
1911
|
+
"text/event-stream": {
|
|
1912
|
+
"schema": {
|
|
1913
|
+
"type": "string",
|
|
1914
|
+
"description": "The server will send a stream of events in SSE format.\n\n**Example event**:\n\nid: 1\n\nevent: message\n\ndata: {}"
|
|
1915
|
+
}
|
|
1910
1916
|
}
|
|
1911
1917
|
}
|
|
1912
1918
|
},
|
|
@@ -2143,8 +2149,11 @@
|
|
|
2143
2149
|
"200": {
|
|
2144
2150
|
"description": "Success",
|
|
2145
2151
|
"content": {
|
|
2146
|
-
"
|
|
2147
|
-
"schema": {
|
|
2152
|
+
"text/event-stream": {
|
|
2153
|
+
"schema": {
|
|
2154
|
+
"type": "string",
|
|
2155
|
+
"description": "The server will send a stream of events in SSE format.\n\n**Example event**:\n\nid: 1\n\nevent: message\n\ndata: {}"
|
|
2156
|
+
}
|
|
2148
2157
|
}
|
|
2149
2158
|
}
|
|
2150
2159
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "langgraph-api"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.6"
|
|
4
4
|
description = ""
|
|
5
5
|
authors = [
|
|
6
6
|
"Nuno Campos <nuno@langchain.dev>",
|
|
@@ -24,7 +24,7 @@ sse-starlette = "^2.1.0"
|
|
|
24
24
|
starlette = ">=0.38.6"
|
|
25
25
|
watchfiles = ">=0.13"
|
|
26
26
|
langgraph = ">=0.2.52,<0.3.0"
|
|
27
|
-
langgraph-checkpoint = ">=2.0.
|
|
27
|
+
langgraph-checkpoint = ">=2.0.7,<3.0"
|
|
28
28
|
orjson = ">=3.10.1"
|
|
29
29
|
uvicorn = ">=0.26.0"
|
|
30
30
|
langsmith = "^0.1.63"
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
from typing import Any
|
|
3
|
-
|
|
4
|
-
from langgraph.checkpoint.memory import PersistentDict
|
|
5
|
-
from langgraph.store.memory import InMemoryStore
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class DiskBackedInMemStore(InMemoryStore):
|
|
9
|
-
def __init__(self, *args: Any, filename: str | None = None, **kwargs: Any) -> None:
|
|
10
|
-
super().__init__(*args, **kwargs)
|
|
11
|
-
self.filename = filename
|
|
12
|
-
self._data = PersistentDict(dict, filename=self.filename)
|
|
13
|
-
self._load_data()
|
|
14
|
-
|
|
15
|
-
def _load_data(self) -> None:
|
|
16
|
-
if not self.filename:
|
|
17
|
-
return
|
|
18
|
-
try:
|
|
19
|
-
self._data.load()
|
|
20
|
-
except FileNotFoundError:
|
|
21
|
-
# It's okay if the file doesn't exist yet
|
|
22
|
-
pass
|
|
23
|
-
except (EOFError, ValueError) as e:
|
|
24
|
-
raise RuntimeError(
|
|
25
|
-
f"Failed to load store from {self.filename}. "
|
|
26
|
-
"This may be due to changes in the stored data structure. "
|
|
27
|
-
"Consider clearing the local store by running: rm -rf .langgraph_api"
|
|
28
|
-
) from e
|
|
29
|
-
except Exception as e:
|
|
30
|
-
raise RuntimeError(
|
|
31
|
-
f"Unexpected error loading store from {self.filename}: {str(e)}"
|
|
32
|
-
) from e
|
|
33
|
-
|
|
34
|
-
def close(self) -> None:
|
|
35
|
-
self._data.close()
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
_STORE_FILE = os.path.join(".langgraph_api", "store.pckl")
|
|
39
|
-
os.makedirs(".langgraph_api", exist_ok=True)
|
|
40
|
-
STORE = DiskBackedInMemStore(filename=_STORE_FILE)
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def Store(*args: Any, **kwargs: Any) -> DiskBackedInMemStore:
|
|
44
|
-
return STORE
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|