langchain-openapi-tools 1.1.3__tar.gz → 2.0.0__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.
Files changed (149) hide show
  1. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/ARCHITECTURE.md +57 -13
  2. langchain_openapi_tools-2.0.0/CHANGELOG.md +58 -0
  3. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/PKG-INFO +56 -3
  4. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/README.md +55 -2
  5. langchain_openapi_tools-2.0.0/docs/architecture.md +72 -0
  6. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/docs/toolkit.md +12 -0
  7. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/langchain_openapi_tools/__init__.py +14 -0
  8. langchain_openapi_tools-2.0.0/langchain_openapi_tools/adapters.py +126 -0
  9. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/langchain_openapi_tools/executor.py +146 -8
  10. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/langchain_openapi_tools/loader.py +12 -7
  11. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/langchain_openapi_tools/models.py +26 -5
  12. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/langchain_openapi_tools/parser.py +130 -16
  13. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/langchain_openapi_tools/schema_converter.py +105 -3
  14. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/langchain_openapi_tools/swagger.py +26 -1
  15. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/pyproject.toml +1 -1
  16. langchain_openapi_tools-2.0.0/tests/test_adapters.py +96 -0
  17. langchain_openapi_tools-2.0.0/tests/test_base_url_resolution.py +324 -0
  18. langchain_openapi_tools-2.0.0/tests/test_compat_matrix.py +486 -0
  19. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/tests/test_loader.py +4 -1
  20. langchain_openapi_tools-2.0.0/tests/test_media_types.py +204 -0
  21. langchain_openapi_tools-2.0.0/tests/test_openapi_31.py +190 -0
  22. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/uv.lock +1 -1
  23. langchain_openapi_tools-1.1.3/CHANGELOG.md +0 -36
  24. langchain_openapi_tools-1.1.3/docs/architecture.md +0 -60
  25. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  26. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/.github/ISSUE_TEMPLATE/documentation.md +0 -0
  27. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  28. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  29. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/.github/dependabot.yml +0 -0
  30. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/.github/workflows/ci.yml +0 -0
  31. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/.github/workflows/pages.yml +0 -0
  32. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/.github/workflows/publish.yml +0 -0
  33. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/.github/workflows/release.yml +0 -0
  34. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/.gitignore +0 -0
  35. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/.pre-commit-config.yaml +0 -0
  36. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/CODE_OF_CONDUCT.md +0 -0
  37. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/CONTRIBUTING.md +0 -0
  38. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/LICENSE +0 -0
  39. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/Makefile +0 -0
  40. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/benchmarks/benchmark.py +0 -0
  41. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/docs/api/executor.md +0 -0
  42. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/docs/api/loader.md +0 -0
  43. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/docs/api/middleware.md +0 -0
  44. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/docs/api/parser.md +0 -0
  45. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/docs/api/toolkit.md +0 -0
  46. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/docs/authentication.md +0 -0
  47. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/docs/contributing.md +0 -0
  48. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/docs/examples/crossref.md +0 -0
  49. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/docs/examples/petstore.md +0 -0
  50. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/docs/index.md +0 -0
  51. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/docs/installation.md +0 -0
  52. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/docs/middleware.md +0 -0
  53. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/docs/prompt_optimization.md +0 -0
  54. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/docs/quickstart.md +0 -0
  55. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/examples/README.md +0 -0
  56. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/examples/crossref/README.md +0 -0
  57. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/examples/crossref/crossref.json +0 -0
  58. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/examples/crossref/main.py +0 -0
  59. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/examples/crossref/requirements.txt +0 -0
  60. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/examples/petstore/README.md +0 -0
  61. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/examples/petstore/main.py +0 -0
  62. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/examples/petstore/petstore.json +0 -0
  63. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/examples/petstore/requirements.txt +0 -0
  64. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/langchain_openapi/__init__.py +0 -0
  65. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/langchain_openapi/py.typed +0 -0
  66. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/langchain_openapi_tools/enums.py +0 -0
  67. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/langchain_openapi_tools/exceptions.py +0 -0
  68. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/langchain_openapi_tools/middleware.py +0 -0
  69. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/langchain_openapi_tools/providers.py +0 -0
  70. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/langchain_openapi_tools/py.typed +0 -0
  71. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/langchain_openapi_tools/toolkit.py +0 -0
  72. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/langchain_openapi_tools/utils.py +0 -0
  73. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/mkdocs.yml +0 -0
  74. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/404.html +0 -0
  75. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/api/executor/index.html +0 -0
  76. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/api/loader/index.html +0 -0
  77. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/api/middleware/index.html +0 -0
  78. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/api/parser/index.html +0 -0
  79. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/api/toolkit/index.html +0 -0
  80. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/architecture/index.html +0 -0
  81. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/assets/_mkdocstrings.css +0 -0
  82. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/assets/images/favicon.png +0 -0
  83. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/assets/javascripts/bundle.d7400e89.min.js +0 -0
  84. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/assets/javascripts/bundle.d7400e89.min.js.map +0 -0
  85. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/assets/javascripts/lunr/min/lunr.ar.min.js +0 -0
  86. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/assets/javascripts/lunr/min/lunr.da.min.js +0 -0
  87. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/assets/javascripts/lunr/min/lunr.de.min.js +0 -0
  88. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/assets/javascripts/lunr/min/lunr.du.min.js +0 -0
  89. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/assets/javascripts/lunr/min/lunr.el.min.js +0 -0
  90. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/assets/javascripts/lunr/min/lunr.es.min.js +0 -0
  91. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/assets/javascripts/lunr/min/lunr.fi.min.js +0 -0
  92. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/assets/javascripts/lunr/min/lunr.fr.min.js +0 -0
  93. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/assets/javascripts/lunr/min/lunr.he.min.js +0 -0
  94. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/assets/javascripts/lunr/min/lunr.hi.min.js +0 -0
  95. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/assets/javascripts/lunr/min/lunr.hu.min.js +0 -0
  96. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/assets/javascripts/lunr/min/lunr.hy.min.js +0 -0
  97. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/assets/javascripts/lunr/min/lunr.it.min.js +0 -0
  98. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/assets/javascripts/lunr/min/lunr.ja.min.js +0 -0
  99. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/assets/javascripts/lunr/min/lunr.jp.min.js +0 -0
  100. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/assets/javascripts/lunr/min/lunr.kn.min.js +0 -0
  101. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/assets/javascripts/lunr/min/lunr.ko.min.js +0 -0
  102. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/assets/javascripts/lunr/min/lunr.multi.min.js +0 -0
  103. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/assets/javascripts/lunr/min/lunr.nl.min.js +0 -0
  104. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/assets/javascripts/lunr/min/lunr.no.min.js +0 -0
  105. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/assets/javascripts/lunr/min/lunr.pt.min.js +0 -0
  106. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/assets/javascripts/lunr/min/lunr.ro.min.js +0 -0
  107. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/assets/javascripts/lunr/min/lunr.ru.min.js +0 -0
  108. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/assets/javascripts/lunr/min/lunr.sa.min.js +0 -0
  109. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/assets/javascripts/lunr/min/lunr.stemmer.support.min.js +0 -0
  110. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/assets/javascripts/lunr/min/lunr.sv.min.js +0 -0
  111. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/assets/javascripts/lunr/min/lunr.ta.min.js +0 -0
  112. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/assets/javascripts/lunr/min/lunr.te.min.js +0 -0
  113. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/assets/javascripts/lunr/min/lunr.th.min.js +0 -0
  114. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/assets/javascripts/lunr/min/lunr.tr.min.js +0 -0
  115. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/assets/javascripts/lunr/min/lunr.vi.min.js +0 -0
  116. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/assets/javascripts/lunr/min/lunr.zh.min.js +0 -0
  117. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/assets/javascripts/lunr/tinyseg.js +0 -0
  118. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/assets/javascripts/lunr/wordcut.js +0 -0
  119. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/assets/javascripts/workers/search.2c215733.min.js +0 -0
  120. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/assets/javascripts/workers/search.2c215733.min.js.map +0 -0
  121. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/assets/stylesheets/main.ec1eaa64.min.css +0 -0
  122. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/assets/stylesheets/main.ec1eaa64.min.css.map +0 -0
  123. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/assets/stylesheets/palette.ab4e12ef.min.css +0 -0
  124. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/assets/stylesheets/palette.ab4e12ef.min.css.map +0 -0
  125. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/authentication/index.html +0 -0
  126. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/contributing/index.html +0 -0
  127. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/examples/crossref/index.html +0 -0
  128. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/examples/petstore/index.html +0 -0
  129. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/index.html +0 -0
  130. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/installation/index.html +0 -0
  131. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/middleware/index.html +0 -0
  132. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/objects.inv +0 -0
  133. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/prompt_optimization/index.html +0 -0
  134. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/quickstart/index.html +0 -0
  135. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/search/search_index.json +0 -0
  136. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/sitemap.xml +0 -0
  137. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/sitemap.xml.gz +0 -0
  138. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/site/toolkit/index.html +0 -0
  139. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/tests/test_compatibility.py +0 -0
  140. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/tests/test_e2e.py +0 -0
  141. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/tests/test_executor.py +0 -0
  142. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/tests/test_import.py +0 -0
  143. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/tests/test_middleware.py +0 -0
  144. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/tests/test_parser.py +0 -0
  145. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/tests/test_providers.py +0 -0
  146. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/tests/test_resolver.py +0 -0
  147. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/tests/test_schema_converter.py +0 -0
  148. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/tests/test_swagger.py +0 -0
  149. {langchain_openapi_tools-1.1.3 → langchain_openapi_tools-2.0.0}/tests/test_toolkit.py +0 -0
