openrewrite-migrate-python 0.1.0__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.
- openrewrite_migrate_python/__init__.py +35 -0
- openrewrite_migrate_python/migrate/__init__.py +219 -0
- openrewrite_migrate_python/migrate/aifc_migrations.py +517 -0
- openrewrite_migrate_python/migrate/array_deprecations.py +133 -0
- openrewrite_migrate_python/migrate/ast_deprecations.py +322 -0
- openrewrite_migrate_python/migrate/asyncio_coroutine_to_async.py +225 -0
- openrewrite_migrate_python/migrate/asyncio_deprecations.py +117 -0
- openrewrite_migrate_python/migrate/calendar_deprecations.py +134 -0
- openrewrite_migrate_python/migrate/cgi_migrations.py +213 -0
- openrewrite_migrate_python/migrate/cgi_parse_deprecations.py +170 -0
- openrewrite_migrate_python/migrate/collections_abc_migrations.py +207 -0
- openrewrite_migrate_python/migrate/configparser_deprecations.py +252 -0
- openrewrite_migrate_python/migrate/datetime_utc.py +136 -0
- openrewrite_migrate_python/migrate/distutils_deprecations.py +107 -0
- openrewrite_migrate_python/migrate/distutils_migrations.py +125 -0
- openrewrite_migrate_python/migrate/functools_deprecations.py +98 -0
- openrewrite_migrate_python/migrate/gettext_deprecations.py +139 -0
- openrewrite_migrate_python/migrate/html_parser_deprecations.py +110 -0
- openrewrite_migrate_python/migrate/imp_migrations.py +135 -0
- openrewrite_migrate_python/migrate/langchain_classic_imports.py +308 -0
- openrewrite_migrate_python/migrate/langchain_community_imports.py +154 -0
- openrewrite_migrate_python/migrate/langchain_provider_imports.py +240 -0
- openrewrite_migrate_python/migrate/locale_deprecations.py +177 -0
- openrewrite_migrate_python/migrate/locale_getdefaultlocale_deprecation.py +103 -0
- openrewrite_migrate_python/migrate/macpath_deprecations.py +125 -0
- openrewrite_migrate_python/migrate/mailcap_migrations.py +129 -0
- openrewrite_migrate_python/migrate/nntplib_migrations.py +127 -0
- openrewrite_migrate_python/migrate/os_deprecations.py +158 -0
- openrewrite_migrate_python/migrate/pathlib_deprecations.py +98 -0
- openrewrite_migrate_python/migrate/pep594_system_migrations.py +429 -0
- openrewrite_migrate_python/migrate/pipes_migrations.py +130 -0
- openrewrite_migrate_python/migrate/pkgutil_deprecations.py +180 -0
- openrewrite_migrate_python/migrate/platform_deprecations.py +99 -0
- openrewrite_migrate_python/migrate/re_deprecations.py +122 -0
- openrewrite_migrate_python/migrate/removed_modules_312.py +139 -0
- openrewrite_migrate_python/migrate/shutil_deprecations.py +112 -0
- openrewrite_migrate_python/migrate/socket_deprecations.py +96 -0
- openrewrite_migrate_python/migrate/ssl_deprecations.py +121 -0
- openrewrite_migrate_python/migrate/string_formatting.py +526 -0
- openrewrite_migrate_python/migrate/sys_deprecations.py +97 -0
- openrewrite_migrate_python/migrate/sys_last_deprecations.py +104 -0
- openrewrite_migrate_python/migrate/tarfile_deprecations.py +120 -0
- openrewrite_migrate_python/migrate/telnetlib_migrations.py +132 -0
- openrewrite_migrate_python/migrate/tempfile_deprecations.py +113 -0
- openrewrite_migrate_python/migrate/threading_deprecations.py +488 -0
- openrewrite_migrate_python/migrate/threading_is_alive_deprecation.py +82 -0
- openrewrite_migrate_python/migrate/typing_callable.py +153 -0
- openrewrite_migrate_python/migrate/typing_deprecations.py +337 -0
- openrewrite_migrate_python/migrate/typing_union_to_pipe.py +272 -0
- openrewrite_migrate_python/migrate/unittest_deprecations.py +140 -0
- openrewrite_migrate_python/migrate/upgrade_to_langchain02.py +56 -0
- openrewrite_migrate_python/migrate/upgrade_to_langchain1.py +66 -0
- openrewrite_migrate_python/migrate/upgrade_to_python310.py +91 -0
- openrewrite_migrate_python/migrate/upgrade_to_python311.py +92 -0
- openrewrite_migrate_python/migrate/upgrade_to_python312.py +101 -0
- openrewrite_migrate_python/migrate/upgrade_to_python313.py +109 -0
- openrewrite_migrate_python/migrate/upgrade_to_python314.py +87 -0
- openrewrite_migrate_python/migrate/upgrade_to_python38.py +85 -0
- openrewrite_migrate_python/migrate/upgrade_to_python39.py +125 -0
- openrewrite_migrate_python/migrate/urllib_deprecations.py +204 -0
- openrewrite_migrate_python/migrate/uu_migrations.py +129 -0
- openrewrite_migrate_python/migrate/xdrlib_migrations.py +129 -0
- openrewrite_migrate_python/migrate/xml_deprecations.py +154 -0
- openrewrite_migrate_python-0.1.0.dist-info/METADATA +31 -0
- openrewrite_migrate_python-0.1.0.dist-info/RECORD +68 -0
- openrewrite_migrate_python-0.1.0.dist-info/WHEEL +5 -0
- openrewrite_migrate_python-0.1.0.dist-info/entry_points.txt +2 -0
- openrewrite_migrate_python-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Recipes for migrating langchain imports to langchain_classic (v1.0).
|
|
3
|
+
|
|
4
|
+
In LangChain v1.0, legacy chains, retrievers, and indexing functionality
|
|
5
|
+
were moved from `langchain` to `langchain_classic`. This recipe handles:
|
|
6
|
+
|
|
7
|
+
from langchain.chains import LLMChain -> from langchain_classic.chains import LLMChain
|
|
8
|
+
from langchain.retrievers import ... -> from langchain_classic.retrievers import ...
|
|
9
|
+
from langchain.indexes import ... -> from langchain_classic.indexes import ...
|
|
10
|
+
from langchain import hub -> from langchain_classic import hub
|
|
11
|
+
|
|
12
|
+
See: https://docs.langchain.com/oss/python/migrate/langchain-v1
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from typing import Any, List, Optional, Set
|
|
16
|
+
|
|
17
|
+
from rewrite import ExecutionContext, Recipe, TreeVisitor
|
|
18
|
+
from rewrite.category import CategoryDescriptor
|
|
19
|
+
from rewrite.decorators import categorize
|
|
20
|
+
from rewrite.marketplace import Python
|
|
21
|
+
from rewrite.markers import Markers, SearchResult
|
|
22
|
+
from rewrite.python.visitor import PythonVisitor
|
|
23
|
+
from rewrite.python.tree import MultiImport
|
|
24
|
+
from rewrite.java.tree import FieldAccess, Identifier
|
|
25
|
+
from rewrite.utils import random_id
|
|
26
|
+
|
|
27
|
+
_LangChain1 = [
|
|
28
|
+
*Python,
|
|
29
|
+
CategoryDescriptor(display_name="Migrate"),
|
|
30
|
+
CategoryDescriptor(display_name="LangChain 1.0"),
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
# Submodules that moved from langchain to langchain_classic in v1.0
|
|
34
|
+
CLASSIC_SUBMODULES: Set[str] = {
|
|
35
|
+
"chains",
|
|
36
|
+
"indexes",
|
|
37
|
+
"retrievers",
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
# Names that can be imported directly from langchain but moved to langchain_classic
|
|
41
|
+
CLASSIC_DIRECT_IMPORTS: Set[str] = {
|
|
42
|
+
"hub",
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _get_root_and_first_submodule(from_part: Any) -> tuple:
|
|
47
|
+
"""Extract root module name and first submodule from a FieldAccess chain."""
|
|
48
|
+
if not isinstance(from_part, FieldAccess):
|
|
49
|
+
return None, None
|
|
50
|
+
|
|
51
|
+
current = from_part
|
|
52
|
+
while isinstance(current.target, FieldAccess):
|
|
53
|
+
current = current.target
|
|
54
|
+
|
|
55
|
+
if isinstance(current.target, Identifier):
|
|
56
|
+
return current.target.simple_name, current.name.simple_name
|
|
57
|
+
return None, None
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _replace_root_module(from_part: Any, new_root_name: str) -> Any:
|
|
61
|
+
"""Replace the root module name in a FieldAccess chain."""
|
|
62
|
+
if isinstance(from_part, Identifier):
|
|
63
|
+
return from_part.replace(_simple_name=new_root_name)
|
|
64
|
+
if isinstance(from_part, FieldAccess):
|
|
65
|
+
new_target = _replace_root_module(from_part.target, new_root_name)
|
|
66
|
+
return from_part.replace(_target=new_target)
|
|
67
|
+
return from_part
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _get_import_names(multi: MultiImport) -> List[str]:
|
|
71
|
+
"""Get the list of names being imported from a MultiImport."""
|
|
72
|
+
names = []
|
|
73
|
+
for import_node in multi.names:
|
|
74
|
+
qualid = import_node.qualid
|
|
75
|
+
if isinstance(qualid, FieldAccess):
|
|
76
|
+
if isinstance(qualid.name, Identifier):
|
|
77
|
+
names.append(qualid.name.simple_name)
|
|
78
|
+
elif isinstance(qualid, Identifier):
|
|
79
|
+
names.append(qualid.simple_name)
|
|
80
|
+
return names
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _mark_deprecated(tree: Any, message: str) -> Any:
|
|
84
|
+
"""Add a SearchResult marker for a deprecation warning."""
|
|
85
|
+
search_marker = SearchResult(random_id(), message)
|
|
86
|
+
current_markers = tree.markers
|
|
87
|
+
new_markers_list = list(current_markers.markers) + [search_marker]
|
|
88
|
+
new_markers = Markers(current_markers.id, new_markers_list)
|
|
89
|
+
return tree.replace(_markers=new_markers)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@categorize(_LangChain1)
|
|
93
|
+
class ReplaceLangchainClassicImports(Recipe):
|
|
94
|
+
"""
|
|
95
|
+
Replace `langchain` legacy imports with `langchain_classic`.
|
|
96
|
+
|
|
97
|
+
In LangChain v1.0, legacy chains, retrievers, and indexing functionality
|
|
98
|
+
were moved to `langchain_classic`.
|
|
99
|
+
|
|
100
|
+
Example:
|
|
101
|
+
Before:
|
|
102
|
+
from langchain.chains import LLMChain
|
|
103
|
+
|
|
104
|
+
After:
|
|
105
|
+
from langchain_classic.chains import LLMChain
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
@property
|
|
109
|
+
def name(self) -> str:
|
|
110
|
+
return "org.openrewrite.python.migrate.langchain.ReplaceLangchainClassicImports"
|
|
111
|
+
|
|
112
|
+
@property
|
|
113
|
+
def display_name(self) -> str:
|
|
114
|
+
return "Replace `langchain` legacy imports with `langchain_classic`"
|
|
115
|
+
|
|
116
|
+
@property
|
|
117
|
+
def description(self) -> str:
|
|
118
|
+
return (
|
|
119
|
+
"Migrate legacy chain, retriever, and indexing imports from "
|
|
120
|
+
"`langchain` to `langchain_classic`. These were moved in "
|
|
121
|
+
"LangChain v1.0."
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
@property
|
|
125
|
+
def tags(self) -> List[str]:
|
|
126
|
+
return ["python", "migration", "langchain", "1.0"]
|
|
127
|
+
|
|
128
|
+
def editor(self) -> TreeVisitor[Any, ExecutionContext]:
|
|
129
|
+
class Visitor(PythonVisitor[ExecutionContext]):
|
|
130
|
+
def visit_multi_import(
|
|
131
|
+
self, multi: MultiImport, p: ExecutionContext
|
|
132
|
+
) -> Optional[MultiImport]:
|
|
133
|
+
multi = super().visit_multi_import(multi, p)
|
|
134
|
+
|
|
135
|
+
from_part = multi.from_
|
|
136
|
+
if from_part is None:
|
|
137
|
+
return multi
|
|
138
|
+
|
|
139
|
+
# Handle 'from langchain.chains import ...' pattern
|
|
140
|
+
if isinstance(from_part, FieldAccess):
|
|
141
|
+
root_name, first_sub = _get_root_and_first_submodule(from_part)
|
|
142
|
+
if root_name != "langchain":
|
|
143
|
+
return multi
|
|
144
|
+
if first_sub not in CLASSIC_SUBMODULES:
|
|
145
|
+
return multi
|
|
146
|
+
|
|
147
|
+
new_from = _replace_root_module(from_part, "langchain_classic")
|
|
148
|
+
old_padded_from = multi.padding.from_
|
|
149
|
+
if old_padded_from is None:
|
|
150
|
+
return multi
|
|
151
|
+
new_padded_from = old_padded_from.replace(_element=new_from)
|
|
152
|
+
return multi.padding.replace(_from=new_padded_from)
|
|
153
|
+
|
|
154
|
+
# Handle 'from langchain import hub' pattern
|
|
155
|
+
if isinstance(from_part, Identifier) and from_part.simple_name == "langchain":
|
|
156
|
+
imported_names = _get_import_names(multi)
|
|
157
|
+
if all(name in CLASSIC_DIRECT_IMPORTS for name in imported_names):
|
|
158
|
+
new_from = from_part.replace(_simple_name="langchain_classic")
|
|
159
|
+
old_padded_from = multi.padding.from_
|
|
160
|
+
if old_padded_from is None:
|
|
161
|
+
return multi
|
|
162
|
+
new_padded_from = old_padded_from.replace(_element=new_from)
|
|
163
|
+
return multi.padding.replace(_from=new_padded_from)
|
|
164
|
+
|
|
165
|
+
return multi
|
|
166
|
+
|
|
167
|
+
return Visitor()
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
@categorize(_LangChain1)
|
|
171
|
+
class FindDeprecatedLangchainAgents(Recipe):
|
|
172
|
+
"""
|
|
173
|
+
Find deprecated LangChain agent patterns.
|
|
174
|
+
|
|
175
|
+
In LangChain v1.0, the legacy agent helpers `initialize_agent` and
|
|
176
|
+
`AgentExecutor` are deprecated. Use `create_agent` instead.
|
|
177
|
+
|
|
178
|
+
Also detects usage of deprecated `LLMChain` which should be replaced
|
|
179
|
+
with direct LLM invocation or LangGraph agents.
|
|
180
|
+
"""
|
|
181
|
+
|
|
182
|
+
@property
|
|
183
|
+
def name(self) -> str:
|
|
184
|
+
return "org.openrewrite.python.migrate.langchain.FindDeprecatedLangchainAgents"
|
|
185
|
+
|
|
186
|
+
@property
|
|
187
|
+
def display_name(self) -> str:
|
|
188
|
+
return "Find deprecated LangChain agent patterns"
|
|
189
|
+
|
|
190
|
+
@property
|
|
191
|
+
def description(self) -> str:
|
|
192
|
+
return (
|
|
193
|
+
"Find usage of deprecated LangChain agent patterns including "
|
|
194
|
+
"`initialize_agent`, `AgentExecutor`, and `LLMChain`. These "
|
|
195
|
+
"were deprecated in LangChain v0.2 and removed in v1.0."
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
@property
|
|
199
|
+
def tags(self) -> List[str]:
|
|
200
|
+
return ["python", "migration", "langchain", "1.0"]
|
|
201
|
+
|
|
202
|
+
DEPRECATED_IMPORTS: Set[str] = {
|
|
203
|
+
"initialize_agent",
|
|
204
|
+
"AgentExecutor",
|
|
205
|
+
"LLMChain",
|
|
206
|
+
"ConversationChain",
|
|
207
|
+
"LLMRequestsChain",
|
|
208
|
+
"TransformChain",
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
def editor(self) -> TreeVisitor[Any, ExecutionContext]:
|
|
212
|
+
deprecated_imports = self.DEPRECATED_IMPORTS
|
|
213
|
+
|
|
214
|
+
class Visitor(PythonVisitor[ExecutionContext]):
|
|
215
|
+
def visit_multi_import(
|
|
216
|
+
self, multi: MultiImport, p: ExecutionContext
|
|
217
|
+
) -> Optional[MultiImport]:
|
|
218
|
+
multi = super().visit_multi_import(multi, p)
|
|
219
|
+
|
|
220
|
+
from_part = multi.from_
|
|
221
|
+
if from_part is None:
|
|
222
|
+
return multi
|
|
223
|
+
|
|
224
|
+
imported_names = _get_import_names(multi)
|
|
225
|
+
found_deprecated = [
|
|
226
|
+
name for name in imported_names if name in deprecated_imports
|
|
227
|
+
]
|
|
228
|
+
|
|
229
|
+
if found_deprecated:
|
|
230
|
+
names_str = ", ".join(found_deprecated)
|
|
231
|
+
return _mark_deprecated(
|
|
232
|
+
multi,
|
|
233
|
+
f"Deprecated LangChain API: {names_str} "
|
|
234
|
+
f"(use create_agent / LangGraph in v1.0)",
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
return multi
|
|
238
|
+
|
|
239
|
+
return Visitor()
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
@categorize(_LangChain1)
|
|
243
|
+
class FindLangchainCreateReactAgent(Recipe):
|
|
244
|
+
"""
|
|
245
|
+
Find `create_react_agent` imports that should be migrated.
|
|
246
|
+
|
|
247
|
+
In LangChain v1.0, the agent creation function was renamed:
|
|
248
|
+
|
|
249
|
+
from langgraph.prebuilt import create_react_agent
|
|
250
|
+
|
|
251
|
+
should become:
|
|
252
|
+
|
|
253
|
+
from langchain.agents import create_agent
|
|
254
|
+
"""
|
|
255
|
+
|
|
256
|
+
@property
|
|
257
|
+
def name(self) -> str:
|
|
258
|
+
return "org.openrewrite.python.migrate.langchain.FindLangchainCreateReactAgent"
|
|
259
|
+
|
|
260
|
+
@property
|
|
261
|
+
def display_name(self) -> str:
|
|
262
|
+
return "Find `create_react_agent` usage (replace with `create_agent`)"
|
|
263
|
+
|
|
264
|
+
@property
|
|
265
|
+
def description(self) -> str:
|
|
266
|
+
return (
|
|
267
|
+
"Find `from langgraph.prebuilt import create_react_agent` which "
|
|
268
|
+
"should be replaced with `from langchain.agents import create_agent` "
|
|
269
|
+
"in LangChain v1.0."
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
@property
|
|
273
|
+
def tags(self) -> List[str]:
|
|
274
|
+
return ["python", "migration", "langchain", "1.0"]
|
|
275
|
+
|
|
276
|
+
def editor(self) -> TreeVisitor[Any, ExecutionContext]:
|
|
277
|
+
class Visitor(PythonVisitor[ExecutionContext]):
|
|
278
|
+
def visit_multi_import(
|
|
279
|
+
self, multi: MultiImport, p: ExecutionContext
|
|
280
|
+
) -> Optional[MultiImport]:
|
|
281
|
+
multi = super().visit_multi_import(multi, p)
|
|
282
|
+
|
|
283
|
+
from_part = multi.from_
|
|
284
|
+
if from_part is None:
|
|
285
|
+
return multi
|
|
286
|
+
|
|
287
|
+
# Check for 'from langgraph.prebuilt import create_react_agent'
|
|
288
|
+
if not isinstance(from_part, FieldAccess):
|
|
289
|
+
return multi
|
|
290
|
+
if not isinstance(from_part.target, Identifier):
|
|
291
|
+
return multi
|
|
292
|
+
if from_part.target.simple_name != "langgraph":
|
|
293
|
+
return multi
|
|
294
|
+
if not isinstance(from_part.name, Identifier):
|
|
295
|
+
return multi
|
|
296
|
+
if from_part.name.simple_name != "prebuilt":
|
|
297
|
+
return multi
|
|
298
|
+
|
|
299
|
+
imported_names = _get_import_names(multi)
|
|
300
|
+
if "create_react_agent" not in imported_names:
|
|
301
|
+
return multi
|
|
302
|
+
|
|
303
|
+
return _mark_deprecated(
|
|
304
|
+
multi,
|
|
305
|
+
"Migrate to `from langchain.agents import create_agent` (LangChain v1.0)",
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
return Visitor()
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Recipes for migrating langchain imports to langchain_community.
|
|
3
|
+
|
|
4
|
+
In LangChain v0.2, third-party integrations were moved from the `langchain`
|
|
5
|
+
package to `langchain_community`. This recipe rewrites imports like:
|
|
6
|
+
|
|
7
|
+
from langchain.chat_models import ChatOpenAI
|
|
8
|
+
from langchain.llms import OpenAI
|
|
9
|
+
from langchain.embeddings import OpenAIEmbeddings
|
|
10
|
+
|
|
11
|
+
to:
|
|
12
|
+
|
|
13
|
+
from langchain_community.chat_models import ChatOpenAI
|
|
14
|
+
from langchain_community.llms import OpenAI
|
|
15
|
+
from langchain_community.embeddings import OpenAIEmbeddings
|
|
16
|
+
|
|
17
|
+
See: https://python.langchain.com/v0.2/docs/versions/v0_2/
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from typing import Any, List, Optional, Set
|
|
21
|
+
|
|
22
|
+
from rewrite import ExecutionContext, Recipe, TreeVisitor
|
|
23
|
+
from rewrite.category import CategoryDescriptor
|
|
24
|
+
from rewrite.decorators import categorize
|
|
25
|
+
from rewrite.marketplace import Python
|
|
26
|
+
from rewrite.python.visitor import PythonVisitor
|
|
27
|
+
from rewrite.python.tree import MultiImport
|
|
28
|
+
from rewrite.java.tree import FieldAccess, Identifier
|
|
29
|
+
|
|
30
|
+
_LangChain02 = [
|
|
31
|
+
*Python,
|
|
32
|
+
CategoryDescriptor(display_name="Migrate"),
|
|
33
|
+
CategoryDescriptor(display_name="LangChain 0.2"),
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
# Submodules that moved from langchain to langchain_community in v0.2
|
|
37
|
+
COMMUNITY_SUBMODULES: Set[str] = {
|
|
38
|
+
"adapters",
|
|
39
|
+
"agent_toolkits",
|
|
40
|
+
"cache",
|
|
41
|
+
"callbacks",
|
|
42
|
+
"chat_loaders",
|
|
43
|
+
"chat_message_histories",
|
|
44
|
+
"chat_models",
|
|
45
|
+
"cross_encoders",
|
|
46
|
+
"docstore",
|
|
47
|
+
"document_compressors",
|
|
48
|
+
"document_loaders",
|
|
49
|
+
"document_transformers",
|
|
50
|
+
"embeddings",
|
|
51
|
+
"example_selectors",
|
|
52
|
+
"graph_vectorstores",
|
|
53
|
+
"graphs",
|
|
54
|
+
"llms",
|
|
55
|
+
"retrievers",
|
|
56
|
+
"storage",
|
|
57
|
+
"tools",
|
|
58
|
+
"utilities",
|
|
59
|
+
"vectorstores",
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _get_root_and_first_submodule(from_part: Any) -> tuple:
|
|
64
|
+
"""Extract root module name and first submodule from a FieldAccess chain.
|
|
65
|
+
|
|
66
|
+
For 'langchain.chat_models': returns ('langchain', 'chat_models')
|
|
67
|
+
For 'langchain.chat_models.openai': returns ('langchain', 'chat_models')
|
|
68
|
+
"""
|
|
69
|
+
if not isinstance(from_part, FieldAccess):
|
|
70
|
+
return None, None
|
|
71
|
+
|
|
72
|
+
current = from_part
|
|
73
|
+
while isinstance(current.target, FieldAccess):
|
|
74
|
+
current = current.target
|
|
75
|
+
|
|
76
|
+
if isinstance(current.target, Identifier):
|
|
77
|
+
return current.target.simple_name, current.name.simple_name
|
|
78
|
+
return None, None
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _replace_root_module(from_part: Any, new_root_name: str) -> Any:
|
|
82
|
+
"""Replace the root module name in a FieldAccess chain."""
|
|
83
|
+
if isinstance(from_part, Identifier):
|
|
84
|
+
return from_part.replace(_simple_name=new_root_name)
|
|
85
|
+
if isinstance(from_part, FieldAccess):
|
|
86
|
+
new_target = _replace_root_module(from_part.target, new_root_name)
|
|
87
|
+
return from_part.replace(_target=new_target)
|
|
88
|
+
return from_part
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@categorize(_LangChain02)
|
|
92
|
+
class ReplaceLangchainCommunityImports(Recipe):
|
|
93
|
+
"""
|
|
94
|
+
Replace `langchain` integration imports with `langchain_community`.
|
|
95
|
+
|
|
96
|
+
In LangChain v0.2, third-party integrations were split out of the main
|
|
97
|
+
`langchain` package into `langchain_community`. This recipe rewrites
|
|
98
|
+
imports like `from langchain.chat_models import X` to
|
|
99
|
+
`from langchain_community.chat_models import X`.
|
|
100
|
+
|
|
101
|
+
Example:
|
|
102
|
+
Before:
|
|
103
|
+
from langchain.chat_models import ChatOpenAI
|
|
104
|
+
|
|
105
|
+
After:
|
|
106
|
+
from langchain_community.chat_models import ChatOpenAI
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
@property
|
|
110
|
+
def name(self) -> str:
|
|
111
|
+
return "org.openrewrite.python.migrate.langchain.ReplaceLangchainCommunityImports"
|
|
112
|
+
|
|
113
|
+
@property
|
|
114
|
+
def display_name(self) -> str:
|
|
115
|
+
return "Replace `langchain` imports with `langchain_community`"
|
|
116
|
+
|
|
117
|
+
@property
|
|
118
|
+
def description(self) -> str:
|
|
119
|
+
return (
|
|
120
|
+
"Migrate third-party integration imports from `langchain` to "
|
|
121
|
+
"`langchain_community`. These integrations were moved in "
|
|
122
|
+
"LangChain v0.2."
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
@property
|
|
126
|
+
def tags(self) -> List[str]:
|
|
127
|
+
return ["python", "migration", "langchain", "0.2"]
|
|
128
|
+
|
|
129
|
+
def editor(self) -> TreeVisitor[Any, ExecutionContext]:
|
|
130
|
+
class Visitor(PythonVisitor[ExecutionContext]):
|
|
131
|
+
def visit_multi_import(
|
|
132
|
+
self, multi: MultiImport, p: ExecutionContext
|
|
133
|
+
) -> Optional[MultiImport]:
|
|
134
|
+
multi = super().visit_multi_import(multi, p)
|
|
135
|
+
|
|
136
|
+
from_part = multi.from_
|
|
137
|
+
if from_part is None:
|
|
138
|
+
return multi
|
|
139
|
+
|
|
140
|
+
root_name, first_sub = _get_root_and_first_submodule(from_part)
|
|
141
|
+
if root_name != "langchain":
|
|
142
|
+
return multi
|
|
143
|
+
|
|
144
|
+
if first_sub not in COMMUNITY_SUBMODULES:
|
|
145
|
+
return multi
|
|
146
|
+
|
|
147
|
+
new_from = _replace_root_module(from_part, "langchain_community")
|
|
148
|
+
old_padded_from = multi.padding.from_
|
|
149
|
+
if old_padded_from is None:
|
|
150
|
+
return multi
|
|
151
|
+
new_padded_from = old_padded_from.replace(_element=new_from)
|
|
152
|
+
return multi.padding.replace(_from=new_padded_from)
|
|
153
|
+
|
|
154
|
+
return Visitor()
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Recipes for migrating langchain_community imports to provider-specific packages.
|
|
3
|
+
|
|
4
|
+
In LangChain v0.2+, provider-specific classes should be imported from their
|
|
5
|
+
dedicated packages instead of langchain_community. For example:
|
|
6
|
+
|
|
7
|
+
from langchain_community.chat_models import ChatOpenAI
|
|
8
|
+
from langchain_community.embeddings import OpenAIEmbeddings
|
|
9
|
+
from langchain_community.vectorstores import Pinecone
|
|
10
|
+
|
|
11
|
+
should become:
|
|
12
|
+
|
|
13
|
+
from langchain_openai import ChatOpenAI
|
|
14
|
+
from langchain_openai import OpenAIEmbeddings
|
|
15
|
+
from langchain_pinecone import Pinecone
|
|
16
|
+
|
|
17
|
+
See: https://python.langchain.com/v0.2/docs/versions/v0_2/
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from typing import Any, Dict, List, Optional, Set, Tuple
|
|
21
|
+
|
|
22
|
+
from rewrite import ExecutionContext, Recipe, TreeVisitor
|
|
23
|
+
from rewrite.category import CategoryDescriptor
|
|
24
|
+
from rewrite.decorators import categorize
|
|
25
|
+
from rewrite.marketplace import Python
|
|
26
|
+
from rewrite.markers import Markers, SearchResult
|
|
27
|
+
from rewrite.python.visitor import PythonVisitor
|
|
28
|
+
from rewrite.python.tree import MultiImport
|
|
29
|
+
from rewrite.java.tree import FieldAccess, Identifier
|
|
30
|
+
from rewrite.utils import random_id
|
|
31
|
+
|
|
32
|
+
_LangChain02 = [
|
|
33
|
+
*Python,
|
|
34
|
+
CategoryDescriptor(display_name="Migrate"),
|
|
35
|
+
CategoryDescriptor(display_name="LangChain 0.2"),
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
# Maps (submodule, class_name) -> provider_package
|
|
39
|
+
PROVIDER_MAPPING: Dict[Tuple[str, str], str] = {
|
|
40
|
+
# OpenAI
|
|
41
|
+
("chat_models", "ChatOpenAI"): "langchain_openai",
|
|
42
|
+
("chat_models", "AzureChatOpenAI"): "langchain_openai",
|
|
43
|
+
("llms", "OpenAI"): "langchain_openai",
|
|
44
|
+
("llms", "AzureOpenAI"): "langchain_openai",
|
|
45
|
+
("embeddings", "OpenAIEmbeddings"): "langchain_openai",
|
|
46
|
+
("embeddings", "AzureOpenAIEmbeddings"): "langchain_openai",
|
|
47
|
+
# Anthropic
|
|
48
|
+
("chat_models", "ChatAnthropic"): "langchain_anthropic",
|
|
49
|
+
("llms", "Anthropic"): "langchain_anthropic",
|
|
50
|
+
# Google GenAI
|
|
51
|
+
("chat_models", "ChatGoogleGenerativeAI"): "langchain_google_genai",
|
|
52
|
+
("llms", "GoogleGenerativeAI"): "langchain_google_genai",
|
|
53
|
+
("embeddings", "GoogleGenerativeAIEmbeddings"): "langchain_google_genai",
|
|
54
|
+
# Google Vertex AI
|
|
55
|
+
("chat_models", "ChatVertexAI"): "langchain_google_vertexai",
|
|
56
|
+
("llms", "VertexAI"): "langchain_google_vertexai",
|
|
57
|
+
("embeddings", "VertexAIEmbeddings"): "langchain_google_vertexai",
|
|
58
|
+
# AWS
|
|
59
|
+
("chat_models", "ChatBedrock"): "langchain_aws",
|
|
60
|
+
("chat_models", "ChatBedrockConverse"): "langchain_aws",
|
|
61
|
+
("llms", "Bedrock"): "langchain_aws",
|
|
62
|
+
("llms", "BedrockLLM"): "langchain_aws",
|
|
63
|
+
("embeddings", "BedrockEmbeddings"): "langchain_aws",
|
|
64
|
+
# Cohere
|
|
65
|
+
("chat_models", "ChatCohere"): "langchain_cohere",
|
|
66
|
+
("llms", "Cohere"): "langchain_cohere",
|
|
67
|
+
("embeddings", "CohereEmbeddings"): "langchain_cohere",
|
|
68
|
+
# Fireworks
|
|
69
|
+
("chat_models", "ChatFireworks"): "langchain_fireworks",
|
|
70
|
+
("llms", "Fireworks"): "langchain_fireworks",
|
|
71
|
+
("embeddings", "FireworksEmbeddings"): "langchain_fireworks",
|
|
72
|
+
# Groq
|
|
73
|
+
("chat_models", "ChatGroq"): "langchain_groq",
|
|
74
|
+
# MistralAI
|
|
75
|
+
("chat_models", "ChatMistralAI"): "langchain_mistralai",
|
|
76
|
+
("embeddings", "MistralAIEmbeddings"): "langchain_mistralai",
|
|
77
|
+
# Ollama
|
|
78
|
+
("chat_models", "ChatOllama"): "langchain_ollama",
|
|
79
|
+
("llms", "Ollama"): "langchain_ollama",
|
|
80
|
+
("embeddings", "OllamaEmbeddings"): "langchain_ollama",
|
|
81
|
+
# Pinecone
|
|
82
|
+
("vectorstores", "Pinecone"): "langchain_pinecone",
|
|
83
|
+
# Chroma
|
|
84
|
+
("vectorstores", "Chroma"): "langchain_chroma",
|
|
85
|
+
# HuggingFace
|
|
86
|
+
("embeddings", "HuggingFaceEmbeddings"): "langchain_huggingface",
|
|
87
|
+
("embeddings", "HuggingFaceBgeEmbeddings"): "langchain_huggingface",
|
|
88
|
+
("embeddings", "HuggingFaceInstructEmbeddings"): "langchain_huggingface",
|
|
89
|
+
("llms", "HuggingFacePipeline"): "langchain_huggingface",
|
|
90
|
+
("llms", "HuggingFaceEndpoint"): "langchain_huggingface",
|
|
91
|
+
("llms", "HuggingFaceHub"): "langchain_huggingface",
|
|
92
|
+
# Nvidia
|
|
93
|
+
("chat_models", "ChatNVIDIA"): "langchain_nvidia_ai_endpoints",
|
|
94
|
+
("embeddings", "NVIDIAEmbeddings"): "langchain_nvidia_ai_endpoints",
|
|
95
|
+
# IBM
|
|
96
|
+
("chat_models", "ChatWatsonx"): "langchain_ibm",
|
|
97
|
+
("llms", "WatsonxLLM"): "langchain_ibm",
|
|
98
|
+
("embeddings", "WatsonxEmbeddings"): "langchain_ibm",
|
|
99
|
+
# Together
|
|
100
|
+
("chat_models", "ChatTogether"): "langchain_together",
|
|
101
|
+
("llms", "Together"): "langchain_together",
|
|
102
|
+
("embeddings", "TogetherEmbeddings"): "langchain_together",
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _get_import_names(multi: MultiImport) -> List[str]:
|
|
107
|
+
"""Get the list of names being imported from a MultiImport."""
|
|
108
|
+
names = []
|
|
109
|
+
for import_node in multi.names:
|
|
110
|
+
qualid = import_node.qualid
|
|
111
|
+
if isinstance(qualid, FieldAccess):
|
|
112
|
+
if isinstance(qualid.name, Identifier):
|
|
113
|
+
names.append(qualid.name.simple_name)
|
|
114
|
+
elif isinstance(qualid, Identifier):
|
|
115
|
+
names.append(qualid.simple_name)
|
|
116
|
+
return names
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _get_submodule_from_community(from_part: Any) -> Optional[str]:
|
|
120
|
+
"""Get the submodule name from a langchain_community.X import.
|
|
121
|
+
|
|
122
|
+
Returns the submodule name if from_part is 'langchain_community.X',
|
|
123
|
+
None otherwise.
|
|
124
|
+
"""
|
|
125
|
+
if not isinstance(from_part, FieldAccess):
|
|
126
|
+
return None
|
|
127
|
+
if not isinstance(from_part.target, Identifier):
|
|
128
|
+
return None
|
|
129
|
+
if from_part.target.simple_name != "langchain_community":
|
|
130
|
+
return None
|
|
131
|
+
if isinstance(from_part.name, Identifier):
|
|
132
|
+
return from_part.name.simple_name
|
|
133
|
+
return None
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _mark_provider_migration(tree: Any, message: str) -> Any:
|
|
137
|
+
"""Add a SearchResult marker for a provider migration suggestion."""
|
|
138
|
+
search_marker = SearchResult(random_id(), message)
|
|
139
|
+
current_markers = tree.markers
|
|
140
|
+
new_markers_list = list(current_markers.markers) + [search_marker]
|
|
141
|
+
new_markers = Markers(current_markers.id, new_markers_list)
|
|
142
|
+
return tree.replace(_markers=new_markers)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@categorize(_LangChain02)
|
|
146
|
+
class ReplaceLangchainProviderImports(Recipe):
|
|
147
|
+
"""
|
|
148
|
+
Replace `langchain_community` imports with provider-specific packages.
|
|
149
|
+
|
|
150
|
+
Provider-specific classes should be imported from their dedicated packages
|
|
151
|
+
for better dependency management and versioning. This recipe auto-fixes
|
|
152
|
+
imports when all imported classes map to the same provider package.
|
|
153
|
+
|
|
154
|
+
Example:
|
|
155
|
+
Before:
|
|
156
|
+
from langchain_community.chat_models import ChatOpenAI
|
|
157
|
+
|
|
158
|
+
After:
|
|
159
|
+
from langchain_openai import ChatOpenAI
|
|
160
|
+
"""
|
|
161
|
+
|
|
162
|
+
@property
|
|
163
|
+
def name(self) -> str:
|
|
164
|
+
return "org.openrewrite.python.migrate.langchain.ReplaceLangchainProviderImports"
|
|
165
|
+
|
|
166
|
+
@property
|
|
167
|
+
def display_name(self) -> str:
|
|
168
|
+
return "Replace `langchain_community` imports with provider packages"
|
|
169
|
+
|
|
170
|
+
@property
|
|
171
|
+
def description(self) -> str:
|
|
172
|
+
return (
|
|
173
|
+
"Migrate provider-specific imports from `langchain_community` to "
|
|
174
|
+
"dedicated provider packages like `langchain_openai`, "
|
|
175
|
+
"`langchain_anthropic`, etc."
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
@property
|
|
179
|
+
def tags(self) -> List[str]:
|
|
180
|
+
return ["python", "migration", "langchain", "0.2"]
|
|
181
|
+
|
|
182
|
+
def editor(self) -> TreeVisitor[Any, ExecutionContext]:
|
|
183
|
+
class Visitor(PythonVisitor[ExecutionContext]):
|
|
184
|
+
def visit_multi_import(
|
|
185
|
+
self, multi: MultiImport, p: ExecutionContext
|
|
186
|
+
) -> Optional[MultiImport]:
|
|
187
|
+
multi = super().visit_multi_import(multi, p)
|
|
188
|
+
|
|
189
|
+
from_part = multi.from_
|
|
190
|
+
if from_part is None:
|
|
191
|
+
return multi
|
|
192
|
+
|
|
193
|
+
submodule = _get_submodule_from_community(from_part)
|
|
194
|
+
if submodule is None:
|
|
195
|
+
return multi
|
|
196
|
+
|
|
197
|
+
imported_names = _get_import_names(multi)
|
|
198
|
+
if not imported_names:
|
|
199
|
+
return multi
|
|
200
|
+
|
|
201
|
+
# Look up provider packages for all imported names
|
|
202
|
+
providers: Set[str] = set()
|
|
203
|
+
has_unmapped = False
|
|
204
|
+
for class_name in imported_names:
|
|
205
|
+
key = (submodule, class_name)
|
|
206
|
+
if key in PROVIDER_MAPPING:
|
|
207
|
+
providers.add(PROVIDER_MAPPING[key])
|
|
208
|
+
else:
|
|
209
|
+
has_unmapped = True
|
|
210
|
+
|
|
211
|
+
if not providers:
|
|
212
|
+
return multi
|
|
213
|
+
|
|
214
|
+
# Auto-fix only when all classes map to the same provider
|
|
215
|
+
# and none are unmapped
|
|
216
|
+
if len(providers) == 1 and not has_unmapped:
|
|
217
|
+
provider_package = providers.pop()
|
|
218
|
+
new_from = Identifier(
|
|
219
|
+
_id=random_id(),
|
|
220
|
+
_prefix=from_part.prefix,
|
|
221
|
+
_markers=Markers.EMPTY,
|
|
222
|
+
_annotations=[],
|
|
223
|
+
_simple_name=provider_package,
|
|
224
|
+
_type=None,
|
|
225
|
+
_field_type=None,
|
|
226
|
+
)
|
|
227
|
+
old_padded_from = multi.padding.from_
|
|
228
|
+
if old_padded_from is None:
|
|
229
|
+
return multi
|
|
230
|
+
new_padded_from = old_padded_from.replace(_element=new_from)
|
|
231
|
+
return multi.padding.replace(_from=new_padded_from)
|
|
232
|
+
|
|
233
|
+
# Flag mixed-provider imports for manual migration
|
|
234
|
+
provider_list = ", ".join(sorted(providers))
|
|
235
|
+
return _mark_provider_migration(
|
|
236
|
+
multi,
|
|
237
|
+
f"Split this import across provider packages: {provider_list}",
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
return Visitor()
|