feat: Add raw OpenSearch DSL search endpoint/tool/SDK, configurable search fuzziness - #2206
feat: Add raw OpenSearch DSL search endpoint/tool/SDK, configurable search fuzziness#2206edwinjosechittilappilly wants to merge 10 commits into
Conversation
…earch fuzziness Expose the Langflow OpenSearch (Multi-Model Multi-Embedding) component's raw_search output as a first-class OpenRAG capability, matching the existing openrag_search endpoint/tool/SDK surface: - POST /v1/search/raw runs a caller-supplied OpenSearch Query DSL (or plain text) through the caller's ACL-scoped OpenSearch client, stripping embedding vectors and fencing chunk text before returning results. - Exposed as the openrag_raw_search MCP tool. - Added client.search.raw_query()/rawQuery() to the Python and TypeScript SDKs, with integration test coverage. - Extracted the duplicated filter-clause-building logic in SearchService into a shared helper. Also exposes the hybrid search's keyword-match fuzziness (previously hardcoded to "AUTO:4,7") as an optional `fuzziness` parameter on /v1/search, search_tool, and both SDKs, defaulting to "AUTO:4,7" to preserve existing behavior.
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughThe change adds configurable keyword fuzziness and authenticated raw OpenSearch queries. The API, search service, MCP interface, Python SDK, and TypeScript SDK support raw DSL or text queries with filters, limits, thresholds, typed responses, and response sanitization. ChangesSearch API and SDK enhancements
Estimated code review effort: 4 (Complex) | ~45 minutes Possibly related PRs
Suggested reviewers: Sequence Diagram(s)sequenceDiagram
participant SearchClient
participant SearchAPI
participant SearchService
participant OpenSearch
SearchClient->>SearchAPI: POST /v1/search/raw
SearchAPI->>SearchService: raw_search(query, filters, limit, score_threshold)
SearchService->>OpenSearch: execute authenticated raw query
OpenSearch-->>SearchService: raw response
SearchService-->>SearchAPI: sanitized response
SearchAPI-->>SearchClient: typed raw search response
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 9
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@sdks/typescript/tests/integration.test.ts`:
- Around line 449-530: Update the “Raw Search” describe block to own its fixture
by adding a beforeAll that creates and ingests a document containing the
expected “orange kangaroos jumping” content before the tests run, and clean up
that document afterward using the block’s existing client APIs. In the filter
test’s finally block, retain document deletion and also remove tmpDir
recursively with force enabled via fs.rmSync.
- Around line 532-540: Update the malformed DSL assertion in the “should raise
an SDK error for a malformed DSL clause” test to expect ValidationError,
matching the SDK’s handling of 400 responses. Keep the existing whitespace-only
query assertion unchanged, and only assert the error message if the test must
distinguish malformed DSL validation.
In `@src/api/v1/search.py`:
- Around line 135-141: Harden the `/v1/search/raw` validation around
`body.query` before it reaches `raw_search()`: enforce an OpenSearch clause
allowlist or denylist, reject oversized or deeply nested query structures, and
apply limits for bucket, top, and other resource-intensive clauses. Preserve the
existing required-query and object/string type errors, and return a clear 400
response for disallowed or over-limit queries.
In `@src/app/routes/public_v1.py`:
- Around line 45-47: Update the add_api_route registration for
v1_search.raw_search_endpoint to declare an API-local success response model
describing the OpenSearch raw-search result shape, and pass that model via the
route’s response_model parameter so OpenAPI documents the endpoint contract.
In `@src/auth_context.py`:
- Line 7: Update the imports in auth_context.py to remove the unused and
deprecated Dict and Optional names, retaining only Any from typing to match the
file’s existing dict[str, Any] and str | None annotations.
In `@src/services/search_service.py`:
- Line 466: Validate SearchV1Body.fuzziness at the API boundary with a Pydantic
field validator, accepting only "0", "1", "2", "AUTO", or the documented
AUTO:<low>,<high> format and preserving None. Reject invalid values through
normal model validation so the endpoint returns status 400, while keeping the
existing fuzziness fallback and multi_match behavior unchanged.
- Around line 758-760: The raw_search authentication failure must use
exception-based signaling and consistent response validation across all affected
sites. In src/services/search_service.py lines 758-760, raise a dedicated
authentication exception instead of returning an error dict, and update the
synthetic empty-hits return at line 767 to preserve the OpenSearch response
shape. In src/api/v1/search.py line 179, catch that exception, return HTTP 401,
and validate results before constructing JSONResponse. In
sdks/typescript/src/search.ts lines 65-88, validate the parsed body has hits
before asserting RawSearchResponse and throw an SDK error when it is absent.
- Around line 762-793: Update the JSON parsing branch in raw_search so parsed
values are used as query_body only when json.loads returns a dict; for valid
JSON scalars or arrays, fall back to the existing keyword multi_match
construction using the stripped input. Preserve the current empty-string
handling and object-query behavior, ensuring later filter, size, and min_score
logic always receives a dictionary.
In `@tests/integration/sdk/test_search.py`:
- Around line 194-208: Update test_raw_query_with_filters_scopes_to_file to
assert that hits is non-empty immediately after extracting results.hits and
before iterating. Keep the existing per-hit filename assertion and cleanup
behavior unchanged; apply the equivalent non-empty assertion in the TypeScript
counterpart if it is part of this change.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: a7aa7ad6-91ad-49e5-a8b4-0693ed2d701e
📒 Files selected for processing (12)
sdks/python/openrag_sdk/models.pysdks/python/openrag_sdk/search.pysdks/typescript/src/search.tssdks/typescript/src/types.tssdks/typescript/tests/integration.test.tssrc/api/v1/search.pysrc/app/routes/public_v1.pysrc/auth_context.pysrc/mcp_http/server.pysrc/services/search_service.pysrc/utils/opensearch_utils.pytests/integration/sdk/test_search.py
…for agents Changes the default keyword-match fuzziness from "AUTO:4,7" to "AUTO:7,10" (fewer noisy fuzzy matches on shorter terms), and expands docs/comments everywhere fuzziness is threaded through (auth_context, SearchService, /v1/search, both SDKs) to spell out every accepted value and when to use it. The openrag_search MCP tool description gets the most detail, since that's what an LLM agent actually reads when deciding what to pass.
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/services/search_service.py (2)
728-736: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winReset omitted
fuzzinessbefore searching with a reused context.
_current_fuzzinesspersists per async context, sosearch()calls can leak one request’s custom fuzziness into a later omitted-fuzzinesscall. Always reset the context value or set it fromconfig.settingsbeforesearch_tool.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/services/search_service.py` around lines 728 - 736, Update the search flow before search_tool in search() to always reset the context fuzziness when the request omits fuzziness, using the configured config.settings value; preserve the explicit set_fuzziness behavior for provided values so reused async contexts cannot retain a prior request’s custom setting.
804-808: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winAdd an OpenSearch query timeout to raw DSL execution.
get_user_opensearch_clientsets a 30s client transport timeout, but this rawsearch(...)call still does not limit server-side execution. Add a server-side timeout to the DSL or search params for expensive user DSL.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/services/search_service.py` around lines 804 - 808, Update the raw DSL execution in the search flow around the OpenSearch client search call to include a 30-second server-side query timeout, using the DSL or search parameters supported by the existing client. Preserve the current index, query body, and terminate_after behavior while ensuring expensive user-supplied queries are bounded independently of the transport timeout.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@src/services/search_service.py`:
- Around line 728-736: Update the search flow before search_tool in search() to
always reset the context fuzziness when the request omits fuzziness, using the
configured config.settings value; preserve the explicit set_fuzziness behavior
for provided values so reused async contexts cannot retain a prior request’s
custom setting.
- Around line 804-808: Update the raw DSL execution in the search flow around
the OpenSearch client search call to include a 30-second server-side query
timeout, using the DSL or search parameters supported by the existing client.
Preserve the current index, query body, and terminate_after behavior while
ensuring expensive user-supplied queries are bounded independently of the
transport timeout.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 30b6fc71-ecb5-4486-87f5-b88e016d1577
📒 Files selected for processing (9)
sdks/python/openrag_sdk/search.pysdks/typescript/src/search.tssdks/typescript/src/types.tssdks/typescript/tests/integration.test.tssrc/api/v1/search.pysrc/auth_context.pysrc/mcp_http/server.pysrc/services/search_service.pytests/integration/sdk/test_search.py
🚧 Files skipped from review as they are similar to previous changes (8)
- sdks/typescript/src/search.ts
- sdks/python/openrag_sdk/search.py
- sdks/typescript/src/types.ts
- src/mcp_http/server.py
- src/auth_context.py
- src/api/v1/search.py
- sdks/typescript/tests/integration.test.ts
- tests/integration/sdk/test_search.py
CodeQL (information exposure through an exception):
- src/api/v1/search.py: search_endpoint and raw_search_endpoint no longer
echo raw exception text to the client; details are logged server-side
and a generic message is returned instead.
CodeRabbit correctness/security findings:
- search_service.raw_search: an unauthenticated call now raises
SearchAuthenticationError instead of returning {"error": ...} with an
implicit 200 (both SDKs previously had no way to detect this); the
endpoint maps it to HTTP 401.
- search_service.raw_search: a JSON string query that parses to a scalar
or array (not an object) no longer crashes attempting dict operations;
it now falls back to the keyword-match path, same as non-JSON text.
- search_service.search: fuzziness is now always reset in the per-request
context (via a shared DEFAULT_FUZZINESS constant) instead of only when
provided, so a prior request's custom fuzziness can no longer leak into
a later request that omits it on the same async context.
- SearchV1Body.fuzziness is now validated against the documented format
("0"/"1"/"2"/"AUTO"/"AUTO:<low>,<high>") at the API boundary, returning
400 instead of a 500 from an unhandled OpenSearch parse error.
- raw_search now rejects scripted query clauses and oversized/deeply
nested query bodies (RawSearchQueryError -> 400), and passes a
server-side query timeout to OpenSearch. This is a scoped mitigation
(script rejection + size/depth caps), not an exhaustive DSL allowlist.
- /v1/search/raw now declares a response_model so its OpenAPI schema
documents the raw OpenSearch response shape.
- TypeScript SDK's rawQuery() validates the parsed response has `hits`
before returning it, instead of silently exposing malformed responses
to callers as a crash on first access.
- sdks/python/openrag_sdk/search.py: removed a pre-existing unused httpx
import that was failing CI's ruff check on this PR (file is touched by
this change, so the lint job runs against its full contents).
Test-hygiene fixes:
- TypeScript "Raw Search" tests now ingest and clean up their own fixture
document instead of depending on document ingestion order from other
describe blocks; the filter test's temp directory is now removed.
- Both SDKs' filter-scoped raw-search tests assert hits is non-empty
before iterating, so a filter that wrongly excludes everything can no
longer pass silently.
- TypeScript's malformed-DSL test now expects ValidationError (matching
the SDK's actual 400 -> ValidationError mapping) instead of the
looser OpenRAGError base class.
Verified locally: ruff check --no-fix, ruff format --check, and mypy all
pass on every changed Python file using CI's exact invocation; the
TypeScript SDK builds and typechecks cleanly (confirmed against
typescript@6.0.3, matching what CI resolves - the repo's committed
node_modules snapshot has a stale 5.9.3 that doesn't support this
tsconfig's ignoreDeprecations setting, a pre-existing, unrelated
environment issue).
# Conflicts: # sdks/typescript/tests/integration.test.ts # tests/integration/sdk/test_search.py
The previous CodeQL fix (str(e) -> generic message) only covered the
generic `except Exception` branch. It introduced two new instances of the
same taint pattern for the purpose-built SearchAuthenticationError and
RawSearchQueryError branches, each flagged as a fresh alert on this PR's
merge commit:
- SearchAuthenticationError's message is always the same literal
("Authentication required"), so it's now hardcoded directly instead of
routed through str(e), eliminating the exception-derived taint flow
entirely.
- RawSearchQueryError's message is dynamic but 100% developer-authored
(validation reasons from _validate_raw_query_safety, e.g. "'size' must
not exceed 1000") - never OpenSearch/driver internals. This is the
genuinely-safe, user-facing validation message CodeRabbit asked for in
the prior review round, so it's suppressed with the same lgtm[] +
justification-comment convention already used elsewhere in this repo
(src/utils/provider_health_cache.py) rather than discarded.
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/services/search_service.py (1)
243-245: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winCorrect the documented default fuzziness.
Both docstrings specify
"AUTO:7,10". The PR contract preserves"AUTO:4,7". Update these values to matchDEFAULT_FUZZINESS.Proposed fix
- clause. See the openrag_search MCP tool description - for accepted values. Defaults to "AUTO:7,10". + clause. See the openrag_search MCP tool description + for accepted values. Defaults to "AUTO:4,7". ... - values. Defaults to "AUTO:7,10" if not provided. + values. Defaults to "AUTO:4,7" if not provided.Also applies to: 750-752
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/services/search_service.py` around lines 243 - 245, Update the fuzziness defaults documented in both docstring locations for the relevant search service method to `"AUTO:4,7"`, matching the `DEFAULT_FUZZINESS` contract; change documentation only and leave runtime behavior unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/api/v1/search.py`:
- Around line 217-225: Update the raw-search exception handler to classify
failures before selecting a response: preserve 401/403 responses for
authentication or access-denied errors, return 400 only for recognized request
or query-validation errors, and map unexpected OpenSearch service or network
failures to 500/502. Use the existing raw-search error handling symbols and keep
logging the original exception details.
---
Outside diff comments:
In `@src/services/search_service.py`:
- Around line 243-245: Update the fuzziness defaults documented in both
docstring locations for the relevant search service method to `"AUTO:4,7"`,
matching the `DEFAULT_FUZZINESS` contract; change documentation only and leave
runtime behavior unchanged.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 3b1f3c93-0324-4eb9-91d2-b452bff6a70a
📒 Files selected for processing (8)
sdks/python/openrag_sdk/search.pysdks/typescript/src/search.tssdks/typescript/tests/integration.test.tssrc/api/v1/search.pysrc/app/routes/public_v1.pysrc/auth_context.pysrc/services/search_service.pytests/integration/sdk/test_search.py
💤 Files with no reviewable changes (1)
- sdks/python/openrag_sdk/search.py
🚧 Files skipped from review as they are similar to previous changes (4)
- tests/integration/sdk/test_search.py
- src/auth_context.py
- src/app/routes/public_v1.py
- sdks/typescript/src/search.ts
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
sdks/typescript/tests/integration.test.ts (1)
644-660: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winRemove the temporary directory during cleanup.
tmpDirremains on the test runner after this test. Delete it even ifclient.documents.delete()fails.Proposed cleanup
} finally { - await client.documents.delete(filterDocName); + try { + await client.documents.delete(filterDocName); + } finally { + fs.rmSync(tmpDir, { recursive: true, force: true }); + } }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@sdks/typescript/tests/integration.test.ts` around lines 644 - 660, Update the test cleanup in “should filter by a raw filters dict (data_sources)” so the temporary directory identified by tmpDir is removed in the finally block, even when client.documents.delete(filterDocName) fails. Ensure directory removal still occurs if the document deletion throws.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@sdks/typescript/tests/integration.test.ts`:
- Around line 644-660: Update the test cleanup in “should filter by a raw
filters dict (data_sources)” so the temporary directory identified by tmpDir is
removed in the finally block, even when client.documents.delete(filterDocName)
fails. Ensure directory removal still occurs if the document deletion throws.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: c7e2c95d-431f-42cf-a85f-39ccc47ff312
📒 Files selected for processing (4)
sdks/typescript/src/types.tssdks/typescript/tests/integration.test.tssrc/api/v1/search.pytests/integration/sdk/test_search.py
🚧 Files skipped from review as they are similar to previous changes (2)
- sdks/typescript/src/types.ts
- tests/integration/sdk/test_search.py
…ss remaining CodeRabbit findings The lgtm[py/stack-trace-exposure] suppression comment on RawSearchQueryError's response didn't work - GitHub's code scanning flagged a fresh alert (#195) on that exact line on the next push. Checked the repo's only other lgtm[] usage (src/utils/provider_health_cache.py): its alert was dismissed manually by a maintainer via the Security tab, not by the comment. The inline suppression syntax isn't honored by this repo's code-scanning setup, so it can't be relied on - dismissing alerts is a maintainer call, not something to route around. Replaced it with a real fix: RawSearchQueryError now has three subclasses (RawSearchScriptedQueryError, RawSearchQuerySizeError, RawSearchQueryDepthError), one per _validate_raw_query_safety violation. raw_search_endpoint dispatches on exception *type* through a fixed dict literal to pick the client-facing message - it never reads the exception's message text, so there is no exception-derived value flowing into the HTTP response for CodeQL to flag, while callers still get a specific, useful error per violation kind instead of one generic message. Also addresses two more CodeRabbit findings from this round: - raw_search_endpoint's final `except Exception` branch returned 400 for every unclassified failure, including genuine OpenSearch transport/service errors that aren't the caller's fault. Now returns 400 only for opensearchpy.RequestError (OpenSearch itself rejected the query DSL) and 500 for anything else. - TypeScript integration tests: both raw-search and hybrid-search "filter by data_sources" tests now remove their temp directory even if client.documents.delete() throws in the finally block, instead of leaking it on the test runner. (The "fuzziness docstrings still say AUTO:7,10, should be AUTO:4,7" comment from this same review round is stale - checked, all three docstrings already correctly match DEFAULT_FUZZINESS = "AUTO:7,10"; not a real issue.)
|
React Doctor skipped this pull request — it changed no React files. Reviewed by React Doctor for commit |
There was a problem hiding this comment.
♻️ Duplicate comments (1)
src/api/v1/search.py (1)
237-250: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winUse typed OpenSearch authorization exceptions.
Line 240 derives the HTTP status from
str(e). If aRequestErrorincludes rejected query text containing"access denied", this branch returns403before Line 242 returns400. An authorization exception that does not contain either checked phrase returns500instead of403.Catch the opensearch-py authentication and authorization exception types before
RequestError. Keep the catch-all branch for500responses.Proposed fix
-from opensearchpy.exceptions import RequestError +from opensearchpy.exceptions import ( + AuthenticationException, + AuthorizationException, + RequestError, +) ... - except Exception as e: - error_msg = str(e) - logger.error("Raw search failed", error=error_msg, user_id=user.user_id) - if "AuthenticationException" in error_msg or "access denied" in error_msg.lower(): - return JSONResponse({"error": "Access denied"}, status_code=403) - if isinstance(e, RequestError): + except (AuthenticationException, AuthorizationException) as e: + logger.error("Raw search access denied", error=str(e), user_id=user.user_id) + return JSONResponse({"error": "Access denied"}, status_code=403) + except RequestError as e: + logger.error("Raw search query rejected", error=str(e), user_id=user.user_id) return JSONResponse( { "error": "Raw search query was rejected by OpenSearch. Check server logs for details." }, status_code=400, ) + except Exception as e: + logger.error("Raw search failed", error=str(e), user_id=user.user_id) + return JSONResponse( + {"error": "Raw search failed. Check server logs for details."}, status_code=500 + )For opensearch-py 3.0.0, confirm which exported exceptions represent OpenSearch HTTP 401 and 403 responses. Confirm whether `AuthenticationException` and `AuthorizationException` are the correct types to catch before `RequestError`.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/api/v1/search.py` around lines 237 - 250, Update the raw search exception handling around the catch-all in the search handler to import and catch opensearch-py’s AuthenticationException and AuthorizationException before RequestError, returning 403 for either typed authorization failure. Remove the message-based authorization checks so RequestError always reaches the existing 400 response, while retaining the catch-all path for 500 responses.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Duplicate comments:
In `@src/api/v1/search.py`:
- Around line 237-250: Update the raw search exception handling around the
catch-all in the search handler to import and catch opensearch-py’s
AuthenticationException and AuthorizationException before RequestError,
returning 403 for either typed authorization failure. Remove the message-based
authorization checks so RequestError always reaches the existing 400 response,
while retaining the catch-all path for 500 responses.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 6f404f91-ea8e-4ab6-9e61-572f5972df62
📒 Files selected for processing (3)
sdks/typescript/tests/integration.test.tssrc/api/v1/search.pysrc/services/search_service.py
🚧 Files skipped from review as they are similar to previous changes (2)
- sdks/typescript/tests/integration.test.ts
- src/services/search_service.py
… str(e) Both search_endpoint and raw_search_endpoint decided between 403 and a generic error status by checking whether "AuthenticationException" or "access denied" appeared as a substring of str(e). This is fragile in both directions: a RequestError whose rejected-query text happens to contain "access denied" would be misclassified as 403 before reaching the RequestError check, and a genuine typed authorization exception whose message doesn't contain either literal substring would fall through to the generic 500/400 branch instead of 403. Both endpoints now catch opensearchpy's AuthenticationException and AuthorizationException explicitly, ahead of RequestError and the generic Exception catch-all, so classification is driven by the actual exception type OpenSearch raised rather than pattern-matching its message text.
Summary
Exposes the Langflow "OpenSearch (Multi-Model Multi-Embedding)" component's
raw_searchoutput as a first-class OpenRAG capability, with the same endpoint/tool/SDK surfaceopenrag_searchalready has:POST /v1/search/raw(src/api/v1/search.py) — runs a caller-supplied OpenSearch Query DSL (dict) or plain-text query through the caller's ACL-scoped OpenSearch client (session_manager.get_user_opensearch_client), so document-level ACLs still apply, unlike the Langflow component's admin-credentialed client. Supportsfilters/filter_id/limit/score_threshold, same as/v1/search.SearchService.raw_search()(src/services/search_service.py) — the service-layer implementation; also extracts the filter-clause-building logic (previously duplicated twice insearch_tool) into a shared_build_filter_clauses()helper.sanitize_raw_search_response()(src/utils/opensearch_utils.py) — strips embedding-vector fields and fences chunk text with untrusted-content markers before returning raw hits, mirroring the Langflow component's own sanitization.openrag_raw_search(src/mcp_http/server.py) — auto-exposed from the new route via the existingCOMPONENT_CUSTOMIZATIONSmechanism.client.search.raw_query(...)(Python) andclient.search.rawQuery(...)(TypeScript), each with aRawSearchResponsemodel/type and integration test coverage.Raw DSL execution was deliberately not added to the chat agent's autonomous tool registry (
agentd'ssearch_tool) — letting the LLM run arbitrary OpenSearch DSL mid-conversation is a materially larger attack surface than the semantic search it already has. This stays an explicit, API-key-gated capability only.Also included: the hybrid search's keyword-match
fuzziness(previously hardcoded to"AUTO:4,7"inSearchService.search_tool) is now an optional parameter threaded throughsearch_tool/search(),/v1/search, and both SDKs — defaulting to"AUTO:4,7"to preserve existing behavior, but overridable (e.g."AUTO:7,10","0"for exact-only matching).Test plan
python -m py_compile/ruff checkon all touched Python files (one pre-existing unrelatedF401insdks/python/openrag_sdk/search.pyconfirmed present onmainbefore this change)pytest tests/unit/test_search_service_exact_filter.pypasses (validates the extracted_build_filter_clauseshelper is behavior-preserving)tsup(JS/CJS output);.d.tsgeneration fails on a pre-existingtsconfig.json/installed-typescript-version mismatch unrelated to this change (confirmed viagit stash)vitest run tests/integration.test.tscollects all tests (37, up from 35) with no syntax errorstests/integration/sdk/test_search.py::TestRawSearch,sdks/typescript/tests/integration.test.ts"Raw Search"/fuzziness cases) require a live OpenRAG instance — not run in this environmentSummary by CodeRabbit