mirror of
https://github.com/langgenius/dify.git
synced 2026-09-08 02:43:49 +08:00
fix(knowledge-fs): clarify golden question evidence editing
This commit is contained in:
parent
40cfea870b
commit
39c31ef46c
@ -1,7 +1,7 @@
|
||||
{
|
||||
"schemaVersion": 5,
|
||||
"subtreeTree": "8546a4070c6c6815883bee1fc681ec2af3c36b28",
|
||||
"openapiSha256": "37c8bdd6a6e7696aae3b336b0de215577ecda45535b1c2eb02d7a337b7399a95",
|
||||
"subtreeTree": "ec8579ba2dcd26f230c3a046c13c7c71b22889ae",
|
||||
"openapiSha256": "5a1057572e2ba2442808296e3ef10cf39236de5b9f8fd58508e111ca9778f787",
|
||||
"capabilityV2AuthManifestSha256": "fc0a47e23cce12544882f0298522b4933002e892b84ce1815df7e81d36a7a0c7",
|
||||
"capabilityV2AuthTestVectorSha256": "ae0de37b1ff05c40f905cf17a7b410d8971acacf64db07d5ee3d6fecfa559ce3",
|
||||
"productOperationManifestSha256": "f936926be46ec84e828f993fa5536a92add52b46348f03c0c9f6e1414812f846",
|
||||
|
||||
@ -1977,13 +1977,22 @@ class KnowledgeFSDataFacade:
|
||||
control_space_id=control_space_id,
|
||||
operation_id="matchGoldenQuestionEvidence",
|
||||
payload=KnowledgeFSGoldenQuestionEvidenceMatchRemotePayload(
|
||||
evidence_texts=[payload.evidence],
|
||||
evidence_texts=[payload.evidence] if payload.evidence else None,
|
||||
minimum_similarity=payload.minimum_similarity,
|
||||
node_ids=payload.node_ids or None,
|
||||
top_k=payload.top_k,
|
||||
),
|
||||
)
|
||||
if not isinstance(raw, dict):
|
||||
raise KnowledgeFSProductRemoteError("KnowledgeFS returned an invalid evidence match response")
|
||||
if payload.node_ids:
|
||||
return KnowledgeFSGoldenQuestionEvidenceMatchResponse.model_validate(
|
||||
{
|
||||
"candidates": raw.get("resolvedEvidence", []),
|
||||
"evidence": "",
|
||||
"matched": False,
|
||||
}
|
||||
)
|
||||
items = raw.get("items")
|
||||
if not isinstance(items, list) or len(items) != 1 or not isinstance(items[0], dict):
|
||||
raise KnowledgeFSProductRemoteError("KnowledgeFS returned an invalid evidence match response")
|
||||
@ -1991,7 +2000,7 @@ class KnowledgeFSDataFacade:
|
||||
return KnowledgeFSGoldenQuestionEvidenceMatchResponse.model_validate(
|
||||
{
|
||||
"candidates": item.get("candidates", []),
|
||||
"evidence": item.get("evidenceText", payload.evidence),
|
||||
"evidence": item.get("evidenceText", payload.evidence or ""),
|
||||
"matched": item.get("matched", False),
|
||||
}
|
||||
)
|
||||
|
||||
@ -3182,8 +3182,9 @@ class KnowledgeFSGoldenQuestionListResponse(ResponseModel):
|
||||
|
||||
|
||||
class KnowledgeFSGoldenQuestionEvidenceMatchPayload(BaseModel):
|
||||
evidence: str = Field(min_length=1, max_length=8_000)
|
||||
evidence: str | None = Field(default=None, min_length=1, max_length=8_000)
|
||||
minimum_similarity: float = Field(default=0.7, ge=0, le=1)
|
||||
node_ids: list[str] = Field(default_factory=list, max_length=50)
|
||||
top_k: int = Field(default=5, ge=1, le=10)
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
@ -3193,10 +3194,22 @@ class KnowledgeFSGoldenQuestionEvidenceMatchPayload(BaseModel):
|
||||
def strip_evidence(cls, value: object) -> object:
|
||||
return value.strip() if isinstance(value, str) else value
|
||||
|
||||
@field_validator("node_ids")
|
||||
@classmethod
|
||||
def normalize_node_ids(cls, value: list[str]) -> list[str]:
|
||||
return list(dict.fromkeys(item.strip() for item in value if item.strip()))
|
||||
|
||||
@model_validator(mode="after")
|
||||
def require_one_lookup(self) -> KnowledgeFSGoldenQuestionEvidenceMatchPayload:
|
||||
if bool(self.evidence) == bool(self.node_ids):
|
||||
raise ValueError("Provide exactly one of evidence or node_ids")
|
||||
return self
|
||||
|
||||
|
||||
class KnowledgeFSGoldenQuestionEvidenceMatchRemotePayload(BaseModel):
|
||||
evidence_texts: list[str] = Field(serialization_alias="evidenceTexts")
|
||||
evidence_texts: list[str] | None = Field(default=None, serialization_alias="evidenceTexts")
|
||||
minimum_similarity: float = Field(serialization_alias="minimumSimilarity")
|
||||
node_ids: list[str] | None = Field(default=None, serialization_alias="nodeIds")
|
||||
top_k: int = Field(serialization_alias="topK")
|
||||
|
||||
model_config = ConfigDict(extra="forbid", serialize_by_alias=True)
|
||||
@ -3206,8 +3219,8 @@ class KnowledgeFSGoldenQuestionEvidenceCandidateResponse(ResponseModel):
|
||||
document_asset_id: str = Field(validation_alias=AliasChoices("document_asset_id", "documentAssetId"))
|
||||
node_id: str = Field(validation_alias=AliasChoices("node_id", "nodeId"))
|
||||
page_number: int | None = Field(default=None, validation_alias=AliasChoices("page_number", "pageNumber"))
|
||||
projection_id: str = Field(validation_alias=AliasChoices("projection_id", "projectionId"))
|
||||
score: float = Field(ge=0, le=1)
|
||||
projection_id: str | None = Field(default=None, validation_alias=AliasChoices("projection_id", "projectionId"))
|
||||
score: float | None = Field(default=None, ge=0, le=1)
|
||||
section_path: list[str] = Field(validation_alias=AliasChoices("section_path", "sectionPath"))
|
||||
text: str
|
||||
|
||||
|
||||
@ -1665,7 +1665,9 @@ def test_golden_question_facade_matches_evidence_and_forwards_csv_as_one_batch()
|
||||
"matchGoldenQuestionEvidence",
|
||||
"bulkImportGoldenQuestions",
|
||||
]
|
||||
assert interactive.call_args_list[0].kwargs["payload"].model_dump(mode="json", by_alias=True) == {
|
||||
assert interactive.call_args_list[0].kwargs["payload"].model_dump(
|
||||
mode="json", by_alias=True, exclude_none=True
|
||||
) == {
|
||||
"evidenceTexts": ["Refund policy"],
|
||||
"minimumSimilarity": 0.7,
|
||||
"topK": 5,
|
||||
@ -1687,6 +1689,43 @@ def test_golden_question_facade_matches_evidence_and_forwards_csv_as_one_batch()
|
||||
}
|
||||
|
||||
|
||||
def test_golden_question_facade_resolves_saved_evidence_ids_without_a_search_term() -> None:
|
||||
facade = KnowledgeFSDataFacade(broker=MagicMock(), remote=MagicMock())
|
||||
interactive = MagicMock(
|
||||
return_value={
|
||||
"items": [],
|
||||
"resolvedEvidence": [
|
||||
{
|
||||
"documentAssetId": "document-1",
|
||||
"nodeId": "node-1",
|
||||
"pageNumber": 2,
|
||||
"sectionPath": ["Permissions", "Roles"],
|
||||
"text": "Only workspace owners can change permissions.",
|
||||
}
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
with patch.object(facade, "_interactive", interactive):
|
||||
result = facade.match_golden_question_evidence(
|
||||
tenant_id="tenant-1",
|
||||
account_id="account-1",
|
||||
control_space_id="control-1",
|
||||
payload=KnowledgeFSGoldenQuestionEvidenceMatchPayload(node_ids=[" node-1 ", "node-1"]),
|
||||
)
|
||||
|
||||
assert result.evidence == ""
|
||||
assert result.matched is False
|
||||
assert result.candidates[0].node_id == "node-1"
|
||||
assert result.candidates[0].projection_id is None
|
||||
assert result.candidates[0].score is None
|
||||
assert interactive.call_args.kwargs["payload"].model_dump(mode="json", by_alias=True, exclude_none=True) == {
|
||||
"minimumSimilarity": 0.7,
|
||||
"nodeIds": ["node-1"],
|
||||
"topK": 5,
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"remote_response",
|
||||
[None, True, 1, 1.5, [], "invalid", {}, {"items": []}, {"items": [None]}],
|
||||
|
||||
@ -20,6 +20,7 @@ from services.knowledge_fs.product_dto import (
|
||||
KnowledgeFSDocumentReindexPayload,
|
||||
KnowledgeFSDocumentReindexResponse,
|
||||
KnowledgeFSFindQuery,
|
||||
KnowledgeFSGoldenQuestionEvidenceMatchPayload,
|
||||
KnowledgeFSGoldenQuestionPayload,
|
||||
KnowledgeFSGoldenQuestionResponse,
|
||||
KnowledgeFSGrepQuery,
|
||||
@ -66,6 +67,19 @@ from services.knowledge_fs.product_dto import (
|
||||
)
|
||||
|
||||
|
||||
def test_golden_question_evidence_match_requires_exactly_one_lookup_mode() -> None:
|
||||
assert KnowledgeFSGoldenQuestionEvidenceMatchPayload(evidence=" permissions ").evidence == "permissions"
|
||||
assert KnowledgeFSGoldenQuestionEvidenceMatchPayload(node_ids=[" node-1 ", "node-1", "node-2"]).node_ids == [
|
||||
"node-1",
|
||||
"node-2",
|
||||
]
|
||||
|
||||
with pytest.raises(ValidationError, match="Provide exactly one of evidence or node_ids"):
|
||||
KnowledgeFSGoldenQuestionEvidenceMatchPayload()
|
||||
with pytest.raises(ValidationError, match="Provide exactly one of evidence or node_ids"):
|
||||
KnowledgeFSGoldenQuestionEvidenceMatchPayload(evidence="permissions", node_ids=["node-1"])
|
||||
|
||||
|
||||
def test_settings_response_serializes_rerank_plugin_id_with_its_public_alias() -> None:
|
||||
response = KnowledgeFSSettingsResponse.model_validate(
|
||||
{
|
||||
|
||||
@ -0,0 +1,29 @@
|
||||
# Golden Question Evidence Editor
|
||||
|
||||
## Summary
|
||||
|
||||
- Separated the durable Golden Question evidence selection from the transient text used to search
|
||||
for candidate evidence.
|
||||
- Added exact, permission-filtered node resolution to the existing evidence-match operation so an
|
||||
editor can render the passages represented by persisted `expectedEvidenceIds`.
|
||||
- Kept semantic evidence matching unchanged for interactive searches while allowing callers to
|
||||
request exact nodes with `nodeIds`.
|
||||
- Stopped new Console create and update flows from persisting the evidence search text as Golden
|
||||
Question metadata.
|
||||
|
||||
## User-visible behavior
|
||||
|
||||
- Editing a Golden Question displays every selected passage, its section path, and an individual
|
||||
remove action instead of only showing the selected count.
|
||||
- The evidence search field starts empty, clears after a successful lookup, and never becomes part
|
||||
of the Golden Question payload.
|
||||
- Saved node IDs that are no longer visible or no longer exist remain removable and are shown as
|
||||
stale instead of silently disappearing from the selection.
|
||||
|
||||
## Regression contract
|
||||
|
||||
- Exact evidence resolution preserves the requested node order and applies candidate permission
|
||||
filtering before returning text.
|
||||
- Semantic matching and retrieval-test promotion continue to expose selectable candidates.
|
||||
- Create and update requests persist `expectedEvidenceIds` and `matchPolicy`, but omit transient
|
||||
evidence search text.
|
||||
@ -1,4 +1,4 @@
|
||||
import type { GoldenQuestion } from "@knowledge/core";
|
||||
import { type GoldenQuestion, KnowledgeNodeSchema } from "@knowledge/core";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { encodeGoldenQuestionCursor } from "./cursor-utils";
|
||||
@ -16,6 +16,7 @@ import {
|
||||
deleteGoldenQuestionRoute,
|
||||
getGoldenQuestionRoute,
|
||||
listGoldenQuestionsRoute,
|
||||
matchGoldenQuestionEvidenceRoute,
|
||||
updateGoldenQuestionRoute,
|
||||
} from "./golden-question-routes";
|
||||
import { KnowledgeFsValidationError } from "./knowledge-fs-errors";
|
||||
@ -111,6 +112,48 @@ describe("golden-question handler branch coverage", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("resolves the exact visible evidence nodes without running semantic matching", async () => {
|
||||
const node = KnowledgeNodeSchema.parse({
|
||||
artifactHash: "f".repeat(64),
|
||||
documentAssetId: ASSET_ID,
|
||||
endOffset: 26,
|
||||
id: NODE_ID,
|
||||
kind: "chunk",
|
||||
knowledgeSpaceId: SPACE_ID,
|
||||
metadata: {},
|
||||
parseArtifactId: "018f0d60-7a49-7cc2-9c1b-5b36f18f2c46",
|
||||
permissionScope: [],
|
||||
sourceLocation: {
|
||||
endOffset: 26,
|
||||
pageNumber: 2,
|
||||
sectionPath: ["Permissions", "Roles"],
|
||||
startOffset: 0,
|
||||
},
|
||||
startOffset: 0,
|
||||
text: "Only workspace owners can change permissions.",
|
||||
});
|
||||
const fixture = goldenFixture({
|
||||
body: { nodeIds: [NODE_ID] },
|
||||
nodes: [node],
|
||||
});
|
||||
|
||||
expect(await fixture.invoke(matchGoldenQuestionEvidenceRoute)).toEqual({
|
||||
body: {
|
||||
items: [],
|
||||
resolvedEvidence: [
|
||||
{
|
||||
documentAssetId: ASSET_ID,
|
||||
nodeId: NODE_ID,
|
||||
pageNumber: 2,
|
||||
sectionPath: ["Permissions", "Roles"],
|
||||
text: "Only workspace owners can change permissions.",
|
||||
},
|
||||
],
|
||||
},
|
||||
status: 200,
|
||||
});
|
||||
});
|
||||
|
||||
it("gets existing questions and hides absent rows", async () => {
|
||||
expect(await goldenFixture().invoke(getGoldenQuestionRoute)).toMatchObject({
|
||||
body: { id: QUESTION_ID },
|
||||
@ -325,6 +368,7 @@ interface GoldenFixtureOptions {
|
||||
readonly keySpaceId?: string;
|
||||
readonly listError?: Error;
|
||||
readonly listResult?: { readonly items: GoldenQuestion[]; readonly nextCursor?: unknown };
|
||||
readonly nodes?: readonly unknown[];
|
||||
readonly permissionError?: Error;
|
||||
readonly query?: Record<string, unknown>;
|
||||
readonly space?: unknown;
|
||||
@ -380,7 +424,9 @@ function goldenFixture(options: GoldenFixtureOptions = {}) {
|
||||
get: vi.fn(async () => (options.asset === undefined ? { metadata: {} } : options.asset)),
|
||||
} as never,
|
||||
authorization: { authorize } as never,
|
||||
nodes: { getManyByIdsAcrossGenerations: vi.fn(async () => []) } as never,
|
||||
nodes: {
|
||||
getManyByIdsAcrossGenerations: vi.fn(async () => options.nodes ?? []),
|
||||
} as never,
|
||||
now: () => "2026-07-14T12:00:00.000Z",
|
||||
questions: questions as never,
|
||||
spaces: {
|
||||
|
||||
@ -145,12 +145,44 @@ export function registerGoldenQuestionHandlers({
|
||||
knowledgeSpaceId,
|
||||
now,
|
||||
});
|
||||
const body = context.req.valid("json");
|
||||
if (body.nodeIds) {
|
||||
const resolvedNodes = await nodes.getManyByIdsAcrossGenerations({
|
||||
ids: uniqueStrings(body.nodeIds),
|
||||
knowledgeSpaceId,
|
||||
});
|
||||
const nodesById = new Map(
|
||||
resolvedNodes
|
||||
.filter((node) => candidatePermissionAllowsNode(node, permission.candidateGrants))
|
||||
.map((node) => [node.id, node]),
|
||||
);
|
||||
return context.json(
|
||||
{
|
||||
items: [],
|
||||
resolvedEvidence: body.nodeIds.flatMap((nodeId) => {
|
||||
const node = nodesById.get(nodeId);
|
||||
if (!node) return [];
|
||||
return [
|
||||
{
|
||||
documentAssetId: node.documentAssetId,
|
||||
nodeId: node.id,
|
||||
...(node.sourceLocation.pageNumber === undefined
|
||||
? {}
|
||||
: { pageNumber: node.sourceLocation.pageNumber }),
|
||||
sectionPath: [...node.sourceLocation.sectionPath],
|
||||
text: node.text,
|
||||
},
|
||||
];
|
||||
}),
|
||||
},
|
||||
200,
|
||||
);
|
||||
}
|
||||
if (!evidenceMatcher) {
|
||||
return context.json({ error: "Golden question evidence matching is unavailable" }, 503);
|
||||
}
|
||||
const body = context.req.valid("json");
|
||||
const matches = await evidenceMatcher.match({
|
||||
evidenceTexts: body.evidenceTexts,
|
||||
evidenceTexts: body.evidenceTexts ?? [],
|
||||
knowledgeSpaceId,
|
||||
minimumSimilarity: body.minimumSimilarity,
|
||||
permissionScope: permission.candidateGrants,
|
||||
|
||||
@ -10,6 +10,7 @@ import {
|
||||
const MAX_GOLDEN_QUESTION_ANNOTATION_EVIDENCE = 50;
|
||||
export const MAX_GOLDEN_QUESTION_BULK_IMPORT_ROWS = 500;
|
||||
export const MAX_GOLDEN_QUESTION_EVIDENCE_MATCH_TEXTS = 500;
|
||||
export const MAX_GOLDEN_QUESTION_EXPECTED_EVIDENCE_IDS = 50;
|
||||
const DEFAULT_LIST_LIMIT = 100;
|
||||
const BoundedListLimitSchema = z.preprocess(
|
||||
(value) => (value === undefined ? DEFAULT_LIST_LIMIT : value),
|
||||
@ -148,11 +149,20 @@ export const MatchGoldenQuestionEvidenceSchema = z
|
||||
evidenceTexts: z
|
||||
.array(GoldenQuestionEvidenceTextSchema)
|
||||
.min(1)
|
||||
.max(MAX_GOLDEN_QUESTION_EVIDENCE_MATCH_TEXTS),
|
||||
.max(MAX_GOLDEN_QUESTION_EVIDENCE_MATCH_TEXTS)
|
||||
.optional(),
|
||||
minimumSimilarity: GoldenQuestionMinimumSimilaritySchema,
|
||||
nodeIds: z
|
||||
.array(z.string().uuid())
|
||||
.min(1)
|
||||
.max(MAX_GOLDEN_QUESTION_EXPECTED_EVIDENCE_IDS)
|
||||
.optional(),
|
||||
topK: GoldenQuestionEvidenceTopKSchema,
|
||||
})
|
||||
.strict();
|
||||
.strict()
|
||||
.refine((value) => Boolean(value.evidenceTexts) !== Boolean(value.nodeIds), {
|
||||
message: "Provide exactly one of evidenceTexts or nodeIds",
|
||||
});
|
||||
|
||||
export const GoldenQuestionEvidenceCandidateSchema = z
|
||||
.object({
|
||||
@ -174,9 +184,20 @@ export const GoldenQuestionEvidenceMatchSchema = z
|
||||
})
|
||||
.strict();
|
||||
|
||||
export const GoldenQuestionResolvedEvidenceSchema = z
|
||||
.object({
|
||||
documentAssetId: z.string().uuid(),
|
||||
nodeId: z.string().uuid(),
|
||||
pageNumber: z.number().int().positive().optional(),
|
||||
sectionPath: z.array(z.string()),
|
||||
text: z.string(),
|
||||
})
|
||||
.strict();
|
||||
|
||||
export const MatchGoldenQuestionEvidenceResponseSchema = z
|
||||
.object({
|
||||
items: z.array(GoldenQuestionEvidenceMatchSchema),
|
||||
resolvedEvidence: z.array(GoldenQuestionResolvedEvidenceSchema).optional(),
|
||||
})
|
||||
.strict();
|
||||
|
||||
|
||||
@ -415,8 +415,9 @@ export type KnowledgeFsGoldenQuestionBulkImportResponse = {
|
||||
}
|
||||
|
||||
export type KnowledgeFsGoldenQuestionEvidenceMatchPayload = {
|
||||
evidence: string
|
||||
evidence?: string | null
|
||||
minimum_similarity?: number
|
||||
node_ids?: Array<string>
|
||||
top_k?: number
|
||||
}
|
||||
|
||||
@ -1590,8 +1591,8 @@ export type KnowledgeFsGoldenQuestionEvidenceCandidateResponse = {
|
||||
document_asset_id: string
|
||||
node_id: string
|
||||
page_number?: number | null
|
||||
projection_id: string
|
||||
score: number
|
||||
projection_id?: string | null
|
||||
score?: number | null
|
||||
section_path: Array<string>
|
||||
text: string
|
||||
}
|
||||
|
||||
@ -213,8 +213,9 @@ export const zKnowledgeFsGoldenQuestionListResponse = z.object({
|
||||
* KnowledgeFSGoldenQuestionEvidenceMatchPayload
|
||||
*/
|
||||
export const zKnowledgeFsGoldenQuestionEvidenceMatchPayload = z.object({
|
||||
evidence: z.string().min(1).max(8000),
|
||||
evidence: z.string().min(1).max(8000).nullish(),
|
||||
minimum_similarity: z.number().gte(0).lte(1).optional().default(0.7),
|
||||
node_ids: z.array(z.string()).max(50).optional(),
|
||||
top_k: z.int().gte(1).lte(10).optional().default(5),
|
||||
})
|
||||
|
||||
@ -1521,8 +1522,8 @@ export const zKnowledgeFsGoldenQuestionEvidenceCandidateResponse = z.object({
|
||||
document_asset_id: z.string(),
|
||||
node_id: z.string(),
|
||||
page_number: z.int().nullish(),
|
||||
projection_id: z.string(),
|
||||
score: z.number().gte(0).lte(1),
|
||||
projection_id: z.string().nullish(),
|
||||
score: z.number().gte(0).lte(1).nullish(),
|
||||
section_path: z.array(z.string()),
|
||||
text: z.string(),
|
||||
})
|
||||
|
||||
@ -73,6 +73,17 @@ describe('SearchInput', () => {
|
||||
screen.queryByRole('button', { name: 'common.operation.clear' }),
|
||||
).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('forwards keyboard events so forms can own the search action', async () => {
|
||||
const onKeyDown = vi.fn()
|
||||
const user = userEvent.setup()
|
||||
render(<SearchInput value="query" onValueChange={() => {}} onKeyDown={onKeyDown} />)
|
||||
|
||||
await user.click(screen.getByRole('searchbox'))
|
||||
await user.keyboard('{Enter}')
|
||||
|
||||
expect(onKeyDown).toHaveBeenCalledWith(expect.objectContaining({ key: 'Enter' }))
|
||||
})
|
||||
})
|
||||
|
||||
describe('Interaction', () => {
|
||||
|
||||
@ -13,7 +13,7 @@ type SearchInputProps = {
|
||||
className?: string
|
||||
} & Pick<
|
||||
InputGroupInputProps,
|
||||
'aria-describedby' | 'aria-label' | 'autoFocus' | 'disabled' | 'name'
|
||||
'aria-describedby' | 'aria-label' | 'autoFocus' | 'disabled' | 'name' | 'onKeyDown'
|
||||
>
|
||||
|
||||
export function SearchInput({
|
||||
@ -25,6 +25,7 @@ export function SearchInput({
|
||||
name = 'query',
|
||||
autoFocus,
|
||||
disabled,
|
||||
onKeyDown,
|
||||
'aria-describedby': ariaDescribedBy,
|
||||
'aria-label': ariaLabel,
|
||||
}: SearchInputProps) {
|
||||
@ -89,6 +90,7 @@ export function SearchInput({
|
||||
// oxlint-disable-next-line jsx-a11y/no-autofocus
|
||||
autoFocus={autoFocus}
|
||||
enterKeyHint="search"
|
||||
onKeyDown={onKeyDown}
|
||||
/>
|
||||
<InputGroupAddon className="ps-1.75 pe-1.25">
|
||||
<span
|
||||
|
||||
@ -224,7 +224,6 @@ describe('QualityPage', () => {
|
||||
expect(serviceMock.createGolden.mock.calls[0]?.[0]).toEqual({
|
||||
body: {
|
||||
annotation: 'Expected answer',
|
||||
evidence_text: '',
|
||||
expected_evidence_ids: [],
|
||||
match_policy: 'all',
|
||||
question: 'New question',
|
||||
@ -263,7 +262,6 @@ describe('QualityPage', () => {
|
||||
expect(serviceMock.createGolden.mock.calls[0]?.[0]).toEqual({
|
||||
body: {
|
||||
annotation: 'Expected answer',
|
||||
evidence_text: '',
|
||||
expected_evidence_ids: [],
|
||||
match_policy: 'all',
|
||||
question: 'New question',
|
||||
@ -274,7 +272,7 @@ describe('QualityPage', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('matches a human-readable evidence passage and stores the selected node id', async () => {
|
||||
it('uses a human-readable search only to select the persisted evidence node id', async () => {
|
||||
serviceMock.matchEvidence.mockResolvedValue({
|
||||
candidates: [
|
||||
{
|
||||
@ -322,7 +320,6 @@ describe('QualityPage', () => {
|
||||
expect(serviceMock.createGolden.mock.calls[0]?.[0]).toEqual({
|
||||
body: {
|
||||
annotation: 'The answer must cite the refund window.',
|
||||
evidence_text: 'refund within 30 days',
|
||||
expected_evidence_ids: ['node-1'],
|
||||
match_policy: 'all',
|
||||
question: 'When can I request a refund?',
|
||||
@ -417,7 +414,6 @@ describe('QualityPage', () => {
|
||||
expect(serviceMock.createGolden.mock.calls[0]?.[0]).toEqual({
|
||||
body: {
|
||||
annotation: 'The answer must cite both policies.',
|
||||
evidence_text: 'refund and cancellation policy',
|
||||
expected_evidence_ids: ['node-1', 'node-2'],
|
||||
match_policy: 'any',
|
||||
question: 'When can I request a refund?',
|
||||
@ -580,7 +576,6 @@ describe('QualityPage', () => {
|
||||
{
|
||||
body: {
|
||||
annotation: 'Updated expected answer',
|
||||
evidence_text: '',
|
||||
expected_evidence_ids: [],
|
||||
match_policy: 'all',
|
||||
question: 'What is the refund policy?',
|
||||
@ -596,6 +591,100 @@ describe('QualityPage', () => {
|
||||
).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('resolves and displays saved evidence passages while keeping the search query ephemeral', async () => {
|
||||
serviceMock.getGolden.mockResolvedValue({
|
||||
data: [
|
||||
{
|
||||
annotation: 'Must cite both permission rules.',
|
||||
created_at: '2026-07-28T00:00:00Z',
|
||||
evidence_text: '权限',
|
||||
expected_evidence_ids: ['node-1', 'node-2'],
|
||||
id: 'golden-1',
|
||||
match_policy: 'all',
|
||||
question: 'Who can change permissions?',
|
||||
status: 'active',
|
||||
tags: ['permissions'],
|
||||
updated_at: '2026-07-28T00:00:00Z',
|
||||
},
|
||||
],
|
||||
next_cursor: null,
|
||||
})
|
||||
serviceMock.matchEvidence.mockImplementation(
|
||||
async ({ body }: { body: { evidence?: string; node_ids?: string[] } }) => {
|
||||
if (body.node_ids) {
|
||||
return {
|
||||
candidates: [
|
||||
{
|
||||
document_asset_id: 'document-1',
|
||||
node_id: 'node-1',
|
||||
section_path: ['Permissions', 'Owners'],
|
||||
text: 'Workspace owners can change member permissions.',
|
||||
},
|
||||
{
|
||||
document_asset_id: 'document-1',
|
||||
node_id: 'node-2',
|
||||
section_path: ['Permissions', 'Admins'],
|
||||
text: 'Administrators can assign application roles.',
|
||||
},
|
||||
],
|
||||
evidence: '',
|
||||
matched: false,
|
||||
}
|
||||
}
|
||||
return { candidates: [], evidence: body.evidence ?? '', matched: false }
|
||||
},
|
||||
)
|
||||
serviceMock.updateGolden.mockResolvedValue({})
|
||||
const user = userEvent.setup()
|
||||
renderPage()
|
||||
|
||||
await screen.findByText('Who can change permissions?')
|
||||
await user.click(
|
||||
screen.getByRole('button', {
|
||||
name: /dataset\.newKnowledge\.qualityPage\.questionActions/,
|
||||
}),
|
||||
)
|
||||
await user.click(
|
||||
await screen.findByRole('menuitem', { name: 'dataset.newKnowledge.qualityPage.edit' }),
|
||||
)
|
||||
|
||||
expect(await screen.findByText('Workspace owners can change member permissions.')).toBeVisible()
|
||||
expect(screen.getByText('Administrators can assign application roles.')).toBeVisible()
|
||||
expect(serviceMock.matchEvidence.mock.calls[0]?.[0]).toEqual({
|
||||
body: { node_ids: ['node-1', 'node-2'] },
|
||||
params: { control_space_id: 'space-1' },
|
||||
})
|
||||
|
||||
const search = screen.getByRole('searchbox', {
|
||||
name: 'dataset.newKnowledge.qualityPage.findEvidence',
|
||||
})
|
||||
expect(search).toHaveValue('')
|
||||
await user.type(search, '权限')
|
||||
await user.click(
|
||||
screen.getByRole('button', { name: 'dataset.newKnowledge.qualityPage.findEvidence' }),
|
||||
)
|
||||
await waitFor(() => expect(search).toHaveValue(''))
|
||||
expect(screen.getByText('Workspace owners can change member permissions.')).toBeVisible()
|
||||
expect(screen.getByText('Administrators can assign application roles.')).toBeVisible()
|
||||
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.qualityPage.save' }))
|
||||
await waitFor(() =>
|
||||
expect(serviceMock.updateGolden).toHaveBeenCalledWith(
|
||||
{
|
||||
body: {
|
||||
annotation: 'Must cite both permission rules.',
|
||||
expected_evidence_ids: ['node-1', 'node-2'],
|
||||
match_policy: 'all',
|
||||
question: 'Who can change permissions?',
|
||||
tags: ['permissions'],
|
||||
},
|
||||
params: { control_space_id: 'space-1', question_id: 'golden-1' },
|
||||
},
|
||||
expect.anything(),
|
||||
),
|
||||
)
|
||||
})
|
||||
|
||||
it('resolves the protected trace reference before navigating', async () => {
|
||||
const user = userEvent.setup()
|
||||
navigationMock.tab = 'bad-cases'
|
||||
@ -892,7 +981,6 @@ describe('QualityPage', () => {
|
||||
expect(serviceMock.createGolden.mock.calls[0]?.[0]).toEqual({
|
||||
body: {
|
||||
annotation: 'coverage gap',
|
||||
evidence_text: '',
|
||||
expected_evidence_ids: [],
|
||||
match_policy: 'all',
|
||||
question: 'Refund after activation',
|
||||
|
||||
@ -1567,7 +1567,6 @@ describe('RetrievalTestPage', () => {
|
||||
expect(apiMock.createGolden).toHaveBeenCalledWith({
|
||||
body: {
|
||||
annotation: 'The answer must cite the retrieved useEffect evidence.',
|
||||
evidence_text: '',
|
||||
expected_evidence_ids: ['chunk-1'],
|
||||
match_policy: 'all',
|
||||
question: 'What is useEffect?',
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import type { FormEvent } from 'react'
|
||||
import type { FormEvent, KeyboardEvent } from 'react'
|
||||
import type { GoldenQuestionDraft, GoldenQuestionEvidenceOption } from './types'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { Checkbox } from '@langgenius/dify-ui/checkbox'
|
||||
@ -15,12 +15,14 @@ import {
|
||||
} from '@langgenius/dify-ui/dialog'
|
||||
import { Field, FieldError, FieldItem, FieldLabel } from '@langgenius/dify-ui/field'
|
||||
import { Fieldset, FieldsetLegend } from '@langgenius/dify-ui/fieldset'
|
||||
import { IconButton } from '@langgenius/dify-ui/icon-button'
|
||||
import { Input } from '@langgenius/dify-ui/input'
|
||||
import { RadioGroup, RadioItem } from '@langgenius/dify-ui/radio'
|
||||
import { Textarea } from '@langgenius/dify-ui/textarea'
|
||||
import { useMutation } from '@tanstack/react-query'
|
||||
import { useState } from 'react'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { SearchInput } from '@/app/components/base/search-input'
|
||||
import { consoleQuery } from '@/service/client'
|
||||
|
||||
type DialogMode = 'create' | 'edit' | 'promote'
|
||||
@ -66,18 +68,44 @@ export function GoldenQuestionDialog({
|
||||
pending?: boolean
|
||||
}) {
|
||||
const { t } = useTranslation('dataset')
|
||||
const { t: tCommon } = useTranslation('common')
|
||||
const [question, setQuestion] = useState(initialValue.question)
|
||||
const [annotation, setAnnotation] = useState(initialValue.annotation)
|
||||
const [evidenceText, setEvidenceText] = useState(initialValue.evidenceText)
|
||||
const [evidenceQuery, setEvidenceQuery] = useState('')
|
||||
const [expectedEvidenceIds, setExpectedEvidenceIds] = useState(initialValue.expectedEvidenceIds)
|
||||
const [evidenceByNodeId, setEvidenceByNodeId] = useState(
|
||||
() => new Map(evidenceOptions.map((option) => [option.node_id, option])),
|
||||
)
|
||||
const [matchPolicy, setMatchPolicy] = useState(initialValue.matchPolicy)
|
||||
const [tags, setTags] = useState(initialValue.tags.join(', '))
|
||||
const [questionInvalid, setQuestionInvalid] = useState(false)
|
||||
const [annotationInvalid, setAnnotationInvalid] = useState(false)
|
||||
const [matchError, setMatchError] = useState<'unavailable' | 'unknown'>()
|
||||
const mergeEvidenceOptions = useCallback((options: readonly GoldenQuestionEvidenceOption[]) => {
|
||||
setEvidenceByNodeId((current) => {
|
||||
const next = new Map(current)
|
||||
for (const option of options) next.set(option.node_id, option)
|
||||
return next
|
||||
})
|
||||
}, [])
|
||||
const matchMutation = useMutation(
|
||||
consoleQuery.knowledgeFs.spaces.byControlSpaceId.goldenQuestions.evidenceMatches.post.mutationOptions(),
|
||||
)
|
||||
const resolveMutation = useMutation({
|
||||
...consoleQuery.knowledgeFs.spaces.byControlSpaceId.goldenQuestions.evidenceMatches.post.mutationOptions(),
|
||||
onSuccess: (data) => mergeEvidenceOptions(data.candidates),
|
||||
})
|
||||
const unresolvedInitialEvidenceKey = initialValue.expectedEvidenceIds
|
||||
.filter((nodeId) => !evidenceByNodeId.has(nodeId))
|
||||
.join(',')
|
||||
const resolveEvidence = resolveMutation.mutate
|
||||
useEffect(() => {
|
||||
if (!unresolvedInitialEvidenceKey) return
|
||||
resolveEvidence({
|
||||
body: { node_ids: unresolvedInitialEvidenceKey.split(',') },
|
||||
params: { control_space_id: knowledgeSpaceId },
|
||||
})
|
||||
}, [knowledgeSpaceId, resolveEvidence, unresolvedInitialEvidenceKey])
|
||||
const title =
|
||||
mode === 'create'
|
||||
? t(($) => $['newKnowledge.qualityPage.createTitle'])
|
||||
@ -98,7 +126,6 @@ export function GoldenQuestionDialog({
|
||||
if (nextQuestionInvalid || nextAnnotationInvalid) return
|
||||
await onSubmit({
|
||||
annotation: annotation.trim(),
|
||||
evidenceText: evidenceText.trim(),
|
||||
expectedEvidenceIds,
|
||||
matchPolicy,
|
||||
question: question.trim(),
|
||||
@ -107,24 +134,28 @@ export function GoldenQuestionDialog({
|
||||
}
|
||||
|
||||
const findEvidence = async () => {
|
||||
if (!evidenceText.trim()) return
|
||||
const query = evidenceQuery.trim()
|
||||
if (!query) return
|
||||
setMatchError(undefined)
|
||||
try {
|
||||
await matchMutation.mutateAsync({
|
||||
body: { evidence: evidenceText.trim() },
|
||||
const result = await matchMutation.mutateAsync({
|
||||
body: { evidence: query },
|
||||
params: { control_space_id: knowledgeSpaceId },
|
||||
})
|
||||
mergeEvidenceOptions(result.candidates)
|
||||
setEvidenceQuery('')
|
||||
} catch (error) {
|
||||
setMatchError(errorStatus(error) === 503 ? 'unavailable' : 'unknown')
|
||||
}
|
||||
}
|
||||
|
||||
const candidatesByNodeId = new Map<string, GoldenQuestionEvidenceOption>(
|
||||
evidenceOptions.map((candidate) => [candidate.node_id, candidate]),
|
||||
)
|
||||
for (const candidate of matchMutation.data?.candidates ?? [])
|
||||
candidatesByNodeId.set(candidate.node_id, candidate)
|
||||
const candidates = [...candidatesByNodeId.values()]
|
||||
const handleEvidenceSearchKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
|
||||
if (event.key !== 'Enter' || event.nativeEvent.isComposing) return
|
||||
event.preventDefault()
|
||||
void findEvidence()
|
||||
}
|
||||
|
||||
const searchCandidates = matchMutation.data?.candidates ?? evidenceOptions
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogPortal>
|
||||
@ -181,47 +212,90 @@ export function GoldenQuestionDialog({
|
||||
)}
|
||||
{!annotationInvalid && error && <FieldError match>{error}</FieldError>}
|
||||
</Field>
|
||||
<div className="grid min-w-0 gap-1">
|
||||
<Field name="evidence">
|
||||
<FieldLabel>{t(($) => $['newKnowledge.qualityPage.evidence'])}</FieldLabel>
|
||||
<Textarea
|
||||
className="h-20 resize-y"
|
||||
placeholder={t(($) => $['newKnowledge.qualityPage.evidencePlaceholder'])}
|
||||
value={evidenceText}
|
||||
onValueChange={(value) => {
|
||||
setEvidenceText(value)
|
||||
setMatchError(undefined)
|
||||
matchMutation.reset()
|
||||
}}
|
||||
/>
|
||||
<div className="mt-2 flex items-center justify-between gap-3">
|
||||
<span className="system-xs-regular text-text-tertiary">
|
||||
{expectedEvidenceIds.length > 0
|
||||
? t(($) => $['newKnowledge.qualityPage.evidenceSelected'], {
|
||||
count: expectedEvidenceIds.length,
|
||||
})
|
||||
: t(($) => $['newKnowledge.qualityPage.noEvidenceSelected'])}
|
||||
</span>
|
||||
<div className="flex gap-2">
|
||||
{expectedEvidenceIds.length > 0 && (
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
disabled={pending || matchMutation.isPending}
|
||||
onClick={() => setExpectedEvidenceIds([])}
|
||||
>
|
||||
{t(($) => $['newKnowledge.qualityPage.clearEvidence'])}
|
||||
</Button>
|
||||
)}
|
||||
<div className="grid min-w-0 gap-4">
|
||||
<Field name="expectedEvidenceIds">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<FieldLabel>{t(($) => $['newKnowledge.qualityPage.evidence'])}</FieldLabel>
|
||||
{expectedEvidenceIds.length > 0 && (
|
||||
<Button
|
||||
type="button"
|
||||
loading={matchMutation.isPending}
|
||||
disabled={!evidenceText.trim() || pending || matchMutation.isPending}
|
||||
onClick={() => void findEvidence()}
|
||||
variant="ghost"
|
||||
disabled={pending}
|
||||
onClick={() => setExpectedEvidenceIds([])}
|
||||
>
|
||||
{t(($) => $['newKnowledge.qualityPage.findEvidence'])}
|
||||
{t(($) => $['newKnowledge.qualityPage.clearEvidence'])}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<p className="body-xs-regular text-text-tertiary">
|
||||
{expectedEvidenceIds.length > 0
|
||||
? t(($) => $['newKnowledge.qualityPage.evidenceSelected'], {
|
||||
count: expectedEvidenceIds.length,
|
||||
})
|
||||
: t(($) => $['newKnowledge.qualityPage.noEvidenceSelected'])}
|
||||
</p>
|
||||
{expectedEvidenceIds.length > 0 && (
|
||||
<div className="mt-2 flex max-h-52 flex-col gap-2 overflow-y-auto rounded-lg border border-divider-subtle p-2">
|
||||
{expectedEvidenceIds.map((nodeId) => {
|
||||
const evidence = evidenceByNodeId.get(nodeId)
|
||||
return (
|
||||
<div
|
||||
key={nodeId}
|
||||
className="flex items-start gap-2 rounded-md bg-background-section-burn p-2"
|
||||
>
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="body-xs-regular whitespace-pre-wrap text-text-secondary">
|
||||
{evidence?.text || nodeId}
|
||||
</p>
|
||||
<p className="mt-1 system-2xs-medium-uppercase text-text-tertiary">
|
||||
{evidence?.section_path.join(' / ') ||
|
||||
t(($) => $['newKnowledge.qualityPage.goldenStatus.stale'])}
|
||||
</p>
|
||||
</div>
|
||||
<IconButton
|
||||
type="button"
|
||||
size="sm"
|
||||
disabled={pending}
|
||||
aria-label={`${tCommon(($) => $['operation.remove'])}: ${evidence?.text || nodeId}`}
|
||||
onClick={() =>
|
||||
setExpectedEvidenceIds((current) =>
|
||||
current.filter((currentId) => currentId !== nodeId),
|
||||
)
|
||||
}
|
||||
>
|
||||
<span aria-hidden className="i-ri-close-line size-4" />
|
||||
</IconButton>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</Field>
|
||||
<Field name="evidenceSearch">
|
||||
<FieldLabel>{t(($) => $['newKnowledge.qualityPage.findEvidence'])}</FieldLabel>
|
||||
<div className="flex items-center gap-2">
|
||||
<SearchInput
|
||||
name="evidence-search"
|
||||
aria-label={t(($) => $['newKnowledge.qualityPage.findEvidence'])}
|
||||
className="min-w-0 flex-1"
|
||||
disabled={pending || matchMutation.isPending}
|
||||
placeholder={t(($) => $['newKnowledge.qualityPage.evidencePlaceholder'])}
|
||||
value={evidenceQuery}
|
||||
onKeyDown={handleEvidenceSearchKeyDown}
|
||||
onValueChange={(value) => {
|
||||
setEvidenceQuery(value)
|
||||
setMatchError(undefined)
|
||||
matchMutation.reset()
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
loading={matchMutation.isPending}
|
||||
disabled={!evidenceQuery.trim() || pending || matchMutation.isPending}
|
||||
onClick={() => void findEvidence()}
|
||||
>
|
||||
{t(($) => $['newKnowledge.qualityPage.findEvidence'])}
|
||||
</Button>
|
||||
</div>
|
||||
{matchError && (
|
||||
<FieldError match>
|
||||
@ -230,16 +304,16 @@ export function GoldenQuestionDialog({
|
||||
: t(($) => $.unknownError)}
|
||||
</FieldError>
|
||||
)}
|
||||
{matchMutation.isSuccess && (matchMutation.data?.candidates.length ?? 0) === 0 && (
|
||||
{matchMutation.isSuccess && searchCandidates.length === 0 && (
|
||||
<p className="mt-2 body-xs-regular text-text-tertiary">
|
||||
{t(($) => $['newKnowledge.qualityPage.noEvidenceMatch'])}
|
||||
</p>
|
||||
)}
|
||||
</Field>
|
||||
{candidates.length > 0 && (
|
||||
<Field name="expectedEvidenceIds">
|
||||
{searchCandidates.length > 0 && (
|
||||
<Field name="evidenceSearchResults">
|
||||
<Fieldset
|
||||
className="mt-2 flex max-h-52 flex-col gap-2 overflow-y-auto rounded-lg border border-divider-subtle p-2"
|
||||
className="flex max-h-52 flex-col gap-2 overflow-y-auto rounded-lg border border-divider-subtle p-2"
|
||||
render={
|
||||
<CheckboxGroup
|
||||
value={expectedEvidenceIds}
|
||||
@ -248,9 +322,9 @@ export function GoldenQuestionDialog({
|
||||
}
|
||||
>
|
||||
<FieldsetLegend className="sr-only">
|
||||
{t(($) => $['newKnowledge.qualityPage.evidence'])}
|
||||
{t(($) => $['newKnowledge.qualityPage.findEvidence'])}
|
||||
</FieldsetLegend>
|
||||
{candidates.map((candidate) => (
|
||||
{searchCandidates.map((candidate) => (
|
||||
<FieldItem key={candidate.node_id}>
|
||||
<FieldLabel className="flex w-full cursor-pointer items-start gap-2 rounded-md p-2 hover:bg-state-base-hover">
|
||||
<Checkbox className="mt-0.5" value={candidate.node_id} />
|
||||
@ -261,7 +335,7 @@ export function GoldenQuestionDialog({
|
||||
<span className="mt-1 block system-2xs-medium-uppercase text-text-tertiary">
|
||||
{candidate.section_path.join(' / ') ||
|
||||
t(($) => $['newKnowledge.qualityPage.evidence'])}
|
||||
{candidate.score !== undefined && (
|
||||
{candidate.score !== undefined && candidate.score !== null && (
|
||||
<>
|
||||
{' · '}
|
||||
{Math.round(candidate.score * 100)}%
|
||||
|
||||
@ -36,7 +36,6 @@ import { GoldenQuestionImportDialog } from './golden-question-import-dialog'
|
||||
|
||||
const emptyDraft: GoldenQuestionDraft = {
|
||||
annotation: '',
|
||||
evidenceText: '',
|
||||
expectedEvidenceIds: [],
|
||||
matchPolicy: 'all',
|
||||
question: '',
|
||||
@ -128,7 +127,6 @@ function GoldenStatus({ status }: { status: 'active' | 'draft' | 'stale' }) {
|
||||
function goldenQuestionPayload(draft: GoldenQuestionDraft) {
|
||||
return {
|
||||
annotation: draft.annotation,
|
||||
evidence_text: draft.evidenceText,
|
||||
expected_evidence_ids: draft.expectedEvidenceIds,
|
||||
match_policy: draft.matchPolicy,
|
||||
question: draft.question,
|
||||
@ -345,7 +343,6 @@ export function QualityPage({ knowledgeSpaceId }: { knowledgeSpaceId: string })
|
||||
try {
|
||||
const { badCase, goldenQuestionId } = await ensureLinkedGoldenQuestion(item, {
|
||||
annotation: item.reason,
|
||||
evidenceText: '',
|
||||
expectedEvidenceIds: [],
|
||||
matchPolicy: 'all',
|
||||
question: item.question ?? '',
|
||||
@ -585,7 +582,6 @@ export function QualityPage({ knowledgeSpaceId }: { knowledgeSpaceId: string })
|
||||
mode: 'edit',
|
||||
value: {
|
||||
annotation: item.annotation,
|
||||
evidenceText: item.evidence_text ?? '',
|
||||
expectedEvidenceIds: item.expected_evidence_ids ?? [],
|
||||
matchPolicy: item.match_policy ?? 'all',
|
||||
question: item.question,
|
||||
@ -695,7 +691,6 @@ export function QualityPage({ knowledgeSpaceId }: { knowledgeSpaceId: string })
|
||||
mode: 'promote',
|
||||
value: {
|
||||
annotation: '',
|
||||
evidenceText: '',
|
||||
expectedEvidenceIds: [],
|
||||
matchPolicy: 'all',
|
||||
question: item.question ?? '',
|
||||
@ -736,7 +731,6 @@ export function QualityPage({ knowledgeSpaceId }: { knowledgeSpaceId: string })
|
||||
mode: 'promote',
|
||||
value: {
|
||||
annotation: '',
|
||||
evidenceText: '',
|
||||
expectedEvidenceIds: [],
|
||||
matchPolicy: 'all',
|
||||
question: item.question ?? '',
|
||||
|
||||
@ -2,7 +2,6 @@ import type { KnowledgeFsGoldenQuestionEvidenceCandidateResponse } from '@dify/c
|
||||
|
||||
export type GoldenQuestionDraft = {
|
||||
annotation: string
|
||||
evidenceText: string
|
||||
expectedEvidenceIds: string[]
|
||||
matchPolicy: 'all' | 'any'
|
||||
question: string
|
||||
|
||||
@ -1601,7 +1601,6 @@ export function RetrievalTestPage({ knowledgeSpaceId }: { knowledgeSpaceId: stri
|
||||
resultKey,
|
||||
value: {
|
||||
annotation: '',
|
||||
evidenceText: '',
|
||||
expectedEvidenceIds: [],
|
||||
matchPolicy: 'all',
|
||||
question: selectedQuery,
|
||||
@ -1641,7 +1640,6 @@ export function RetrievalTestPage({ knowledgeSpaceId }: { knowledgeSpaceId: stri
|
||||
await consoleClient.knowledgeFs.spaces.byControlSpaceId.goldenQuestions.post({
|
||||
body: {
|
||||
annotation: draft.annotation,
|
||||
evidence_text: draft.evidenceText,
|
||||
expected_evidence_ids: draft.expectedEvidenceIds,
|
||||
match_policy: draft.matchPolicy,
|
||||
question: draft.question,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user