Skip to content

Commit 5a88c01

Browse files
authored
Feat: Filter structured output data directly during the rendering stage. #10866 (#10958)
### What problem does this PR solve? Feat: Filter structured output data directly during the rendering stage. #10866 ### Type of change - [x] New Feature (non-breaking change which adds functionality)
1 parent 256b0fb commit 5a88c01

File tree

5 files changed

+41
-111
lines changed

5 files changed

+41
-111
lines changed

web/src/pages/agent/form/components/prompt-editor/variable-picker-plugin.tsx

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,10 @@ import * as ReactDOM from 'react-dom';
3131
import { $createVariableNode } from './variable-node';
3232

3333
import {
34-
useFilterStructuredOutputByValue,
3534
useFindAgentStructuredOutputLabel,
3635
useShowSecondaryMenu,
3736
} from '@/pages/agent/hooks/use-build-structured-output';
3837
import { useBuildQueryVariableOptions } from '@/pages/agent/hooks/use-get-begin-query';
39-
import { hasJsonSchemaChild } from '@/pages/agent/utils/filter-agent-structured-output';
4038
import { PromptIdentity } from '../../agent-form/use-build-prompt-options';
4139
import { StructuredOutputSecondaryMenu } from '../structured-output-secondary-menu';
4240
import { ProgrammaticTag } from './constant';
@@ -89,8 +87,6 @@ function VariablePickerMenuItem({
8987
option: VariableOption | VariableInnerOption,
9088
) => void;
9189
}) {
92-
const filterStructuredOutput = useFilterStructuredOutputByValue();
93-
9490
const showSecondaryMenu = useShowSecondaryMenu();
9591

9692
return (
@@ -108,12 +104,6 @@ function VariablePickerMenuItem({
108104
const shouldShowSecondary = showSecondaryMenu(x.value, x.label);
109105

110106
if (shouldShowSecondary) {
111-
const filteredStructuredOutput = filterStructuredOutput(x.value);
112-
113-
if (!hasJsonSchemaChild(filteredStructuredOutput)) {
114-
return null;
115-
}
116-
117107
return (
118108
<StructuredOutputSecondaryMenu
119109
key={x.value}
@@ -124,7 +114,6 @@ function VariablePickerMenuItem({
124114
...y,
125115
} as VariableInnerOption)
126116
}
127-
filteredStructuredOutput={filteredStructuredOutput}
128117
></StructuredOutputSecondaryMenu>
129118
);
130119
}

web/src/pages/agent/form/components/select-with-secondary-menu.tsx

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,9 @@ import { useCallback } from 'react';
2525
import { useTranslation } from 'react-i18next';
2626
import { VariableType } from '../../constant';
2727
import {
28-
useFilterStructuredOutputByValue,
2928
useFindAgentStructuredOutputLabel,
3029
useShowSecondaryMenu,
3130
} from '../../hooks/use-build-structured-output';
32-
import { hasJsonSchemaChild } from '../../utils/filter-agent-structured-output';
3331
import { StructuredOutputSecondaryMenu } from './structured-output-secondary-menu';
3432

3533
type Item = {
@@ -68,7 +66,6 @@ export function GroupedSelectWithSecondaryMenu({
6866
const [open, setOpen] = React.useState(false);
6967

7068
const showSecondaryMenu = useShowSecondaryMenu();
71-
const filterStructuredOutput = useFilterStructuredOutputByValue();
7269
const findAgentStructuredOutputLabel = useFindAgentStructuredOutputLabel();
7370

7471
// Find the label of the selected item
@@ -155,21 +152,11 @@ export function GroupedSelectWithSecondaryMenu({
155152
);
156153

157154
if (shouldShowSecondary) {
158-
const filteredStructuredOutput = filterStructuredOutput(
159-
option.value,
160-
type,
161-
);
162-
163-
if (!hasJsonSchemaChild(filteredStructuredOutput)) {
164-
return null;
165-
}
166-
167155
return (
168156
<StructuredOutputSecondaryMenu
169157
key={option.value}
170158
data={option}
171159
click={handleSecondaryMenuClick}
172-
filteredStructuredOutput={filteredStructuredOutput}
173160
type={type}
174161
></StructuredOutputSecondaryMenu>
175162
);

web/src/pages/agent/form/components/structured-output-secondary-menu.tsx

Lines changed: 36 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { JSONSchema } from '@/components/jsonjoy-builder';
21
import {
32
HoverCard,
43
HoverCardContent,
@@ -9,22 +8,28 @@ import { get, isEmpty, isPlainObject } from 'lodash';
98
import { ChevronRight } from 'lucide-react';
109
import { PropsWithChildren, ReactNode, useCallback } from 'react';
1110
import { JsonSchemaDataType, VariableType } from '../../constant';
11+
import { useGetStructuredOutputByValue } from '../../hooks/use-build-structured-output';
12+
import {
13+
hasJsonSchemaChild,
14+
hasSpecificTypeChild,
15+
} from '../../utils/filter-agent-structured-output';
1216

1317
type DataItem = { label: ReactNode; value: string; parentLabel?: ReactNode };
1418

1519
type StructuredOutputSecondaryMenuProps = {
1620
data: DataItem;
1721
click(option: { label: ReactNode; value: string }): void;
18-
filteredStructuredOutput: JSONSchema;
1922
type?: VariableType | JsonSchemaDataType;
2023
} & PropsWithChildren;
2124

2225
export function StructuredOutputSecondaryMenu({
2326
data,
2427
click,
25-
filteredStructuredOutput,
2628
type,
2729
}: StructuredOutputSecondaryMenuProps) {
30+
const filterStructuredOutput = useGetStructuredOutputByValue();
31+
const structuredOutput = filterStructuredOutput(data.value);
32+
2833
const handleSubMenuClick = useCallback(
2934
(option: { label: ReactNode; value: string }, dataType?: string) => () => {
3035
// The query variable of the iteration operator can only select array type data.
@@ -54,29 +59,42 @@ export function StructuredOutputSecondaryMenu({
5459

5560
const dataType = get(value, 'type');
5661

57-
return (
58-
<li key={key} className="pl-1">
59-
<div
60-
onClick={handleSubMenuClick(nextOption, dataType)}
61-
className="hover:bg-bg-card p-1 text-text-primary rounded-sm flex justify-between"
62-
>
63-
{key}
64-
<span className="text-text-secondary">{dataType}</span>
65-
</div>
66-
{dataType === JsonSchemaDataType.Object &&
67-
renderAgentStructuredOutput(value, nextOption)}
68-
</li>
69-
);
62+
if (
63+
!type ||
64+
(type &&
65+
(dataType === type ||
66+
hasSpecificTypeChild(value ?? {}, type)))
67+
) {
68+
return (
69+
<li key={key} className="pl-1">
70+
<div
71+
onClick={handleSubMenuClick(nextOption, dataType)}
72+
className="hover:bg-bg-card p-1 text-text-primary rounded-sm flex justify-between"
73+
>
74+
{key}
75+
<span className="text-text-secondary">{dataType}</span>
76+
</div>
77+
{dataType === JsonSchemaDataType.Object &&
78+
renderAgentStructuredOutput(value, nextOption)}
79+
</li>
80+
);
81+
}
82+
83+
return null;
7084
})}
7185
</ul>
7286
);
7387
}
7488

7589
return <div></div>;
7690
},
77-
[handleSubMenuClick],
91+
[handleSubMenuClick, type],
7892
);
7993

94+
if (!hasJsonSchemaChild(structuredOutput)) {
95+
return null;
96+
}
97+
8098
return (
8199
<HoverCard key={data.value} openDelay={100} closeDelay={100}>
82100
<HoverCardTrigger asChild>
@@ -96,7 +114,7 @@ export function StructuredOutputSecondaryMenu({
96114
>
97115
<section className="p-2">
98116
<div className="p-1">{data?.parentLabel} structured output:</div>
99-
{renderAgentStructuredOutput(filteredStructuredOutput, data)}
117+
{renderAgentStructuredOutput(structuredOutput, data)}
100118
</section>
101119
</HoverCardContent>
102120
</HoverCard>

web/src/pages/agent/hooks/use-build-structured-output.ts

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { get } from 'lodash';
22
import { ReactNode, useCallback } from 'react';
33
import { AgentStructuredOutputField, Operator } from '../constant';
44
import useGraphStore from '../store';
5-
import { filterAgentStructuredOutput } from '../utils/filter-agent-structured-output';
65

76
function getNodeId(value: string) {
87
return value.split('@').at(0);
@@ -25,28 +24,23 @@ export function useShowSecondaryMenu() {
2524
return showSecondaryMenu;
2625
}
2726

28-
export function useFilterStructuredOutputByValue() {
27+
export function useGetStructuredOutputByValue() {
2928
const { getNode } = useGraphStore((state) => state);
3029

31-
const filterStructuredOutput = useCallback(
32-
(value: string, type?: string) => {
30+
const getStructuredOutput = useCallback(
31+
(value: string) => {
3332
const node = getNode(getNodeId(value));
3433
const structuredOutput = get(
3534
node,
3635
`data.form.outputs.${AgentStructuredOutputField}`,
3736
);
3837

39-
const filteredStructuredOutput = filterAgentStructuredOutput(
40-
structuredOutput,
41-
type,
42-
);
43-
44-
return filteredStructuredOutput;
38+
return structuredOutput;
4539
},
4640
[getNode],
4741
);
4842

49-
return filterStructuredOutput;
43+
return getStructuredOutput;
5044
}
5145

5246
export function useFindAgentStructuredOutputLabel() {

web/src/pages/agent/utils/filter-agent-structured-output.ts

Lines changed: 0 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -2,64 +2,6 @@ import { JSONSchema } from '@/components/jsonjoy-builder';
22
import { get, isPlainObject } from 'lodash';
33
import { JsonSchemaDataType } from '../constant';
44

5-
// Loop operators can only accept variables of type list.
6-
7-
// Recursively traverse the JSON schema, keeping attributes with type "array" and discarding others.
8-
9-
export function filterLoopOperatorInput(
10-
structuredOutput: JSONSchema,
11-
type: string,
12-
path = [],
13-
) {
14-
if (typeof structuredOutput === 'boolean') {
15-
return structuredOutput;
16-
}
17-
if (
18-
structuredOutput.properties &&
19-
isPlainObject(structuredOutput.properties)
20-
) {
21-
const properties = Object.entries({
22-
...structuredOutput.properties,
23-
}).reduce(
24-
(pre, [key, value]) => {
25-
if (
26-
typeof value !== 'boolean' &&
27-
(value.type === type || hasArrayChild(value))
28-
) {
29-
pre[key] = filterLoopOperatorInput(value, type, path);
30-
}
31-
return pre;
32-
},
33-
{} as Record<string, JSONSchema>,
34-
);
35-
36-
return { ...structuredOutput, properties };
37-
}
38-
39-
return structuredOutput;
40-
}
41-
42-
export function filterAgentStructuredOutput(
43-
structuredOutput: JSONSchema,
44-
type?: string,
45-
) {
46-
if (typeof structuredOutput === 'boolean') {
47-
return structuredOutput;
48-
}
49-
if (
50-
structuredOutput.properties &&
51-
isPlainObject(structuredOutput.properties)
52-
) {
53-
if (type) {
54-
return filterLoopOperatorInput(structuredOutput, type);
55-
}
56-
57-
return structuredOutput;
58-
}
59-
60-
return structuredOutput;
61-
}
62-
635
export function hasSpecificTypeChild(
646
data: Record<string, any> | Array<any>,
657
type: string,

0 commit comments

Comments
 (0)