|
1 | 1 | import { type TModelFamily, UITarsModelVersion } from '@midscene/shared/env'; |
2 | 2 | import { assert } from '@midscene/shared/utils'; |
3 | | -import { jsonrepair } from 'jsonrepair'; |
4 | | -import { |
5 | | - extractJSONFromCodeBlock, |
6 | | - safeParseJson, |
7 | | -} from '../../service-caller/json'; |
| 3 | +import { parseModelResponseJson } from '../../service-caller/json'; |
8 | 4 | import { |
9 | 5 | type LocateResultValue, |
10 | 6 | unwrapCoordinateListLikeInput, |
11 | 7 | } from '../../shared/model-locate-result'; |
12 | | -import type { |
13 | | - JsonParserContext, |
14 | | - JsonParserSource, |
15 | | - ModelAdapterDefinition, |
16 | | -} from '../types'; |
| 8 | +import type { ModelAdapterDefinition } from '../types'; |
17 | 9 | import { uiTarsPlanning } from './planning'; |
18 | 10 |
|
19 | 11 | const defaultVlmUiTarsReplanningCycleLimit = 40; |
20 | 12 |
|
21 | | -function normalizeJsonObject( |
22 | | - obj: any, |
23 | | - context: Pick<JsonParserContext, 'preserveStringValueKeys'> = {}, |
24 | | -): any { |
25 | | - if (obj === null || obj === undefined) { |
26 | | - return obj; |
27 | | - } |
28 | | - |
29 | | - if (Array.isArray(obj)) { |
30 | | - return obj.map((item) => normalizeJsonObject(item, context)); |
31 | | - } |
32 | | - |
33 | | - if (typeof obj === 'object') { |
34 | | - const normalized: any = {}; |
35 | | - for (const [key, value] of Object.entries(obj)) { |
36 | | - const trimmedKey = key.trim(); |
37 | | - const preserveStringValue = |
38 | | - context.preserveStringValueKeys?.includes(trimmedKey) ?? false; |
39 | | - const normalizedValue = |
40 | | - typeof value === 'string' |
41 | | - ? preserveStringValue |
42 | | - ? value |
43 | | - : value.trim() |
44 | | - : normalizeJsonObject(value, context); |
45 | | - normalized[trimmedKey] = normalizedValue; |
46 | | - } |
47 | | - return normalized; |
48 | | - } |
49 | | - |
50 | | - return typeof obj === 'string' ? obj.trim() : obj; |
51 | | -} |
52 | | - |
53 | | -function shouldRepairUiTarsLocateJson(source: JsonParserSource) { |
54 | | - return ( |
55 | | - source === 'locate' || |
56 | | - source === 'section-locator' || |
57 | | - source === 'planning-action-param' |
58 | | - ); |
59 | | -} |
60 | | - |
61 | | -function preprocessUiTarsLocateJson(input: string) { |
62 | | - if (input.includes('bbox')) { |
63 | | - while (/\d+\s+\d+/.test(input)) { |
64 | | - input = input.replace(/(\d+)\s+(\d+)/g, '$1,$2'); |
65 | | - } |
66 | | - } |
67 | | - return input; |
68 | | -} |
69 | | - |
70 | | -const uiTarsJsonParser: ModelAdapterDefinition['jsonParser'] = ( |
71 | | - raw, |
72 | | - context = { source: 'generic-object' }, |
73 | | -) => { |
74 | | - const { source } = context; |
75 | | - try { |
76 | | - return safeParseJson(raw, context); |
77 | | - } catch (firstError) { |
78 | | - if (!shouldRepairUiTarsLocateJson(source)) { |
79 | | - throw firstError; |
80 | | - } |
81 | | - |
82 | | - const jsonString = preprocessUiTarsLocateJson( |
83 | | - extractJSONFromCodeBlock(raw), |
84 | | - ); |
85 | | - try { |
86 | | - return normalizeJsonObject(JSON.parse(jsonrepair(jsonString)), context); |
87 | | - } catch (error) { |
88 | | - throw Error( |
89 | | - `failed to parse LLM response into JSON. Error - ${String( |
90 | | - error ?? firstError ?? 'unknown error', |
91 | | - )}. Response - \n ${raw}`, |
92 | | - ); |
93 | | - } |
94 | | - } |
95 | | -}; |
96 | | - |
97 | 13 | // UI-TARS has not received active updates for a long time, so this parser is |
98 | 14 | // intentionally kept separate from Doubao even though the current logic is the |
99 | 15 | // same. This avoids coupling UI-TARS behavior to future Doubao adapter changes. |
@@ -167,7 +83,7 @@ function createUiTarsAdapter( |
167 | 83 | uiTarsModelVersion: UITarsModelVersion, |
168 | 84 | ): ModelAdapterDefinition { |
169 | 85 | return { |
170 | | - jsonParser: uiTarsJsonParser, |
| 86 | + jsonParser: parseModelResponseJson, |
171 | 87 | chatCompletion: { |
172 | 88 | unsupportedUserConfig: [ |
173 | 89 | 'reasoningEnabled', |
|
0 commit comments