@@ -30,13 +30,24 @@ The library is structured as a linear transform pipeline with isolated responsib
30
30
  │ OpenAPI Loader │
31
31
  │ - Parsing (JSON / YAML) │
32
32
  │ - Ref Resolution ($ref expansion) │
33
+ │ - Remembers spec source URL │
34
+ └───────────────────────────┬────────────────────────────┘
35
+
36
+
37
+ ┌────────────────────────────────────────────────────────┐
38
+ │ Spec Adapter Layer │
39
+ │ - Version detection (swagger2 / 3.0 / 3.1) │
40
+ │ - Swagger2Adapter → OpenAPI 3.0 normalization │
41
+ │ - OpenAPI3Adapter (pass-through, 3.0 & 3.1) │
33
42
  └───────────────────────────┬────────────────────────────┘
34
43
 
35
44
 
36
45
  ┌────────────────────────────────────────────────────────┐
37
46
  │ Internal Models │
38
47
  │ - Parsed Spec & Operation trees │
39
- │ - Schema normalization
48
+ │ - Schema normalization (incl. oneOf/anyOf/
49
+ │ allOf / const / read-write flags) │
50
+ │ - `spec_family` tag preserved from adapter │
40
51
  └───────────────────────────┬────────────────────────────┘
41
52
 
42
53
 
@@ -75,7 +86,31 @@ The library is structured as a linear transform pipeline with isolated responsib
75
86
 
76
87
  The **OpenAPI Loader** is responsible for reading OpenAPI specifications from local file paths, raw strings, or remote HTTP URLs. It handles format auto-detection (JSON vs. YAML), syntax validation, and recursively resolves relative and external `$ref` pointers into a unified, flattened schema representation.
77
88
 
