fix(dify): guard retrieval argument error behavior (#14169)

## What problem does this PR solve?

The Dify-compatible `/dify/retrieval` endpoint recently gained stricter
parsing and validation for its request payload, including:
- Normalized `retrieval_setting.top_k` and
`retrieval_setting.score_threshold` types.
- Clear separation between malformed arguments vs missing required
fields.
Previously, there was no unit test explicitly guarding the exact error
code and message contract for these cases.

## What does this PR change?

- **Add guard-style unit test** in `test_dify_retrieval_routes_unit.py`:
  - `test_retrieval_argument_error_messages`:
    - Sends a request with malformed numeric options:
- `retrieval_setting = {"top_k": "not-int", "score_threshold":
"not-float"}`
      - Asserts `code == RetCode.ARGUMENT_ERROR` and message contains  
        `"invalid or malformed arguments:"`.
    - Sends a request with required fields missing:
      - Empty payload (`{}`)
      - Asserts `code == RetCode.ARGUMENT_ERROR` and message contains  
        `"required arguments are missing:"`.

This test encodes the intended behavior of the Dify retrieval API so
future refactors cannot silently regress error handling.

## Type of change

- [x] Tests (add coverage and guardrails for existing behavior)

Co-authored-by: Kevin Hu <kevinhu.sh@gmail.com>
This commit is contained in:
Achieve3318
2026-05-11 13:17:42 +08:00
committed by GitHub
parent 0734fd793a
commit 16354f4e14
2 changed files with 210 additions and 11 deletions

View File

@@ -352,3 +352,82 @@ def test_retrieval_generic_exception_mapping(monkeypatch):
res = _run(inspect.unwrap(module.retrieval)("tenant-1"))
assert res["code"] == module.RetCode.SERVER_ERROR, res
assert "boom" in res["message"], res
@pytest.mark.p2
def test_read_retrieval_request_from_get_args(monkeypatch):
module = _load_dify_retrieval_module(monkeypatch)
monkeypatch.setattr(
module,
"request",
SimpleNamespace(
method="GET",
args={
"knowledge_id": "kb-1",
"query": "hello",
"use_kg": "true",
"top_k": "12",
"score_threshold": "0.66",
},
),
)
req = _run(module._read_retrieval_request())
assert req["knowledge_id"] == "kb-1", req
assert req["query"] == "hello", req
assert req["use_kg"] is True, req
assert req["retrieval_setting"]["top_k"] == 12, req
assert req["retrieval_setting"]["score_threshold"] == 0.66, req
@pytest.mark.p2
def test_read_retrieval_request_from_post_json(monkeypatch):
module = _load_dify_retrieval_module(monkeypatch)
payload = {"knowledge_id": "kb-1", "query": "hello"}
monkeypatch.setattr(module, "request", SimpleNamespace(method="POST", args={}))
monkeypatch.setattr(module, "get_request_json", lambda: _AwaitableValue(payload))
req = _run(module._read_retrieval_request())
assert req == payload, req
@pytest.mark.p2
def test_retrieval_argument_error_messages(monkeypatch):
"""Guard: distinguish malformed vs missing argument errors."""
module = _load_dify_retrieval_module(monkeypatch)
# Case 1: malformed numeric options in retrieval_setting
_set_request_json(
monkeypatch,
module,
{
"knowledge_id": "kb-1",
"query": "hello",
"retrieval_setting": {"top_k": "not-int", "score_threshold": "not-float"},
},
)
res = _run(inspect.unwrap(module.retrieval)("tenant-1"))
assert res["code"] == module.RetCode.ARGUMENT_ERROR, res
assert "invalid or malformed arguments:" in res["message"], res
# Case 2: missing required fields (knowledge_id, query)
_set_request_json(monkeypatch, module, {})
res_missing = _run(inspect.unwrap(module.retrieval)("tenant-1"))
assert res_missing["code"] == module.RetCode.ARGUMENT_ERROR, res_missing
assert "required arguments are missing:" in res_missing["message"], res_missing
# Case 3: partially missing required field (query)
_set_request_json(monkeypatch, module, {"knowledge_id": "kb-1"})
res_missing_query = _run(inspect.unwrap(module.retrieval)("tenant-1"))
assert res_missing_query["code"] == module.RetCode.ARGUMENT_ERROR, res_missing_query
assert "query" in res_missing_query["message"], res_missing_query
# Case 4: retrieval_setting wrong type
_set_request_json(
monkeypatch,
module,
{"knowledge_id": "kb-1", "query": "hello", "retrieval_setting": "bad-type"},
)
res_wrong_type = _run(inspect.unwrap(module.retrieval)("tenant-1"))
assert res_wrong_type["code"] == module.RetCode.ARGUMENT_ERROR, res_wrong_type
assert "retrieval_setting must be an object" in res_wrong_type["message"], res_wrong_type