78
- ### 3.2 Internal Models & Specification Parser
89
+ When loading from a URL, the loader preserves the source URL on the resulting `OpenAPISpec` (`spec.source_url`). This lets `OpenAPISpec.from_dict` and `SwaggerNormalizer` resolve relative `servers` entries and missing `host` fields to absolute URLs, so `RequestBuilder` always receives an absolute base URL — even for specs like `https://fakerestapi.azurewebsites.net/swagger/v1/swagger.json` that omit the `servers` block entirely.
90
+
91
+ ### 3.2 Spec Adapter Layer
92
+
93
+ The **Spec Adapter Layer** (`langchain_openapi_tools.adapters`) provides a
94
+ uniform entry point across every supported specification family. It exposes:
95
+
96
+ * `detect_spec_version(spec_dict) -> Literal["swagger2", "openapi30", "openapi31"]`
97
+ * `SpecAdapter` (`typing.Protocol`) — the shared adapter interface.
98
+ * `Swagger2Adapter` — wraps `SwaggerNormalizer` to lift Swagger 2.0 documents
99
+ into an OpenAPI 3.0-compatible dictionary while remembering the source URL
100
+ so `host`/`basePath` gaps can be filled in from the fetch location.
101
+ * `OpenAPI3Adapter` — a pass-through adapter for both OpenAPI 3.0.x and
102
+ 3.1.x documents.
103
+ * `select_adapter(spec_dict)` and `normalize_spec(spec_dict, source_url)` —
104
+ convenience helpers that pick the right adapter and return a `(normalized
105
+ dict, spec_family)` tuple.
106
+
107
+ `OpenAPISpec.from_dict` calls `normalize_spec` before parsing so downstream
108
+ components (`OpenAPIParser`, `SchemaConverter`, `RequestBuilder`) only ever
109
+ see a single canonical shape. `OpenAPISpec.spec_family` records which
110
+ family the document originated from, which is useful for logging and for
111
+ version-aware feature toggles in the future.
112
+
113
+ ### 3.3 Internal Models & Specification Parser
79
114
 
80
115
  The **OpenAPI Parser** (`OpenAPIParser`) transforms an `OpenAPISpec` into strongly typed, normalized Python dataclasses using the `ReferenceResolver` for local `$ref` pointer resolution:
81
116
 
@@ -99,16 +134,19 @@ OpenAPI Spec (File / URL / Dict)
99
134
  * **`RequestBody`**: Defines the payload expected by POST/PUT/PATCH operations (`required`, `description`, `content`).
100
135
  * **`Response`**: Defines an HTTP response status definition (`status_code`, `description`, `content`).
101
136
  * **`MediaType`**: Maps a MIME content-type string to its schema and example payloads (`content_type`, `schema`, `example`, `examples`).
102
- * **`Schema`**: Normalized schema structure representing primitive and complex types (`type`, `format`, `properties`, `items`, `required`, `enum`, `default`, `nullable`, `description`).
137
+ * **`Schema`**: Normalized schema structure representing primitive and complex types (`type`, `format`, `properties`, `items`, `required`, `enum`, `default`, `nullable`, `description`, `const`, `one_of`, `any_of`, `all_of`, `read_only`, `write_only`, `deprecated`).
103
138
 
104
139
  #### Current Scope Limitations
105
140
 
106
- The Milestone 3 parser intentionally skips:
107
- - Polymorphic composition keywords (`oneOf`, `anyOf`, `allOf`, `discriminator`).
141
+ The parser handles JSON Schema union keywords (`oneOf`, `anyOf`, `allOf`) and
142
+ OpenAPI 3.1 features such as list-form `type` and `const`, but intentionally
143
+ skips:
144
+ - `discriminator`-driven union routing.
108
145
  - OpenAPI callbacks, links, and webhooks.
146
+ - `patternProperties`, `$dynamicRef`, `$dynamicAnchor`.
109
147
  - Advanced example inheritance chains.
110
148
 
111
- ### 3.3 Schema Converter
149
+ ### 3.4 Schema Converter
112
150
 
113
151
  The **Schema Converter** (`SchemaConverter` and `PydanticFactory`) converts internal schema models (`Schema`) into dynamic Pydantic `BaseModel` classes using `pydantic.create_model()`.
114
152
 
@@ -135,6 +173,11 @@ Dynamic Pydantic Model (type[BaseModel])
135
173
  | `array` | `list[T]` | Recursively typed items (e.g. `list[str]`) |
136
174
  | `object` | Dynamic `BaseModel` | Dynamic nested models (e.g. `SearchWorksInput_Address`) |
137
175
  | `enum` | Dynamic `Enum` | String Enum subclass (e.g. `SortEnum`) |
176
+ | `oneOf` / `anyOf` | `typing.Union[...]` | Variants are recursively converted |
177
+ | `allOf` | Merged `BaseModel` | Properties and `required` sets are combined |
178
+ | `type: [X, "null"]` | `Optional[X]` | 3.1-style nullable union |
179
+ | `nullable: true` | `Optional[X]` | 3.0-style nullable flag |
180
+ | `const` | `Literal[value]` | Fixed constant value |
138
181
 
139
182
  #### Model Naming Conventions
140
183
 
@@ -144,13 +187,14 @@ Dynamic Pydantic Model (type[BaseModel])
144
187
 
145
188
  #### Current Limitations
146
189
 
147
- The converter currently skips:
148
- - Polymorphic composition keywords (`oneOf`, `anyOf`, `allOf`, `discriminator`).
190
+ The converter now covers `oneOf` / `anyOf` / `allOf`, list-form `type`,
191
+ `nullable`, and `const`. It intentionally does not yet cover:
192
+ - `discriminator`-driven union routing (variants are unions without tag dispatch).
149
193
  - Recursive schemas.
150
- - XML schema options.
151
- - Complex union types beyond `Optional`.
194
+ - XML schema-driven request serialization.
195
+ - `patternProperties`, `$dynamicRef`, `$dynamicAnchor`.
152
196
 
153
- ### 3.4 Async HTTP Executor Engine
197
+ ### 3.5 Async HTTP Executor Engine
154
198
 
155
199
  The **Async HTTP Executor** (`AsyncHTTPExecutor`) executes an `Operation` asynchronously using `httpx.AsyncClient`, transforming inputs into normalized `ResponseData` instances.
156
200
 
@@ -190,11 +234,11 @@ OpenAPIError
190
234
  └── ExecutionTimeoutError
191
235
  ```
192
236
 
193
- ### 3.5 Authentication Provider
237
+ ### 3.6 Authentication Provider
194
238
 
195
239
  The **Authentication Provider** manages auth headers and query parameters dynamically. It supports multiple authentication schemes (API Key, HTTP Bearer, HTTP Basic, OAuth2) and securely injects credentials into outbound HTTP requests without exposing secret values to the LLM agent or tool definitions.
196
240
 
197
- ### 3.6 Tool Generator
241
+ ### 3.7 Tool Generator
198
242
 
199
243
  The **Tool Generator** acts as the high-level factory interface for end users. It converts parsed operations into `langchain_core.tools.BaseTool` instances. It supports configuration options such as filtering by HTTP method, operation tags, or path patterns, as well as customizing tool naming conventions and prompt description templates.
200
244
 
@@ -0,0 +1,58 @@
1
+ # Changelog
2
+
3
+ All notable changes to `langchain-openapi` will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ---
9
+
10
+ ## [Unreleased]
11
+
12
+ ### Added
13
+ - **Spec Adapter Architecture (`langchain_openapi_tools.adapters`)**: Introduced a `SpecAdapter` protocol plus concrete `Swagger2Adapter` and `OpenAPI3Adapter` implementations that provide a uniform pipeline entry for Swagger 2.0, OpenAPI 3.0.x, and OpenAPI 3.1.x documents. New public helpers: `detect_spec_version`, `select_adapter`, and `normalize_spec`. `OpenAPISpec.spec_family` records the source family, and `OpenAPISpec.from_dict` now routes through the adapter layer.
14
+ - **OpenAPI 3.1 Schema Support**: Parser and `SchemaConverter` now understand union `type` arrays (`["string", "null"]`), `const`, `oneOf` / `anyOf` (rendered as `typing.Union`), `allOf` (merged into a single Pydantic model), and the `readOnly` / `writeOnly` / `deprecated` field flags. `Schema` gained `const`, `one_of`, `any_of`, `all_of`, `read_only`, `write_only`, and `deprecated` attributes.
15
+ - **Full Media-Type Dispatch**: `RequestBuilder` now inspects each operation's declared `requestBody` content types and dispatches to the correct httpx encoding path — `application/json` (and `+json` vendor variants), `application/x-www-form-urlencoded`, `multipart/form-data` (with file-part support), `text/*`, and `application/xml`. Pydantic-model bodies produced by the dynamic args schema are coerced back to JSON-safe primitives before serialization. `BuiltRequest` gained `data`, `files`, and `content` fields.
16
+ - **Real-World Compatibility Tests**: Added `tests/test_adapters.py`, `tests/test_openapi_31.py`, `tests/test_media_types.py`, and `tests/test_compat_matrix.py` (Petstore, Crossref, GitHub, Stripe, Kubernetes, ASP.NET fakerestapi excerpts) exercising the full pipeline end-to-end via `respx`.
17
+
18
+ ### Fixed
19
+ - **Base URL Resolution Across the Pipeline**: The specification's source URL is now propagated from `OpenAPILoader.from_url` → `OpenAPISpec` → `OpenAPIParser` → `RequestBuilder`. This closes an architectural gap where relative or missing `servers` entries produced relative request URLs.
20
+ - OpenAPI 3.x specs that omit the `servers` block (e.g. `https://fakerestapi.azurewebsites.net/swagger/v1/swagger.json`) now default to the document's `scheme://host` origin, matching the OpenAPI 3.x specification.
21
+ - OpenAPI 3.x relative server URLs (e.g. `"/api/v2"`) are resolved against the document location via `urllib.parse.urljoin`.
22
+ - Swagger 2.0 specs that omit `host` fall back to the source URL's host and scheme.
23
+ - `OpenAPISpec` gained a `source_url` field; `OpenAPISpec.from_dict` and `SwaggerNormalizer` accept an optional `source_url` argument for programmatic use.
24
+ - Redundant Swagger normalization calls in `OpenAPILoader.load` and `OpenAPIParser.__init__` were removed — `OpenAPISpec.from_dict` is now the single normalization site.
25
+ - No changes to `RequestBuilder` or `AsyncHTTPExecutor` were required to fix base-URL resolution; they transparently receive an absolute base URL because `spec.servers[0]` is guaranteed absolute whenever the spec was loaded from a URL.
26
+
27
+ ### Compatibility
28
+ - All previously public APIs (`OpenAPIToolkit`, `OpenAPILoader`, `OpenAPIParser`, `OpenAPISpec`, `SwaggerNormalizer`, `RequestBuilder`, `AsyncHTTPExecutor`, middleware, providers) retain their original signatures. New parameters (`source_url`, `spec_family`) are optional.
29
+
30
+ ---
31
+
32
+ ## [1.0.2] - 2026-08-01
33
+
34
+ ### Fixed
35
+ - **Base URL Generation**: Fixed Swagger 2.0 base URL generation (e.g. Crossref host/basePath/schemes resolution) to prevent relative URL execution errors.
36
+ - **URL Resolution**: Updated `RequestBuilder` to use `urllib.parse.urljoin` for safe URL concatenation.
37
+
38
+ ### Added
39
+ - **Package Naming Clarification**: Clarified PyPI package name (`langchain-openapi-tools`) vs import module name (`langchain_openapi`) across documentation.
40
+ - **Prompt Optimization & Config**: Introduced `OpenAPIToolkitConfig` for user-controlled description modes (`full`, `compact`, `minimal`) and description prompt compression (`compress_descriptions=True`).
41
+ - **Tool Description Customization**: Added `tool_description_overrides` dict and custom `description_builder` callback functions.
42
+ - **Operation & Tag Filtering**: Added tag filtering (`include_tags`, `exclude_tags`) and operation filtering (`include_operations`, `exclude_operations`) at initial toolkit construction level.
43
+
44
+ ---
45
+
46
+ ## [1.0.0] - 2026-08-01
47
+
48
+ ### Added
49
+ - **First Production Release (v1.0.0)**.
50
+ - **OpenAPI Loader**: Load specs from remote URLs, local JSON/YAML files, or Python dictionaries.
51
+ - **OpenAPI Parser**: Strongly typed internal data models for operations, parameters, request bodies, and responses with local `$ref` resolution.
52
+ - **Schema Converter Engine**: Convert JSON Schema parameters and request bodies to dynamic Pydantic models at runtime with optional multi-page pagination controls.
53
+ - **Async HTTP Executor Engine**: Asynchronous execution using `httpx.AsyncClient` with dynamic parameter formatting and response parsing.
54
+ - **LangChain Tool Factory**: Convert OpenAPI operations into dynamic LangChain `StructuredTool` collections with filtering options.
55
+ - **Authentication & Request Providers**: Pluggable provider system (`BearerAuthProvider`, `APIKeyHeaderProvider`, `APIKeyQueryProvider`, `BasicAuthProvider`, `CompositeProvider`).
56
+ - **Production Middleware**: Onion-style pipeline (`RetryMiddleware`, `RateLimitMiddleware`, `CacheMiddleware`, `PaginationMiddleware`, `LoggingMiddleware`).
57
+ - **Documentation & Examples**: Comprehensive MkDocs site, complete example applications (Crossref, GitHub, Petstore, JSONPlaceholder), and benchmark suite.
58
+ - **CI/CD & Release Automation**: GitHub Actions for CI, Release generation, PyPI Trusted Publishing, and GitHub Pages deployment.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: langchain-openapi-tools
3
- Version: 1.1.3
3
+ Version: 2.0.0
4
4
  Summary: Convert OpenAPI specifications into native, production-grade LangChain tools.
5
5
  Project-URL: Homepage, https://github.com/abhaywani114/langchain-openapi
6
6
  Project-URL: Documentation, https://abhaywani114.github.io/langchain-openapi/
@@ -109,9 +109,38 @@ from langchain_openapi_tools import OpenAPIToolkit
109
109
 
110
110
  ## Supported Specifications
111
111
 
112
- - ✅ **Swagger 2.0** (Automatically normalized to OpenAPI 3.0)
112
+ - ✅ **Swagger 2.0** (Automatically normalized to OpenAPI 3.0 via the built-in adapter layer)
113
113
  - ✅ **OpenAPI 3.0.x** (JSON and YAML)
114
- - ✅ **OpenAPI 3.1.x** (JSON and YAML)
114
+ - ✅ **OpenAPI 3.1.x** (JSON and YAML, including union `type` arrays, `oneOf` / `anyOf` / `allOf`, and `const`)
115
+
116
+ > **Note:** Swagger 2.0 is commonly referred to as OpenAPI 2.0.
117
+
118
+ ### Compatibility Matrix
119
+
120
+ | Feature | Swagger 2.0 | OpenAPI 3.0 | OpenAPI 3.1 |
121
+ | -------------------------------------- | :---------: | :---------: | :---------: |
122
+ | Path / query / header / cookie params | ✅ | ✅ | ✅ |
123
+ | `$ref` resolution (internal) | ✅ | ✅ | ✅ |
124
+ | Base URL from `host` + `basePath` | ✅ | — | — |
125
+ | Base URL from `servers[]` | — | ✅ | ✅ |
126
+ | Fallback base URL from spec source URL | ✅ | ✅ | ✅ |
127
+ | `application/json` request bodies | ✅ | ✅ | ✅ |
128
+ | `application/x-www-form-urlencoded` | ✅ | ✅ | ✅ |
129
+ | `multipart/form-data` (incl. files) | ✅ | ✅ | ✅ |
130
+ | `text/*` and `application/xml` | ✅ | ✅ | ✅ |
131
+ | Vendor `+json` media types | ✅ | ✅ | ✅ |
132
+ | `oneOf` / `anyOf` (as Python `Union`) | — | ✅ | ✅ |
133
+ | `allOf` merge into single model | — | ✅ | ✅ |
134
+ | `nullable: true` (3.0) | — | ✅ | — |
135
+ | `type: ["string", "null"]` (3.1) | — | — | ✅ |
136
+ | `const` | — | — | ✅ |
137
+ | `readOnly` / `writeOnly` / `deprecated`| ✅ | ✅ | ✅ |
138
+ | Security: Bearer / API key / Basic | ✅ | ✅ | ✅ |
139
+ | Async execution (`httpx.AsyncClient`) | ✅ | ✅ | ✅ |
140
+
141
+ Not yet supported: `discriminator`-driven union routing, `patternProperties`,
142
+ `$dynamicRef` / `$dynamicAnchor`, callbacks, links, webhooks, and XML request-body
143
+ serialization from Python dicts.
115
144
 
116
145
  ---
117
146
 
@@ -256,3 +285,27 @@ toolkit = OpenAPIToolkit.from_url(
256
285
 
257
286
  LangChain StructuredTools
258
287
  ```
288
+
289
+ ### Example:
290
+
291
+ ```python
292
+ from langchain_openapi_tools import OpenAPIToolkit
293
+ from langchain.agents import create_agent
294
+ from langchain.chat_models import init_chat_model
295
+
296
+ toolkit = OpenAPIToolkit.from_url(
297
+ "https://api.crossref.org/swagger-docs"
298
+ )
299
+
300
+ model = init_chat_model("openrouter:qwen/qwen3-32b:free")
301
+
302
+ agent = create_agent(
303
+ model=model,
304
+ tools=toolkit.get_tools(tags=["Works"]),
305
+ )
306
+
307
+ response = agent.invoke({
308
+ "messages": "Find papers about Kashmir"
309
+ })
310
+
311
+ ```
@@ -69,9 +69,38 @@ from langchain_openapi_tools import OpenAPIToolkit
69
69
 
70
70
  ## Supported Specifications
71
71
 
72
- - ✅ **Swagger 2.0** (Automatically normalized to OpenAPI 3.0)
72
+ - ✅ **Swagger 2.0** (Automatically normalized to OpenAPI 3.0 via the built-in adapter layer)
73
73
  - ✅ **OpenAPI 3.0.x** (JSON and YAML)
74
- - ✅ **OpenAPI 3.1.x** (JSON and YAML)
74
+ - ✅ **OpenAPI 3.1.x** (JSON and YAML, including union `type` arrays, `oneOf` / `anyOf` / `allOf`, and `const`)
75
+
76
+ > **Note:** Swagger 2.0 is commonly referred to as OpenAPI 2.0.
77
+
78
+ ### Compatibility Matrix
79
+
80
+ | Feature | Swagger 2.0 | OpenAPI 3.0 | OpenAPI 3.1 |
81
+ | -------------------------------------- | :---------: | :---------: | :---------: |
82
+ | Path / query / header / cookie params | ✅ | ✅ | ✅ |
83
+ | `$ref` resolution (internal) | ✅ | ✅ | ✅ |
84
+ | Base URL from `host` + `basePath` | ✅ | — | — |
85
+ | Base URL from `servers[]` | — | ✅ | ✅ |
86
+ | Fallback base URL from spec source URL | ✅ | ✅ | ✅ |
87
+ | `application/json` request bodies | ✅ | ✅ | ✅ |
88
+ | `application/x-www-form-urlencoded` | ✅ | ✅ | ✅ |
89
+ | `multipart/form-data` (incl. files) | ✅ | ✅ | ✅ |
90
+ | `text/*` and `application/xml` | ✅ | ✅ | ✅ |
91
+ | Vendor `+json` media types | ✅ | ✅ | ✅ |
92
+ | `oneOf` / `anyOf` (as Python `Union`) | — | ✅ | ✅ |
93
+ | `allOf` merge into single model | — | ✅ | ✅ |
94
+ | `nullable: true` (3.0) | — | ✅ | — |
95
+ | `type: ["string", "null"]` (3.1) | — | — | ✅ |
96
+ | `const` | — | — | ✅ |
97
+ | `readOnly` / `writeOnly` / `deprecated`| ✅ | ✅ | ✅ |
98
+ | Security: Bearer / API key / Basic | ✅ | ✅ | ✅ |
99
+ | Async execution (`httpx.AsyncClient`) | ✅ | ✅ | ✅ |
100
+
101
+ Not yet supported: `discriminator`-driven union routing, `patternProperties`,
102
+ `$dynamicRef` / `$dynamicAnchor`, callbacks, links, webhooks, and XML request-body
103
+ serialization from Python dicts.
75
104
 
76
105
  ---
77
106
 
@@ -216,3 +245,27 @@ toolkit = OpenAPIToolkit.from_url(
216
245
 
217
246
  LangChain StructuredTools
218
247
  ```
248
+
249
+ ### Example:
250
+
251
+ ```python
252
+ from langchain_openapi_tools import OpenAPIToolkit
253
+ from langchain.agents import create_agent
254
+ from langchain.chat_models import init_chat_model
255
+
256
+ toolkit = OpenAPIToolkit.from_url(
257
+ "https://api.crossref.org/swagger-docs"
258
+ )
259
+
260
+ model = init_chat_model("openrouter:qwen/qwen3-32b:free")
261
+
262
+ agent = create_agent(
263
+ model=model,
264
+ tools=toolkit.get_tools(tags=["Works"]),
265
+ )
266
+
267
+ response = agent.invoke({
268
+ "messages": "Find papers about Kashmir"
269
+ })
270
+
271
+ ```
@@ -0,0 +1,72 @@
1
+ # Architecture & Core Pipeline
2
+
3
+ `langchain-openapi` processes raw OpenAPI specs into native LangChain tools using a clean, modular pipeline.
4
+
5
+ ---
6
+
7
+ ## Execution Pipeline
8
+
9
+ ```text
10
+ OpenAPI Spec (JSON / YAML / Dict)
11
+
12
+
13
+ OpenAPILoader
14
+
15
+
16
+ Spec Adapter Layer (Swagger 2.0 → 3.0 normalization; 3.0/3.1 pass-through)
17
+
18
+
19
+ OpenAPIParser
20
+
21
+
22
+ SchemaConverter ────► Pydantic Dynamic Models
23
+ │ (oneOf/anyOf → Union, allOf → merged model,
24
+ │ const → Literal, nullable → Optional)
25
+
26
+ LangChainToolFactory
27
+
28
+
29
+ AsyncHTTPExecutor
30
+
31
+
32
+ Media-type dispatch (application/json, x-www-form-urlencoded,
33
+ multipart/form-data, text/*, application/xml,
34
+ vendor +json variants)
35
+
36
+
37
+ Middleware Pipeline (Retry, Cache, RateLimit, Logging, Pagination)
38
+
39
+
40
+ Request Providers (Authentication & Header Mutations)
41
+
42
+
43
+ httpx.AsyncClient (Target HTTP API)
44
+ ```
45
+
46
+ ---
47
+
48
+ ## Pipeline Components
49
+
50
+ ### 1. OpenAPILoader
51
+ In-gests raw JSON/YAML specifications from local disk files, remote HTTP endpoints, or in-memory dictionaries. Normalizes encoding and format. When loading from a URL, the loader retains the source URL on the resulting `OpenAPISpec` so relative `servers` entries (OpenAPI 3.x) and missing `host` values (Swagger 2.0) can be resolved to absolute request URLs downstream.
52
+
53
+ ### 2. Spec Adapter Layer
54
+ `langchain_openapi_tools.adapters` provides `SpecAdapter` implementations for every supported family: `Swagger2Adapter` normalizes Swagger 2.0 documents into OpenAPI 3.0 shape (using the source URL to fill in a missing `host`), and `OpenAPI3Adapter` accepts both 3.0.x and 3.1.x without modification. `detect_spec_version()`, `select_adapter()`, and `normalize_spec()` provide the public entry points. The resulting family tag is preserved on `OpenAPISpec.spec_family`.
55
+
56
+ ### 3. OpenAPIParser
57
+ Translates raw specification dictionaries into strongly-typed internal data models (`Operation`, `Parameter`, `RequestBody`, `Response`, `Schema`). Resolves internal `$ref` pointers using `ReferenceResolver`, and extracts polymorphic keywords (`oneOf`, `anyOf`, `allOf`), `const` values, and `readOnly`/`writeOnly`/`deprecated` markers.
58
+
59
+ ### 4. SchemaConverter & PydanticFactory
60
+ Converts JSON Schema representations into dynamic Pydantic models at runtime using `pydantic.create_model()`. Union keywords become `typing.Union`s, `allOf` merges into a single model, list-form `type` becomes a `Union` of Python types, and `nullable` / `type: [X, "null"]` become `Optional[X]`. Incoming LLM tool arguments are validated against the resulting schema before dispatch.
61
+
62
+ ### 5. LangChainToolFactory
63
+ Translates parsed `Operation` models into executable LangChain `StructuredTool` instances.
64
+
65
+ ### 6. AsyncHTTPExecutor
66
+ Orchestrates request building, middleware execution, provider transformations, and asynchronous network transport. `RequestBuilder` inspects the operation's request-body content-types and dispatches to the correct httpx encoding (JSON, form-urlencoded, multipart with files, or raw text/XML), and coerces validated Pydantic body models back to JSON-safe primitives before serialization.
67
+
68
+ ### 7. Middleware Pipeline
69
+ Intercepts outgoing requests and incoming responses to apply retries, rate limiting, caching, result aggregation, and sanitized telemetry.
70
+
71
+ ### 8. Request Providers
72
+ Applies authentication schemes (Bearer tokens, API Keys, Basic Auth) and static headers before network transport.
@@ -36,6 +36,18 @@ toolkit = OpenAPIToolkit.from_dict(
36
36
 
37
37
  ---
38
38
 
39
+ ## Base URL Resolution
40
+
41
+ The toolkit determines the base URL used for HTTP requests in this order of precedence:
42
+
43
+ 1. Explicit `base_url` argument passed to `OpenAPIToolkit.__init__` / `from_url` / `from_file` / `from_dict`.
44
+ 2. The first entry in the spec's `servers` block (OpenAPI 3.x) or the URL synthesized from `host`/`basePath`/`schemes` (Swagger 2.0).
45
+ 3. When `OpenAPIToolkit.from_url(...)` is used and the spec has no `servers` (or Swagger 2.0 has no `host`), the source URL is used as a fallback: relative server URLs are resolved against it via `urljoin`, and a missing `servers` block defaults to the document's `scheme://host` origin — matching the OpenAPI 3.x specification.
46
+
47
+ This means self-describing endpoints such as `https://fakerestapi.azurewebsites.net/swagger/v1/swagger.json` (an OpenAPI 3.0.1 document that omits the `servers` block) work out of the box without a manual `base_url`. For local files or in-memory dicts with no `servers`, pass `base_url` explicitly.
48
+
49
+ ---
50
+
39
51
  ## Tool Filtering & Selection
40
52
 
41
53
  Filter generated tools by HTTP methods, tags, or operation names:
@@ -5,6 +5,14 @@ executing HTTP requests asynchronously, configuring request providers,
5
5
  applying production middleware, and exporting dynamic LangChain tools.
6
6
  """
7
7
 
8
+ from langchain_openapi_tools.adapters import (
9
+ OpenAPI3Adapter,
10
+ SpecAdapter,
11
+ Swagger2Adapter,
12
+ detect_spec_version,
13
+ normalize_spec,
14
+ select_adapter,
15
+ )
8
16
  from langchain_openapi_tools.enums import DataType, HTTPMethod, ParameterLocation
9
17
  from langchain_openapi_tools.exceptions import (
10
18
  AuthenticationError,
@@ -107,6 +115,7 @@ __all__ = [
107
115
  "MiddlewarePipeline",
108
116
  "NextCallable",
109
117
  "NoAuthProvider",
118
+ "OpenAPI3Adapter",
110
119
  "OpenAPIError",
111
120
  "OpenAPILoader",
112
121
  "OpenAPIParser",
@@ -133,15 +142,20 @@ __all__ = [
133
142
  "RetryMiddleware",
134
143
  "Schema",
135
144
  "SchemaConverter",
145
+ "SpecAdapter",
136
146
  "SpecLoadError",
137
147
  "StaticHeadersProvider",
148
+ "Swagger2Adapter",
138
149
  "SwaggerNormalizer",
139
150
  "UnsupportedVersionError",
140
151
  "__version__",
141
152
  "build_tool_description",
142
153
  "create_async_client",
154
+ "detect_spec_version",
143
155
  "format_tool_name",
144
156
  "generate_fallback_operation_name",
145
157
  "map_schema_type_to_python",
158
+ "normalize_spec",
146
159
  "sanitize_request_log",
160
+ "select_adapter",
147
161
  ]
@@ -0,0 +1,126 @@
1
+ """Specification adapter layer.
2
+
3
+ This module implements the unified ingestion pipeline that transforms any
4
+ supported API specification (Swagger 2.0, OpenAPI 3.0, OpenAPI 3.1) into a
5
+ single normalized OpenAPI 3.x dictionary. Every downstream component
6
+ (``OpenAPIParser``, ``SchemaConverter``, ``RequestBuilder``, ...) operates
7
+ against the normalized shape and never inspects the original spec version.
8
+
9
+ Layering::
10
+
11
+ API Specification
12
+
13
+ ┌────────────────┴────────────────┐
14
+ │ │
15
+ Swagger 2.0 OpenAPI 3.x
16
+ │ │
17
+ ▼ ▼
18
+ Swagger2Adapter OpenAPI3Adapter
19
+ └──────────────┬──────────────────┘
20
+
21
+ Normalized Internal Spec
22
+
23
+ Adapters are stateless and receive an optional ``source_url`` for base-URL
24
+ resolution when the spec omits ``servers`` / ``host``.
25
+ """
26
+
27
+ from __future__ import annotations
28
+
29
+ from typing import Any, Protocol
30
+
31
+
32
+ class SpecAdapter(Protocol):
33
+ """Protocol for specification-version adapters.
34
+
35
+ An adapter accepts a raw specification dictionary and returns a
36
+ normalized OpenAPI 3.x dictionary. The returned document is the sole
37
+ input consumed by the rest of the pipeline.
38
+ """
39
+
40
+ def normalize(
41
+ self, spec_dict: dict[str, Any], source_url: str | None = None
42
+ ) -> dict[str, Any]:
43
+ """Return a normalized OpenAPI 3.x dictionary."""
44
+
45
+
46
+ class Swagger2Adapter:
47
+ """Adapter for Swagger 2.0 (a.k.a. OpenAPI 2.0) specifications.
48
+
49
+ Delegates to ``SwaggerNormalizer`` for the actual field-by-field
50
+ translation, then relies on the pipeline's OpenAPI 3.x processing for
51
+ everything downstream.
52
+ """
53
+
54
+ def normalize(
55
+ self, spec_dict: dict[str, Any], source_url: str | None = None
56
+ ) -> dict[str, Any]:
57
+ from langchain_openapi_tools.swagger import SwaggerNormalizer
58
+
59
+ return SwaggerNormalizer(spec_dict, source_url=source_url).normalize()
60
+
61
+
62
+ class OpenAPI3Adapter:
63
+ """Adapter for OpenAPI 3.0 and 3.1 specifications.
64
+
65
+ OpenAPI 3.x documents already match the internal normalized shape, so
66
+ the adapter primarily performs the following:
67
+
68
+ * Ensures the document has an ``openapi`` field.
69
+ * Backfills ``jsonSchemaDialect`` for 3.1 documents that omit it.
70
+ * Leaves ``servers`` / ``components`` untouched; base-URL resolution is
71
+ handled by :class:`langchain_openapi_tools.parser.OpenAPISpec`.
72
+ """
73
+
74
+ def normalize(
75
+ self, spec_dict: dict[str, Any], source_url: str | None = None
76
+ ) -> dict[str, Any]:
77
+ return spec_dict
78
+
79
+
80
+ def detect_spec_version(spec_dict: dict[str, Any]) -> str:
81
+ """Detect the spec version family: ``"swagger2"``, ``"openapi30"``, ``"openapi31"``.
82
+
83
+ Raises ``UnsupportedVersionError`` for anything else.
84
+ """
85
+ from langchain_openapi_tools.exceptions import UnsupportedVersionError
86
+
87
+ swagger = str(spec_dict.get("swagger", ""))
88
+ if swagger == "2.0" or swagger.startswith("2."):
89
+ return "swagger2"
90
+
91
+ openapi = str(spec_dict.get("openapi", ""))
92
+ if openapi.startswith("3.0"):
93
+ return "openapi30"
94
+ if openapi.startswith("3.1"):
95
+ return "openapi31"
96
+
97
+ raise UnsupportedVersionError(
98
+ f"Unsupported specification version: swagger={swagger!r}, openapi={openapi!r}."
99
+ )
100
+
101
+
102
+ def select_adapter(spec_dict: dict[str, Any]) -> SpecAdapter:
103
+ """Return the correct :class:`SpecAdapter` for the given raw spec dict."""
104
+ family = detect_spec_version(spec_dict)
105
+ if family == "swagger2":
106
+ return Swagger2Adapter()
107
+ return OpenAPI3Adapter()
108
+
109
+
110
+ def normalize_spec(
111
+ spec_dict: dict[str, Any], source_url: str | None = None
112
+ ) -> tuple[dict[str, Any], str]:
113
+ """Normalize any supported spec into an OpenAPI 3.x dictionary.
114
+
115
+ Args:
116
+ spec_dict: Raw specification dictionary (Swagger 2.0 or OpenAPI 3.x).
117
+ source_url: Optional URL the specification was fetched from.
118
+
119
+ Returns:
120
+ Tuple of ``(normalized_dict, family)`` where ``family`` is one of
121
+ ``"swagger2"``, ``"openapi30"``, ``"openapi31"``.
122
+ """
123
+ family = detect_spec_version(spec_dict)
124
+ adapter = select_adapter(spec_dict)
125
+ normalized = adapter.normalize(spec_dict, source_url=source_url)
126
+ return normalized, family