@larkup/workspace-server 0.2.14 → 0.2.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-types/app/api/chat/route.d.ts.map +1 -1
- package/dist-types/app/api/chat/tools.d.ts.map +1 -1
- package/dist-types/lib/chat/analysis-presentation.d.ts +34 -0
- package/dist-types/lib/chat/analysis-presentation.d.ts.map +1 -0
- package/dist-types/lib/chat/conversation-memory.d.ts +9 -0
- package/dist-types/lib/chat/conversation-memory.d.ts.map +1 -1
- package/dist-types/lib/chat/tabular-query-plan.d.ts +3 -1
- package/dist-types/lib/chat/tabular-query-plan.d.ts.map +1 -1
- package/dist-types/lib/chat/tabular-routing.d.ts +8 -0
- package/dist-types/lib/chat/tabular-routing.d.ts.map +1 -1
- package/dist-types/lib/chat/tabular-visualization.d.ts.map +1 -1
- package/dist-types/lib/chat/tool-context.d.ts.map +1 -1
- package/package.json +8 -3
- package/src/app/api/chat/route.ts +41 -1
- package/src/app/api/chat/tools.ts +24 -7
- package/src/lib/chat/analysis-presentation.ts +217 -0
- package/src/lib/chat/conversation-memory.ts +70 -4
- package/src/lib/chat/tabular-query-plan.ts +22 -3
- package/src/lib/chat/tabular-routing.ts +30 -2
- package/src/lib/chat/tabular-visualization.ts +9 -3
- package/src/lib/chat/tool-context.ts +18 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../src/app/api/chat/route.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../src/app/api/chat/route.ts"],"names":[],"mappings":"AAuHA,eAAO,MAAM,WAAW,MAAM,CAAC;AAquB/B,wBAAsB,IAAI,CAAC,GAAG,EAAE,OAAO,qBA+/CtC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../../../src/app/api/chat/tools.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../../../src/app/api/chat/tools.ts"],"names":[],"mappings":"AAoXA,wBAAsB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0I7F;AAyVD,wBAAsB,YAAY,CAAC,OAAO,EAAE;IAC1C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,GAAG,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iGAAiG;IACjG,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,6EAA6E;IAC7E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wEAAwE;IACxE,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,gGAAgG;IAChG,iBAAiB,CAAC,EAAE;QAClB,OAAO,EAAE,MAAM,EAAE,CAAC;QAClB,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;KACtC,CAAC;IACF,oFAAoF;IACpF,wBAAwB,CAAC,EAAE;QACzB,OAAO,EAAE,MAAM,EAAE,CAAC;QAClB,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;KACtC,CAAC;IACF,2FAA2F;IAC3F,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;;;;;;GAmnFA"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export interface AnalysisTablePresentation {
|
|
2
|
+
kind: 'table';
|
|
3
|
+
title: string;
|
|
4
|
+
summary?: string;
|
|
5
|
+
columns: string[];
|
|
6
|
+
rows: Array<Record<string, string | number | boolean | null>>;
|
|
7
|
+
totalItems: number;
|
|
8
|
+
requestedItems?: number;
|
|
9
|
+
complete: boolean;
|
|
10
|
+
fileName?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface AnalysisDocumentPage {
|
|
13
|
+
title?: string;
|
|
14
|
+
content: string;
|
|
15
|
+
}
|
|
16
|
+
export interface AnalysisDocumentPresentation {
|
|
17
|
+
kind: 'document';
|
|
18
|
+
title: string;
|
|
19
|
+
summary?: string;
|
|
20
|
+
pages: AnalysisDocumentPage[];
|
|
21
|
+
totalItems: number;
|
|
22
|
+
requestedItems?: number;
|
|
23
|
+
complete: boolean;
|
|
24
|
+
fileName?: string;
|
|
25
|
+
}
|
|
26
|
+
export type AnalysisPresentation = AnalysisTablePresentation | AnalysisDocumentPresentation;
|
|
27
|
+
export declare function extractAnalysisPresentation(stdout: string): {
|
|
28
|
+
stdout: string;
|
|
29
|
+
presentation?: AnalysisPresentation;
|
|
30
|
+
};
|
|
31
|
+
/** Injected before generated Python so long results have one stable UI contract. */
|
|
32
|
+
export declare const ANALYSIS_PRESENTATION_PYTHON: string;
|
|
33
|
+
export declare function withAnalysisPresentationHelpers(code: string, requestedItems?: number): string;
|
|
34
|
+
//# sourceMappingURL=analysis-presentation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analysis-presentation.d.ts","sourceRoot":"","sources":["../../../src/lib/chat/analysis-presentation.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,yBAAyB;IACxC,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC;IAC9D,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,oBAAoB;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,4BAA4B;IAC3C,IAAI,EAAE,UAAU,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,oBAAoB,EAAE,CAAC;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,MAAM,oBAAoB,GAAG,yBAAyB,GAAG,4BAA4B,CAAC;AAkH5F,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,MAAM,GAAG;IAC3D,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,oBAAoB,CAAC;CACrC,CAqBA;AAED,oFAAoF;AACpF,eAAO,MAAM,4BAA4B,QAmCxC,CAAC;AAEF,wBAAgB,+BAA+B,CAAC,IAAI,EAAE,MAAM,EAAE,cAAc,CAAC,EAAE,MAAM,GAAG,MAAM,CAG7F"}
|
|
@@ -21,6 +21,13 @@ export type ReusableTabularEvidence = {
|
|
|
21
21
|
rows: Array<Record<string, unknown>>;
|
|
22
22
|
totalRows: number;
|
|
23
23
|
};
|
|
24
|
+
export type AnalysisArtifactSummary = {
|
|
25
|
+
kind: 'table' | 'document';
|
|
26
|
+
title: string;
|
|
27
|
+
totalItems: number;
|
|
28
|
+
requestedItems?: number;
|
|
29
|
+
complete: boolean;
|
|
30
|
+
};
|
|
24
31
|
/** Only reuse table evidence while its source is still present in the live data store. */
|
|
25
32
|
export declare function tabularEvidenceDatasetIsAvailable(evidence: ReusableTabularEvidence | undefined, availableDatasetIds: ReadonlySet<string>): boolean;
|
|
26
33
|
export type ConversationEvidence = {
|
|
@@ -42,6 +49,8 @@ export type ConversationEvidence = {
|
|
|
42
49
|
* answers keep their specialized verification paths and are never reused here.
|
|
43
50
|
*/
|
|
44
51
|
export declare function findImmediateExactGroundedAnswer(messages: readonly ChatMessage[], currentQuestion: string): ExactGroundedAnswer | undefined;
|
|
52
|
+
/** Read only completion metadata; the full paged artifact remains in the UI message. */
|
|
53
|
+
export declare function extractLatestAnalysisArtifact(messages: readonly ChatMessage[]): AnalysisArtifactSummary | undefined;
|
|
45
54
|
/** Keep the complete bounded query result for an immediate export follow-up. */
|
|
46
55
|
export declare function extractLatestTabularExportResult(messages: readonly ChatMessage[]): ReusableTabularEvidence | undefined;
|
|
47
56
|
/** Keep only enough exact table evidence for a direct follow-up, never raw sheets. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"conversation-memory.d.ts","sourceRoot":"","sources":["../../../src/lib/chat/conversation-memory.ts"],"names":[],"mappings":"AAeA,KAAK,WAAW,GAAG;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,sBAAsB,EAAE,MAAM,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACrC,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,0FAA0F;AAC1F,wBAAgB,iCAAiC,CAC/C,QAAQ,EAAE,uBAAuB,GAAG,SAAS,EAC7C,mBAAmB,EAAE,WAAW,CAAC,MAAM,CAAC,GACvC,OAAO,CAET;AAED,MAAM,MAAM,oBAAoB,GAAG;IACjC,OAAO,EAAE,KAAK,CAAC;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAClD,MAAM,EAAE,qBAAqB,EAAE,CAAC;IAChC,yFAAyF;IACzF,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,6FAA6F;IAC7F,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,+EAA+E;IAC/E,OAAO,CAAC,EAAE,uBAAuB,CAAC;CACnC,CAAC;AAoFF;;;;GAIG;AACH,wBAAgB,gCAAgC,CAC9C,QAAQ,EAAE,SAAS,WAAW,EAAE,EAChC,eAAe,EAAE,MAAM,GACtB,mBAAmB,GAAG,SAAS,CA0DjC;
|
|
1
|
+
{"version":3,"file":"conversation-memory.d.ts","sourceRoot":"","sources":["../../../src/lib/chat/conversation-memory.ts"],"names":[],"mappings":"AAeA,KAAK,WAAW,GAAG;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,sBAAsB,EAAE,MAAM,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACrC,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,IAAI,EAAE,OAAO,GAAG,UAAU,CAAC;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF,0FAA0F;AAC1F,wBAAgB,iCAAiC,CAC/C,QAAQ,EAAE,uBAAuB,GAAG,SAAS,EAC7C,mBAAmB,EAAE,WAAW,CAAC,MAAM,CAAC,GACvC,OAAO,CAET;AAED,MAAM,MAAM,oBAAoB,GAAG;IACjC,OAAO,EAAE,KAAK,CAAC;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAClD,MAAM,EAAE,qBAAqB,EAAE,CAAC;IAChC,yFAAyF;IACzF,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,6FAA6F;IAC7F,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,+EAA+E;IAC/E,OAAO,CAAC,EAAE,uBAAuB,CAAC;CACnC,CAAC;AAoFF;;;;GAIG;AACH,wBAAgB,gCAAgC,CAC9C,QAAQ,EAAE,SAAS,WAAW,EAAE,EAChC,eAAe,EAAE,MAAM,GACtB,mBAAmB,GAAG,SAAS,CA0DjC;AAoFD,wFAAwF;AACxF,wBAAgB,6BAA6B,CAC3C,QAAQ,EAAE,SAAS,WAAW,EAAE,GAC/B,uBAAuB,GAAG,SAAS,CAyBrC;AAED,gFAAgF;AAChF,wBAAgB,gCAAgC,CAC9C,QAAQ,EAAE,SAAS,WAAW,EAAE,GAC/B,uBAAuB,GAAG,SAAS,CAoBrC;AAED,sFAAsF;AACtF,wBAAgB,iCAAiC,CAC/C,MAAM,EAAE,OAAO,EAAE,EACjB,OAAO,EAAE,MAAM,EAAE,GAChB,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAuBhC;AAYD;;;GAGG;AACH,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,SAAS,WAAW,EAAE,GAC/B,oBAAoB,CA6ItB;AAmCD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,oBAAoB,GAAG,OAAO,CAavF;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,oBAAoB,GAAG,OAAO,CAK5F;AAED,gGAAgG;AAChG,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,oBAAoB,GAC7B;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAO/E;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,oBAAoB,GAAG,OAAO,CAE/F;AAUD;;;;GAIG;AACH,wBAAgB,iCAAiC,CAC/C,eAAe,EAAE,MAAM,EACvB,gBAAgB,EAAE,MAAM,GACvB,MAAM,GAAG,SAAS,CAepB;AAED;;;;GAIG;AACH,wBAAgB,mCAAmC,CACjD,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,oBAAoB,GAC7B,MAAM,GAAG,SAAS,CAyBpB;AAED,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,oBAAoB,GAAG,MAAM,CAoCjF"}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type TabularDataset, type TabularQueryRequest } from '@larkup/core/tabular-store';
|
|
2
2
|
export type InferredTabularPlan = {
|
|
3
3
|
request: TabularQueryRequest;
|
|
4
4
|
chartTitle?: string;
|
|
5
5
|
};
|
|
6
|
+
/** Preserve an explicit long-list size instead of relying on a model default. */
|
|
7
|
+
export declare function requestedTabularResultLimit(text: string): number | undefined;
|
|
6
8
|
/**
|
|
7
9
|
* Normalize a clear calendar request from the dataset schema. It never
|
|
8
10
|
* assumes business columns or domain names: a time series is derived only
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tabular-query-plan.d.ts","sourceRoot":"","sources":["../../../src/lib/chat/tabular-query-plan.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"tabular-query-plan.d.ts","sourceRoot":"","sources":["../../../src/lib/chat/tabular-query-plan.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,mBAAmB,EACzB,MAAM,4BAA4B,CAAC;AAEpC,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,EAAE,mBAAmB,CAAC;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AA2EF,iFAAiF;AACjF,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAQ5E;AAwCD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,OAAO,EAAE,cAAc,EACvB,OAAO,EAAE,mBAAmB,GAC3B,mBAAmB,CAuErB"}
|
|
@@ -28,6 +28,14 @@ export declare function hasCompletedTabularVisualizationStep(steps: Array<{
|
|
|
28
28
|
* column names.
|
|
29
29
|
*/
|
|
30
30
|
export declare function requiresTabularSandbox(text: string): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Long per-record transformations cannot be completed safely as streamed prose:
|
|
33
|
+
* the model will eventually hit its output ceiling and a later "continue" loses
|
|
34
|
+
* global constraints. Route these through one sandbox run and a paged artifact.
|
|
35
|
+
*/
|
|
36
|
+
export declare function requestsLongAnalysisArtifact(text: string): boolean;
|
|
37
|
+
/** Preserve explicit batch sizes for sandbox artifacts (larger than chat prose limits). */
|
|
38
|
+
export declare function requestedLongAnalysisItemCount(text: string): number | undefined;
|
|
31
39
|
/**
|
|
32
40
|
* A workspace can contain spreadsheets alongside PDFs, media, and notes.
|
|
33
41
|
* Only force the table path when the wording actually asks for tabular facts;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tabular-routing.d.ts","sourceRoot":"","sources":["../../../src/lib/chat/tabular-routing.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE;IACzC,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;;;;;;;;;EAqBA;AAED,gFAAgF;AAChF,wBAAgB,oCAAoC,CAClD,KAAK,EAAE,KAAK,CAAC;IACX,WAAW,CAAC,EAAE,SAAS,OAAO,EAAE,CAAC;CAClC,CAAC,WAgBH;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,
|
|
1
|
+
{"version":3,"file":"tabular-routing.d.ts","sourceRoot":"","sources":["../../../src/lib/chat/tabular-routing.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE;IACzC,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;;;;;;;;;EAqBA;AAED,gFAAgF;AAChF,wBAAgB,oCAAoC,CAClD,KAAK,EAAE,KAAK,CAAC;IACX,WAAW,CAAC,EAAE,SAAS,OAAO,EAAE,CAAC;CAClC,CAAC,WAgBH;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAM5D;AAED;;;;GAIG;AACH,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAUlE;AAED,2FAA2F;AAC3F,wBAAgB,8BAA8B,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAM/E;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE;IAC7C,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB,WA8CA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tabular-visualization.d.ts","sourceRoot":"","sources":["../../../src/lib/chat/tabular-visualization.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,kBAAkB,GAAG;IAC/B,SAAS,EAAE,KAAK,GAAG,MAAM,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC7C,UAAU,EAAE,OAAO,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,KAAK,0BAA0B,GAAG;IAChC,aAAa,CAAC,EAAE,kBAAkB,CAAC;IACnC,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC;AAoBF,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,
|
|
1
|
+
{"version":3,"file":"tabular-visualization.d.ts","sourceRoot":"","sources":["../../../src/lib/chat/tabular-visualization.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,kBAAkB,GAAG;IAC/B,SAAS,EAAE,KAAK,GAAG,MAAM,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC7C,UAAU,EAAE,OAAO,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,KAAK,0BAA0B,GAAG;IAChC,aAAa,CAAC,EAAE,kBAAkB,CAAC;IACnC,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC;AAoBF,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAU3D;AA+BD,0EAA0E;AAC1E,wBAAgB,0BAA0B,CACxC,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,MAAM,EAAE;IAAE,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAA;CAAE,GAC7D,kBAAkB,GAAG,SAAS,CAsDhC;AAED;;;;GAIG;AACH,wBAAgB,gCAAgC,CAAC,MAAM,EAAE,0BAA0B,GAAG,MAAM,CA4B3F"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-context.d.ts","sourceRoot":"","sources":["../../../src/lib/chat/tool-context.ts"],"names":[],"mappings":"AAUA;;;;;;GAMG;AACH,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAmBpE;AAED,0EAA0E;AAC1E,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAgB/D;AAED,kFAAkF;AAClF,wBAAgB,gCAAgC,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAmBxE;AAmBD,oEAAoE;AACpE,wBAAgB,kCAAkC,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAuB7F;AAED;;;;GAIG;AACH,wBAAgB,iCAAiC,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,EAAE,CA+D1E;AAED;;;;GAIG;AACH,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,EAAE,CAyDrE;AAED;;;;;GAKG;AACH,wBAAgB,kCAAkC,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CA4KrF;AAED;;;;;;GAMG;AACH,wBAAgB,8BAA8B,CAC5C,KAAK,EAAE,OAAO,EACd,aAAa,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,SAAS,CAAC,EAAE,OAAO,CAAA;CAAE,GACtD,MAAM,GAAG,SAAS,CAwGpB;AAED,sFAAsF;AACtF,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,OAAO,EACd,aAAa,EAAE,2BAA2B,GACzC,MAAM,GAAG,SAAS,CAyHpB;AAED,8FAA8F;AAC9F,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,SAAK,GAAG,MAAM,GAAG,SAAS,CAqE/F;AAED,8EAA8E;AAC9E,wBAAgB,+BAA+B,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CA2BvE;AAED,sGAAsG;AACtG,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CA0D/F;AAYD;;;GAGG;AACH,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CA+IjG;AAED,gGAAgG;AAChG,wBAAgB,2BAA2B,CAAC,YAAY,EAAE,MAAM,mCAwC/D;
|
|
1
|
+
{"version":3,"file":"tool-context.d.ts","sourceRoot":"","sources":["../../../src/lib/chat/tool-context.ts"],"names":[],"mappings":"AAUA;;;;;;GAMG;AACH,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAmBpE;AAED,0EAA0E;AAC1E,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAgB/D;AAED,kFAAkF;AAClF,wBAAgB,gCAAgC,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAmBxE;AAmBD,oEAAoE;AACpE,wBAAgB,kCAAkC,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAuB7F;AAED;;;;GAIG;AACH,wBAAgB,iCAAiC,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,EAAE,CA+D1E;AAED;;;;GAIG;AACH,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,EAAE,CAyDrE;AAED;;;;;GAKG;AACH,wBAAgB,kCAAkC,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CA4KrF;AAED;;;;;;GAMG;AACH,wBAAgB,8BAA8B,CAC5C,KAAK,EAAE,OAAO,EACd,aAAa,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,SAAS,CAAC,EAAE,OAAO,CAAA;CAAE,GACtD,MAAM,GAAG,SAAS,CAwGpB;AAED,sFAAsF;AACtF,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,OAAO,EACd,aAAa,EAAE,2BAA2B,GACzC,MAAM,GAAG,SAAS,CAyHpB;AAED,8FAA8F;AAC9F,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,SAAK,GAAG,MAAM,GAAG,SAAS,CAqE/F;AAED,8EAA8E;AAC9E,wBAAgB,+BAA+B,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CA2BvE;AAED,sGAAsG;AACtG,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CA0D/F;AAYD;;;GAGG;AACH,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CA+IjG;AAED,gGAAgG;AAChG,wBAAgB,2BAA2B,CAAC,YAAY,EAAE,MAAM,mCAwC/D;AAmMD;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,SAAS,GAAG,EAAE,GAAG,GAAG,EAAE,CAapE;AAED,uFAAuF;AACvF,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,SAAS,GAAG,EAAE,GAAG,GAAG,EAAE,CAiB1E;AACD,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,4CAA4C,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@larkup/workspace-server",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.16",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"src",
|
|
@@ -53,6 +53,11 @@
|
|
|
53
53
|
"import": "./src/lib/chat/assistant-source-scope.ts",
|
|
54
54
|
"default": "./src/lib/chat/assistant-source-scope.ts"
|
|
55
55
|
},
|
|
56
|
+
"./lib/chat/analysis-presentation": {
|
|
57
|
+
"types": "./dist-types/lib/chat/analysis-presentation.d.ts",
|
|
58
|
+
"import": "./src/lib/chat/analysis-presentation.ts",
|
|
59
|
+
"default": "./src/lib/chat/analysis-presentation.ts"
|
|
60
|
+
},
|
|
56
61
|
"./lib/chat/conversation-memory": {
|
|
57
62
|
"types": "./dist-types/lib/chat/conversation-memory.d.ts",
|
|
58
63
|
"import": "./src/lib/chat/conversation-memory.ts",
|
|
@@ -326,8 +331,8 @@
|
|
|
326
331
|
"mammoth": "^1.12.0",
|
|
327
332
|
"@larkup/marketplace": "0.3.11",
|
|
328
333
|
"@larkup/sandbox": "0.1.8",
|
|
329
|
-
"@larkup/
|
|
330
|
-
"@larkup/
|
|
334
|
+
"@larkup/vector-stores": "0.1.27",
|
|
335
|
+
"@larkup/scraper": "0.2.2"
|
|
331
336
|
},
|
|
332
337
|
"devDependencies": {
|
|
333
338
|
"typescript": "^7.0.2",
|
|
@@ -44,6 +44,7 @@ import { collectExhaustiveVideoEvidencePages } from '../../../lib/chat/video-rag
|
|
|
44
44
|
import { reconcileVideoInvestigation } from '../../../lib/chat/video-query-planning';
|
|
45
45
|
import {
|
|
46
46
|
extractConversationEvidence,
|
|
47
|
+
extractLatestAnalysisArtifact,
|
|
47
48
|
extractLatestTabularExportResult,
|
|
48
49
|
contextualizeKnowledgeFollowUpQuery,
|
|
49
50
|
formatConversationEvidence,
|
|
@@ -78,6 +79,7 @@ import {
|
|
|
78
79
|
} from '../../../lib/chat/tabular-visualization';
|
|
79
80
|
import {
|
|
80
81
|
isLikelyTabularQuestion,
|
|
82
|
+
requestsLongAnalysisArtifact,
|
|
81
83
|
requiresTabularSandbox,
|
|
82
84
|
tabularToolsForStep,
|
|
83
85
|
} from '../../../lib/chat/tabular-routing';
|
|
@@ -176,6 +178,10 @@ ${PERSONALIZED_RESPONSE_STYLE}
|
|
|
176
178
|
|
|
177
179
|
Keep the answer brief and direct: give the answer first, then only the essential context. Do not repeat tool output, dump rows, or add a table unless the user asks to see data. Use a short list only when it improves clarity.
|
|
178
180
|
|
|
181
|
+
When a tabular tool returns a requested long list, the structured table is the complete visible answer. Briefly state what it contains and its coverage; never stream the rows again as prose or Markdown.
|
|
182
|
+
|
|
183
|
+
When the user asks for a transformation repeated across many records (for example matches, recommendations, explanations, profiles, or summaries for the first 25 or for every item), use the sandbox analysis tool. It must prepare the whole requested result in one run with larkup_table or larkup_document. Treat that paged artifact as the answer: state its coverage briefly and never repeat its entries in chat prose. Do not ask the user to say "continue" for an artifact result.
|
|
184
|
+
|
|
179
185
|
When the user asks for an Excel, CSV, or PDF export, use createDataExport. Never claim that a file was created and never write a Markdown download link unless the tool returned a real file artifact.
|
|
180
186
|
|
|
181
187
|
When the user asks for every item and the media evidence marks its continuation as exhaustive, include every returned item in chronological order. In that case completeness overrides brevity; deduplicate wording but do not summarize items away.
|
|
@@ -1197,6 +1203,19 @@ ${fieldLines}`;
|
|
|
1197
1203
|
? precedingNumberedDocumentReference(messagesToProcess)
|
|
1198
1204
|
: undefined;
|
|
1199
1205
|
const reusableEvidence = extractConversationEvidence(evidenceMessages);
|
|
1206
|
+
const latestAnalysisArtifact = extractLatestAnalysisArtifact(evidenceMessages);
|
|
1207
|
+
if (
|
|
1208
|
+
latestAnalysisArtifact?.complete &&
|
|
1209
|
+
/^(?:continue|go\s+on|keep\s+going|show\s+the\s+rest|more|تابع|أكمل|اكمل)[.!\s]*$/iu.test(
|
|
1210
|
+
userText.trim(),
|
|
1211
|
+
)
|
|
1212
|
+
) {
|
|
1213
|
+
const count = latestAnalysisArtifact.requestedItems ?? latestAnalysisArtifact.totalItems;
|
|
1214
|
+
return directAssistantTextResponse(
|
|
1215
|
+
messages,
|
|
1216
|
+
`The complete ${count.toLocaleString()}-item result is already prepared in “${latestAnalysisArtifact.title}”. Use its page controls, search, or download button to inspect the rest without restarting the analysis.`,
|
|
1217
|
+
);
|
|
1218
|
+
}
|
|
1200
1219
|
const latestTabularExportResult = extractLatestTabularExportResult(evidenceMessages);
|
|
1201
1220
|
// This compact string is used by retrieval only, not appended to model
|
|
1202
1221
|
// history. It preserves the immediate topic while keeping the model context
|
|
@@ -1217,8 +1236,11 @@ ${fieldLines}`;
|
|
|
1217
1236
|
columnNames: tabularColumnNames,
|
|
1218
1237
|
datasetNames: tabularDatasetNames,
|
|
1219
1238
|
});
|
|
1239
|
+
const requestsLongArtifact = requestsLongAnalysisArtifact(userText);
|
|
1240
|
+
const requiresCorpusSandboxAnalysis = !tabularQuestion && requestsLongArtifact;
|
|
1220
1241
|
const requiresSandboxAnalysis =
|
|
1221
|
-
hasTabularData && tabularQuestion && requiresTabularSandbox(userText)
|
|
1242
|
+
(hasTabularData && tabularQuestion && requiresTabularSandbox(userText)) ||
|
|
1243
|
+
requiresCorpusSandboxAnalysis;
|
|
1222
1244
|
const isExplicitVideoCorrection =
|
|
1223
1245
|
/^(?:no|nah),\s+|\b(?:that(?:'s| is) (?:wrong|incorrect)|correction\s*:|actually\s*,|instead\s*,|should be|not .{0,80}\bbut|i meant)\b/i.test(
|
|
1224
1246
|
userText,
|
|
@@ -1318,6 +1340,9 @@ ${fieldLines}`;
|
|
|
1318
1340
|
createDataExport: allTools.createDataExport,
|
|
1319
1341
|
generateVisualization: allTools.generateVisualization,
|
|
1320
1342
|
...(allTools.executeAnalysis ? { executeAnalysis: allTools.executeAnalysis } : {}),
|
|
1343
|
+
...(allTools.analyzeCorpusWithCode
|
|
1344
|
+
? { analyzeCorpusWithCode: allTools.analyzeCorpusWithCode }
|
|
1345
|
+
: {}),
|
|
1321
1346
|
inspectPdfPages: allTools.inspectPdfPages,
|
|
1322
1347
|
analyzePdfPages: allTools.analyzePdfPages,
|
|
1323
1348
|
analyzeImageDeeply: allTools.analyzeImageDeeply,
|
|
@@ -1340,6 +1365,7 @@ ${fieldLines}`;
|
|
|
1340
1365
|
userText.trim() &&
|
|
1341
1366
|
!isGreeting &&
|
|
1342
1367
|
!docSessionId &&
|
|
1368
|
+
!(requiresCorpusSandboxAnalysis && builtInTools.analyzeCorpusWithCode) &&
|
|
1343
1369
|
(!hasTabularData || !tabularQuestion) &&
|
|
1344
1370
|
builtInTools.searchKnowledgeBase,
|
|
1345
1371
|
);
|
|
@@ -2106,6 +2132,20 @@ ${fieldLines}`;
|
|
|
2106
2132
|
};
|
|
2107
2133
|
}
|
|
2108
2134
|
|
|
2135
|
+
if (requiresCorpusSandboxAnalysis && builtInTools.analyzeCorpusWithCode) {
|
|
2136
|
+
return stepNumber === 0
|
|
2137
|
+
? {
|
|
2138
|
+
toolChoice: { type: 'tool' as const, toolName: 'analyzeCorpusWithCode' },
|
|
2139
|
+
activeTools: ['analyzeCorpusWithCode'],
|
|
2140
|
+
messages: compactToolContextForModel(messages),
|
|
2141
|
+
}
|
|
2142
|
+
: {
|
|
2143
|
+
toolChoice: 'none' as const,
|
|
2144
|
+
activeTools: [],
|
|
2145
|
+
messages: withFinalAnswerNudge(compactToolContextForModel(messages)),
|
|
2146
|
+
};
|
|
2147
|
+
}
|
|
2148
|
+
|
|
2109
2149
|
if (tabularFollowUp) {
|
|
2110
2150
|
if (dataExportFormat && stepNumber === 1 && builtInTools.createDataExport) {
|
|
2111
2151
|
return {
|
|
@@ -96,6 +96,7 @@ import { normalizeChartConfigWithEvidence } from '@larkup/core/chart-config';
|
|
|
96
96
|
import { createDataExport, selectDataExportSource } from '@larkup/core/data-export';
|
|
97
97
|
import { assistantSourceScopeFingerprint } from '../../../lib/chat/assistant-source-scope';
|
|
98
98
|
import { inferTabularPlan } from '../../../lib/chat/tabular-query-plan';
|
|
99
|
+
import { requestedLongAnalysisItemCount } from '../../../lib/chat/tabular-routing';
|
|
99
100
|
import {
|
|
100
101
|
resolveTabularDatasetForQuestion,
|
|
101
102
|
selectTabularDatasetForQuestion,
|
|
@@ -104,6 +105,10 @@ import {
|
|
|
104
105
|
createTabularSandboxFiles,
|
|
105
106
|
remapLegacyMultiDatasetReads,
|
|
106
107
|
} from '../../../lib/chat/tabular-sandbox-files';
|
|
108
|
+
import {
|
|
109
|
+
extractAnalysisPresentation,
|
|
110
|
+
withAnalysisPresentationHelpers,
|
|
111
|
+
} from '../../../lib/chat/analysis-presentation';
|
|
107
112
|
import { leadingMediaAssetId } from '../../../lib/chat/media-retrieval-routing';
|
|
108
113
|
import { findIndexedImageSource } from '../../../lib/chat/visual-routing';
|
|
109
114
|
import { indexedVideoEvidenceIsSufficient } from '../../../lib/chat/video-rag-routing';
|
|
@@ -2759,12 +2764,12 @@ export async function getChatTools(context: {
|
|
|
2759
2764
|
|
|
2760
2765
|
executeAnalysis: tool({
|
|
2761
2766
|
description:
|
|
2762
|
-
'Execute Python code in a secure sandbox for deep data analysis. Use this for
|
|
2767
|
+
'Execute Python code in a secure sandbox for deep data analysis and complete multi-record results. Use this for correlations, matching or recommendations across many rows, constrained ranking, transformations, clustering, and custom visualizations. For a result containing many records, do not print the records as prose. Call larkup_table(title, rows, columns, summary, requested_items, file_name) exactly once so Chat renders the complete result with pagination and download. For long narrative sections, call larkup_document(title, pages, summary, requested_items, file_name), where pages is a list of strings or {title, content} objects. Produce every requested item in this one execution, validate counts and global constraints in Python, and keep stdout to a short diagnostic. The code has pandas, numpy, matplotlib, scipy, scikit-learn, and seaborn. Use plt.show() for charts. The sandbox already configures headless plotting and cache directories.',
|
|
2763
2768
|
inputSchema: z.object({
|
|
2764
2769
|
code: z
|
|
2765
2770
|
.string()
|
|
2766
2771
|
.describe(
|
|
2767
|
-
'Python code to execute. Has pandas (2.0+), numpy, matplotlib, scipy, sklearn, seaborn
|
|
2772
|
+
'Python code to execute. For a list/table longer than a short chat answer, finish with larkup_table(...). For long prose, finish with larkup_document(...). These helpers are preloaded; never redefine them. requested_items must equal the user-requested top/first/all count so completeness is verified. Has pandas (2.0+), numpy, matplotlib, scipy, sklearn, seaborn. ALWAYS use "ME" instead of "M" for resample frequency. Use plt.show() for charts. Plot configuration is already prepared; do not set MPLBACKEND, MPLCONFIGDIR, or XDG_CACHE_HOME.',
|
|
2768
2773
|
),
|
|
2769
2774
|
datasetId: z
|
|
2770
2775
|
.string()
|
|
@@ -2850,6 +2855,10 @@ export async function getChatTools(context: {
|
|
|
2850
2855
|
) {
|
|
2851
2856
|
finalCode = `import os\n${finalCode}`;
|
|
2852
2857
|
}
|
|
2858
|
+
finalCode = withAnalysisPresentationHelpers(
|
|
2859
|
+
finalCode,
|
|
2860
|
+
requestedLongAnalysisItemCount(requestText ?? ''),
|
|
2861
|
+
);
|
|
2853
2862
|
|
|
2854
2863
|
let result = await sandboxManager.execute({
|
|
2855
2864
|
code: finalCode,
|
|
@@ -2880,10 +2889,12 @@ export async function getChatTools(context: {
|
|
|
2880
2889
|
});
|
|
2881
2890
|
}
|
|
2882
2891
|
|
|
2892
|
+
const extracted = extractAnalysisPresentation(result.stdout);
|
|
2883
2893
|
return {
|
|
2884
|
-
stdout:
|
|
2894
|
+
stdout: extracted.stdout.slice(0, 5000),
|
|
2885
2895
|
stderr: result.stderr.slice(0, 2000),
|
|
2886
2896
|
exitCode: result.exitCode,
|
|
2897
|
+
...(extracted.presentation ? { presentation: extracted.presentation } : {}),
|
|
2887
2898
|
artifacts: result.artifacts.map((a) => ({
|
|
2888
2899
|
name: a.name,
|
|
2889
2900
|
mimeType: a.mimeType,
|
|
@@ -3052,12 +3063,12 @@ export async function getChatTools(context: {
|
|
|
3052
3063
|
|
|
3053
3064
|
analyzeCorpusWithCode: tool({
|
|
3054
3065
|
description:
|
|
3055
|
-
'Run Python code in a sandbox with the FULL indexed corpus available as a file. Use this for
|
|
3066
|
+
'Run Python code in a sandbox with the FULL indexed corpus available as a file. Use this for complete long inventories or transformations across documents, parsing fields, grouping, detecting duplicates, and corpus-scale analysis. For many result records call larkup_table(...) exactly once; for long narrative sections call larkup_document(...) exactly once. Produce the complete requested item count in one execution instead of streaming a long answer. The corpus is available as "corpus.csv" (or "corpus.jsonl"). CSV columns: id, title, source, url, charCount, status, createdAt, content, plus metadata_* columns.',
|
|
3056
3067
|
inputSchema: z.object({
|
|
3057
3068
|
code: z
|
|
3058
3069
|
.string()
|
|
3059
3070
|
.describe(
|
|
3060
|
-
'Python code to execute. The corpus file is in the working directory. Use pandas to load it
|
|
3071
|
+
'Python code to execute. The corpus file is in the working directory. Use pandas to load it. For long structured results finish with larkup_table(...); for long prose finish with larkup_document(...). These helpers are preloaded. Set requested_items to the requested count. Has pandas, numpy, matplotlib, scipy, sklearn, seaborn. ALWAYS use "ME" instead of "M" for resample frequency.',
|
|
3061
3072
|
),
|
|
3062
3073
|
format: z
|
|
3063
3074
|
.enum(['csv', 'jsonl'])
|
|
@@ -3092,7 +3103,10 @@ export async function getChatTools(context: {
|
|
|
3092
3103
|
const fileName = format === 'jsonl' ? 'corpus.jsonl' : 'corpus.csv';
|
|
3093
3104
|
const files = [{ name: fileName, content: corpusData }];
|
|
3094
3105
|
|
|
3095
|
-
let finalCode =
|
|
3106
|
+
let finalCode = withAnalysisPresentationHelpers(
|
|
3107
|
+
code,
|
|
3108
|
+
requestedLongAnalysisItemCount(requestText ?? ''),
|
|
3109
|
+
);
|
|
3096
3110
|
let result = await sandboxManager.execute({
|
|
3097
3111
|
code: finalCode,
|
|
3098
3112
|
language: 'python',
|
|
@@ -3116,10 +3130,12 @@ export async function getChatTools(context: {
|
|
|
3116
3130
|
});
|
|
3117
3131
|
}
|
|
3118
3132
|
|
|
3133
|
+
const extracted = extractAnalysisPresentation(result.stdout);
|
|
3119
3134
|
return {
|
|
3120
|
-
stdout:
|
|
3135
|
+
stdout: extracted.stdout.slice(0, 5000),
|
|
3121
3136
|
stderr: result.stderr.slice(0, 2000),
|
|
3122
3137
|
exitCode: result.exitCode,
|
|
3138
|
+
...(extracted.presentation ? { presentation: extracted.presentation } : {}),
|
|
3123
3139
|
artifacts: result.artifacts.map((a) => ({
|
|
3124
3140
|
name: a.name,
|
|
3125
3141
|
mimeType: a.mimeType,
|
|
@@ -3389,6 +3405,7 @@ export async function getChatTools(context: {
|
|
|
3389
3405
|
'createDataExport',
|
|
3390
3406
|
'generateVisualization',
|
|
3391
3407
|
'executeAnalysis',
|
|
3408
|
+
'analyzeCorpusWithCode',
|
|
3392
3409
|
// PDF/image hits can require one visual pass after retrieval. This is
|
|
3393
3410
|
// available only to a follow-up tool step, never auto-run on chat.
|
|
3394
3411
|
'inspectPdfPages',
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
const PRESENTATION_MARKER = '__LARKUP_PRESENTATION__:';
|
|
2
|
+
const MAX_PRESENTATION_CHARS = 3_000_000;
|
|
3
|
+
const MAX_TABLE_ROWS = 5_000;
|
|
4
|
+
const MAX_TABLE_COLUMNS = 50;
|
|
5
|
+
const MAX_CELL_CHARS = 20_000;
|
|
6
|
+
const MAX_DOCUMENT_PAGES = 1_000;
|
|
7
|
+
|
|
8
|
+
export interface AnalysisTablePresentation {
|
|
9
|
+
kind: 'table';
|
|
10
|
+
title: string;
|
|
11
|
+
summary?: string;
|
|
12
|
+
columns: string[];
|
|
13
|
+
rows: Array<Record<string, string | number | boolean | null>>;
|
|
14
|
+
totalItems: number;
|
|
15
|
+
requestedItems?: number;
|
|
16
|
+
complete: boolean;
|
|
17
|
+
fileName?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface AnalysisDocumentPage {
|
|
21
|
+
title?: string;
|
|
22
|
+
content: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface AnalysisDocumentPresentation {
|
|
26
|
+
kind: 'document';
|
|
27
|
+
title: string;
|
|
28
|
+
summary?: string;
|
|
29
|
+
pages: AnalysisDocumentPage[];
|
|
30
|
+
totalItems: number;
|
|
31
|
+
requestedItems?: number;
|
|
32
|
+
complete: boolean;
|
|
33
|
+
fileName?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type AnalysisPresentation = AnalysisTablePresentation | AnalysisDocumentPresentation;
|
|
37
|
+
|
|
38
|
+
function boundedText(value: unknown, max: number): string | undefined {
|
|
39
|
+
if (typeof value !== 'string') return undefined;
|
|
40
|
+
const text = value.trim();
|
|
41
|
+
return text ? text.slice(0, max) : undefined;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function boundedCount(value: unknown): number | undefined {
|
|
45
|
+
return typeof value === 'number' && Number.isFinite(value) && value >= 0
|
|
46
|
+
? Math.floor(value)
|
|
47
|
+
: undefined;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function normalizeCell(value: unknown): string | number | boolean | null {
|
|
51
|
+
if (value === null || typeof value === 'number' || typeof value === 'boolean') return value;
|
|
52
|
+
if (typeof value === 'string') return value.slice(0, MAX_CELL_CHARS);
|
|
53
|
+
try {
|
|
54
|
+
const serialized = JSON.stringify(value);
|
|
55
|
+
return (serialized ?? String(value)).slice(0, MAX_CELL_CHARS);
|
|
56
|
+
} catch {
|
|
57
|
+
return String(value).slice(0, MAX_CELL_CHARS);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function normalizeTable(value: Record<string, unknown>): AnalysisTablePresentation | undefined {
|
|
62
|
+
if (!Array.isArray(value.rows)) return undefined;
|
|
63
|
+
const inferredColumns = value.rows.flatMap((row) =>
|
|
64
|
+
row && typeof row === 'object' && !Array.isArray(row) ? Object.keys(row) : [],
|
|
65
|
+
);
|
|
66
|
+
const columns = [
|
|
67
|
+
...new Set(
|
|
68
|
+
(Array.isArray(value.columns) ? value.columns : inferredColumns).filter(
|
|
69
|
+
(column): column is string => typeof column === 'string' && column.trim().length > 0,
|
|
70
|
+
),
|
|
71
|
+
),
|
|
72
|
+
]
|
|
73
|
+
.slice(0, MAX_TABLE_COLUMNS)
|
|
74
|
+
.map((column) => column.slice(0, 160));
|
|
75
|
+
if (columns.length === 0) return undefined;
|
|
76
|
+
|
|
77
|
+
const rows: AnalysisTablePresentation['rows'] = [];
|
|
78
|
+
let serializedChars = 0;
|
|
79
|
+
for (const candidate of value.rows) {
|
|
80
|
+
if (rows.length >= MAX_TABLE_ROWS || serializedChars >= MAX_PRESENTATION_CHARS) break;
|
|
81
|
+
if (!candidate || typeof candidate !== 'object' || Array.isArray(candidate)) continue;
|
|
82
|
+
const source = candidate as Record<string, unknown>;
|
|
83
|
+
const row = Object.fromEntries(
|
|
84
|
+
columns.map((column) => [column, normalizeCell(source[column])]),
|
|
85
|
+
);
|
|
86
|
+
const rowChars = JSON.stringify(row).length;
|
|
87
|
+
if (serializedChars + rowChars > MAX_PRESENTATION_CHARS) break;
|
|
88
|
+
rows.push(row);
|
|
89
|
+
serializedChars += rowChars;
|
|
90
|
+
}
|
|
91
|
+
if (rows.length === 0) return undefined;
|
|
92
|
+
|
|
93
|
+
const requestedItems = boundedCount(value.requestedItems);
|
|
94
|
+
const reportedTotal = boundedCount(value.totalItems) ?? value.rows.length;
|
|
95
|
+
const totalItems = Math.max(reportedTotal, rows.length);
|
|
96
|
+
const retainedEveryRow = rows.length === value.rows.length;
|
|
97
|
+
const satisfiesRequest = requestedItems === undefined || rows.length >= requestedItems;
|
|
98
|
+
return {
|
|
99
|
+
kind: 'table',
|
|
100
|
+
title: boundedText(value.title, 160) ?? 'Analysis results',
|
|
101
|
+
...(boundedText(value.summary, 800) ? { summary: boundedText(value.summary, 800) } : {}),
|
|
102
|
+
columns,
|
|
103
|
+
rows,
|
|
104
|
+
totalItems,
|
|
105
|
+
...(requestedItems !== undefined ? { requestedItems } : {}),
|
|
106
|
+
complete: value.complete !== false && retainedEveryRow && satisfiesRequest,
|
|
107
|
+
...(boundedText(value.fileName, 120) ? { fileName: boundedText(value.fileName, 120) } : {}),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function normalizeDocument(
|
|
112
|
+
value: Record<string, unknown>,
|
|
113
|
+
): AnalysisDocumentPresentation | undefined {
|
|
114
|
+
if (!Array.isArray(value.pages)) return undefined;
|
|
115
|
+
const pages: AnalysisDocumentPage[] = [];
|
|
116
|
+
let serializedChars = 0;
|
|
117
|
+
for (const candidate of value.pages) {
|
|
118
|
+
if (pages.length >= MAX_DOCUMENT_PAGES || serializedChars >= MAX_PRESENTATION_CHARS) break;
|
|
119
|
+
const page =
|
|
120
|
+
typeof candidate === 'string'
|
|
121
|
+
? { content: candidate }
|
|
122
|
+
: candidate && typeof candidate === 'object' && !Array.isArray(candidate)
|
|
123
|
+
? (candidate as Record<string, unknown>)
|
|
124
|
+
: undefined;
|
|
125
|
+
const content = boundedText(page?.content, MAX_CELL_CHARS);
|
|
126
|
+
if (!content || serializedChars + content.length > MAX_PRESENTATION_CHARS) break;
|
|
127
|
+
const title = boundedText(page?.title, 160);
|
|
128
|
+
pages.push({ ...(title ? { title } : {}), content });
|
|
129
|
+
serializedChars += content.length;
|
|
130
|
+
}
|
|
131
|
+
if (pages.length === 0) return undefined;
|
|
132
|
+
|
|
133
|
+
const requestedItems = boundedCount(value.requestedItems);
|
|
134
|
+
const reportedTotal = boundedCount(value.totalItems) ?? value.pages.length;
|
|
135
|
+
const totalItems = Math.max(reportedTotal, pages.length);
|
|
136
|
+
const retainedEveryPage = pages.length === value.pages.length;
|
|
137
|
+
const satisfiesRequest = requestedItems === undefined || pages.length >= requestedItems;
|
|
138
|
+
return {
|
|
139
|
+
kind: 'document',
|
|
140
|
+
title: boundedText(value.title, 160) ?? 'Prepared document',
|
|
141
|
+
...(boundedText(value.summary, 800) ? { summary: boundedText(value.summary, 800) } : {}),
|
|
142
|
+
pages,
|
|
143
|
+
totalItems,
|
|
144
|
+
...(requestedItems !== undefined ? { requestedItems } : {}),
|
|
145
|
+
complete: value.complete !== false && retainedEveryPage && satisfiesRequest,
|
|
146
|
+
...(boundedText(value.fileName, 120) ? { fileName: boundedText(value.fileName, 120) } : {}),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function extractAnalysisPresentation(stdout: string): {
|
|
151
|
+
stdout: string;
|
|
152
|
+
presentation?: AnalysisPresentation;
|
|
153
|
+
} {
|
|
154
|
+
const markerIndex = stdout.lastIndexOf(PRESENTATION_MARKER);
|
|
155
|
+
if (markerIndex < 0) return { stdout };
|
|
156
|
+
const payloadStart = markerIndex + PRESENTATION_MARKER.length;
|
|
157
|
+
const lineEnd = stdout.indexOf('\n', payloadStart);
|
|
158
|
+
const payload = stdout.slice(payloadStart, lineEnd < 0 ? undefined : lineEnd).trim();
|
|
159
|
+
const cleanedStdout =
|
|
160
|
+
`${stdout.slice(0, markerIndex)}${lineEnd < 0 ? '' : stdout.slice(lineEnd + 1)}`.trim();
|
|
161
|
+
if (!payload || payload.length > MAX_PRESENTATION_CHARS * 2) return { stdout: cleanedStdout };
|
|
162
|
+
try {
|
|
163
|
+
const value = JSON.parse(payload) as Record<string, unknown>;
|
|
164
|
+
const presentation =
|
|
165
|
+
value?.kind === 'table'
|
|
166
|
+
? normalizeTable(value)
|
|
167
|
+
: value?.kind === 'document'
|
|
168
|
+
? normalizeDocument(value)
|
|
169
|
+
: undefined;
|
|
170
|
+
return { stdout: cleanedStdout, ...(presentation ? { presentation } : {}) };
|
|
171
|
+
} catch {
|
|
172
|
+
return { stdout: cleanedStdout };
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Injected before generated Python so long results have one stable UI contract. */
|
|
177
|
+
export const ANALYSIS_PRESENTATION_PYTHON = String.raw`
|
|
178
|
+
import json as _larkup_json
|
|
179
|
+
|
|
180
|
+
def _larkup_json_default(value):
|
|
181
|
+
item = getattr(value, "item", None)
|
|
182
|
+
if callable(item):
|
|
183
|
+
try:
|
|
184
|
+
return item()
|
|
185
|
+
except Exception:
|
|
186
|
+
pass
|
|
187
|
+
return str(value)
|
|
188
|
+
|
|
189
|
+
def larkup_table(title, rows, columns=None, summary=None, requested_items=None, file_name=None):
|
|
190
|
+
if hasattr(rows, "to_dict"):
|
|
191
|
+
rows = rows.to_dict(orient="records")
|
|
192
|
+
rows = list(rows)
|
|
193
|
+
if columns is None:
|
|
194
|
+
columns = list(rows[0].keys()) if rows else []
|
|
195
|
+
requested_items = requested_items if requested_items is not None else _LARKUP_REQUESTED_ITEMS
|
|
196
|
+
payload = {
|
|
197
|
+
"kind": "table", "title": title, "summary": summary,
|
|
198
|
+
"columns": list(columns), "rows": rows, "totalItems": len(rows),
|
|
199
|
+
"requestedItems": requested_items, "complete": True, "fileName": file_name,
|
|
200
|
+
}
|
|
201
|
+
print("${PRESENTATION_MARKER}" + _larkup_json.dumps(payload, ensure_ascii=False, default=_larkup_json_default))
|
|
202
|
+
|
|
203
|
+
def larkup_document(title, pages, summary=None, requested_items=None, file_name=None):
|
|
204
|
+
pages = list(pages)
|
|
205
|
+
requested_items = requested_items if requested_items is not None else _LARKUP_REQUESTED_ITEMS
|
|
206
|
+
payload = {
|
|
207
|
+
"kind": "document", "title": title, "summary": summary,
|
|
208
|
+
"pages": pages, "totalItems": len(pages), "requestedItems": requested_items,
|
|
209
|
+
"complete": True, "fileName": file_name,
|
|
210
|
+
}
|
|
211
|
+
print("${PRESENTATION_MARKER}" + _larkup_json.dumps(payload, ensure_ascii=False, default=_larkup_json_default))
|
|
212
|
+
`;
|
|
213
|
+
|
|
214
|
+
export function withAnalysisPresentationHelpers(code: string, requestedItems?: number): string {
|
|
215
|
+
const requested = requestedItems === undefined ? 'None' : String(requestedItems);
|
|
216
|
+
return `${ANALYSIS_PRESENTATION_PYTHON}\n_LARKUP_REQUESTED_ITEMS = ${requested}\n${code}`;
|
|
217
|
+
}
|
|
@@ -40,6 +40,14 @@ export type ReusableTabularEvidence = {
|
|
|
40
40
|
totalRows: number;
|
|
41
41
|
};
|
|
42
42
|
|
|
43
|
+
export type AnalysisArtifactSummary = {
|
|
44
|
+
kind: 'table' | 'document';
|
|
45
|
+
title: string;
|
|
46
|
+
totalItems: number;
|
|
47
|
+
requestedItems?: number;
|
|
48
|
+
complete: boolean;
|
|
49
|
+
};
|
|
50
|
+
|
|
43
51
|
/** Only reuse table evidence while its source is still present in the live data store. */
|
|
44
52
|
export function tabularEvidenceDatasetIsAvailable(
|
|
45
53
|
evidence: ReusableTabularEvidence | undefined,
|
|
@@ -283,6 +291,44 @@ function completeTabularEvidence(
|
|
|
283
291
|
};
|
|
284
292
|
}
|
|
285
293
|
|
|
294
|
+
function analysisPresentation(output: unknown): Record<string, unknown> | undefined {
|
|
295
|
+
const result = unwrapJson(output) as Record<string, unknown> | undefined;
|
|
296
|
+
const presentation = result?.presentation;
|
|
297
|
+
return presentation && typeof presentation === 'object' && !Array.isArray(presentation)
|
|
298
|
+
? (presentation as Record<string, unknown>)
|
|
299
|
+
: undefined;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/** Read only completion metadata; the full paged artifact remains in the UI message. */
|
|
303
|
+
export function extractLatestAnalysisArtifact(
|
|
304
|
+
messages: readonly ChatMessage[],
|
|
305
|
+
): AnalysisArtifactSummary | undefined {
|
|
306
|
+
for (const message of [...messages].reverse()) {
|
|
307
|
+
if (message.role !== 'assistant') continue;
|
|
308
|
+
for (const part of toolParts(message).reverse()) {
|
|
309
|
+
if (part.name !== 'executeAnalysis' && part.name !== 'analyzeCorpusWithCode') continue;
|
|
310
|
+
const presentation = analysisPresentation(part.output);
|
|
311
|
+
if (
|
|
312
|
+
(presentation?.kind !== 'table' && presentation?.kind !== 'document') ||
|
|
313
|
+
typeof presentation.title !== 'string' ||
|
|
314
|
+
typeof presentation.totalItems !== 'number' ||
|
|
315
|
+
typeof presentation.complete !== 'boolean'
|
|
316
|
+
)
|
|
317
|
+
continue;
|
|
318
|
+
return {
|
|
319
|
+
kind: presentation.kind,
|
|
320
|
+
title: presentation.title,
|
|
321
|
+
totalItems: presentation.totalItems,
|
|
322
|
+
...(typeof presentation.requestedItems === 'number'
|
|
323
|
+
? { requestedItems: presentation.requestedItems }
|
|
324
|
+
: {}),
|
|
325
|
+
complete: presentation.complete,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
return undefined;
|
|
330
|
+
}
|
|
331
|
+
|
|
286
332
|
/** Keep the complete bounded query result for an immediate export follow-up. */
|
|
287
333
|
export function extractLatestTabularExportResult(
|
|
288
334
|
messages: readonly ChatMessage[],
|
|
@@ -290,8 +336,18 @@ export function extractLatestTabularExportResult(
|
|
|
290
336
|
for (const message of [...messages].reverse()) {
|
|
291
337
|
if (message.role !== 'assistant') continue;
|
|
292
338
|
const result = toolParts(message)
|
|
293
|
-
.filter(
|
|
294
|
-
|
|
339
|
+
.filter(
|
|
340
|
+
(part) =>
|
|
341
|
+
part.name === 'queryTabularData' ||
|
|
342
|
+
part.name === 'executeAnalysis' ||
|
|
343
|
+
part.name === 'analyzeCorpusWithCode',
|
|
344
|
+
)
|
|
345
|
+
.map((part) =>
|
|
346
|
+
completeTabularEvidence(
|
|
347
|
+
part.input,
|
|
348
|
+
part.name === 'queryTabularData' ? part.output : analysisPresentation(part.output),
|
|
349
|
+
),
|
|
350
|
+
)
|
|
295
351
|
.find(Boolean);
|
|
296
352
|
if (result) return result;
|
|
297
353
|
}
|
|
@@ -348,8 +404,18 @@ export function extractConversationEvidence(
|
|
|
348
404
|
if (message.role !== 'assistant') continue;
|
|
349
405
|
const parts = toolParts(message);
|
|
350
406
|
const table = parts
|
|
351
|
-
.filter(
|
|
352
|
-
|
|
407
|
+
.filter(
|
|
408
|
+
(part) =>
|
|
409
|
+
part.name === 'queryTabularData' ||
|
|
410
|
+
part.name === 'executeAnalysis' ||
|
|
411
|
+
part.name === 'analyzeCorpusWithCode',
|
|
412
|
+
)
|
|
413
|
+
.map((part) =>
|
|
414
|
+
tabularEvidence(
|
|
415
|
+
part.input,
|
|
416
|
+
part.name === 'queryTabularData' ? part.output : analysisPresentation(part.output),
|
|
417
|
+
),
|
|
418
|
+
)
|
|
353
419
|
.find(Boolean);
|
|
354
420
|
if (table) {
|
|
355
421
|
return { sources: [], images: [], mediaAssetIds: [], tabular: table };
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {
|
|
2
|
+
MAX_TABULAR_RESULT_LIMIT,
|
|
3
|
+
type TabularDataset,
|
|
4
|
+
type TabularQueryRequest,
|
|
5
|
+
} from '@larkup/core/tabular-store';
|
|
2
6
|
|
|
3
7
|
export type InferredTabularPlan = {
|
|
4
8
|
request: TabularQueryRequest;
|
|
@@ -78,6 +82,17 @@ function requestedSortOrder(text: string) {
|
|
|
78
82
|
return undefined;
|
|
79
83
|
}
|
|
80
84
|
|
|
85
|
+
/** Preserve an explicit long-list size instead of relying on a model default. */
|
|
86
|
+
export function requestedTabularResultLimit(text: string): number | undefined {
|
|
87
|
+
const match = text.match(
|
|
88
|
+
/\b(?:top|first|last|show|list|return|give(?:\s+me)?|display)\s+(?:the\s+)?(\d{1,4})\b/iu,
|
|
89
|
+
);
|
|
90
|
+
if (!match) return undefined;
|
|
91
|
+
const requested = Number(match[1]);
|
|
92
|
+
if (!Number.isSafeInteger(requested) || requested < 1) return undefined;
|
|
93
|
+
return Math.min(requested, MAX_TABULAR_RESULT_LIMIT);
|
|
94
|
+
}
|
|
95
|
+
|
|
81
96
|
function normalizeAggregations(
|
|
82
97
|
dataset: TabularDataset,
|
|
83
98
|
request: TabularQueryRequest,
|
|
@@ -133,6 +148,7 @@ export function inferTabularPlan(
|
|
|
133
148
|
const measureColumns =
|
|
134
149
|
requestedMeasures.length > 0 ? requestedMeasures : numeric.length === 1 ? [numeric[0]] : [];
|
|
135
150
|
const year = text.match(/\b(20\d{2})\b/)?.[1];
|
|
151
|
+
const explicitLimit = requestedTabularResultLimit(text);
|
|
136
152
|
|
|
137
153
|
if (
|
|
138
154
|
dateColumn &&
|
|
@@ -184,12 +200,15 @@ export function inferTabularPlan(
|
|
|
184
200
|
...(sortOrder
|
|
185
201
|
? { sortBy: `${aggregations[0].op}_${aggregations[0].column}`, sortOrder }
|
|
186
202
|
: {}),
|
|
187
|
-
limit: Math.min(request.limit ?? 120, 120),
|
|
203
|
+
limit: explicitLimit ?? Math.min(request.limit ?? 120, 120),
|
|
188
204
|
},
|
|
189
205
|
};
|
|
190
206
|
}
|
|
191
207
|
|
|
192
208
|
return {
|
|
193
|
-
request:
|
|
209
|
+
request: {
|
|
210
|
+
...normalizeDistinctCountIntent(text, normalizeAggregations(dataset, request)),
|
|
211
|
+
...(explicitLimit ? { limit: explicitLimit } : {}),
|
|
212
|
+
},
|
|
194
213
|
};
|
|
195
214
|
}
|
|
@@ -59,9 +59,37 @@ export function hasCompletedTabularVisualizationStep(
|
|
|
59
59
|
* column names.
|
|
60
60
|
*/
|
|
61
61
|
export function requiresTabularSandbox(text: string): boolean {
|
|
62
|
-
return
|
|
63
|
-
|
|
62
|
+
return (
|
|
63
|
+
/\b(?:join|correlat(?:e|ion)|regression|cluster(?:ing)?|forecast(?:ing)?|statistical|sandbox|python|matplotlib|seaborn|scikit(?:-learn)?|custom\s+(?:analysis|chart|visuali[sz]ation)|across\s+(?:sheets?|tables?|datasets?|files?)|between\s+(?:sheets?|tables?|datasets?|files?))\b/i.test(
|
|
64
|
+
text,
|
|
65
|
+
) || requestsLongAnalysisArtifact(text)
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Long per-record transformations cannot be completed safely as streamed prose:
|
|
71
|
+
* the model will eventually hit its output ceiling and a later "continue" loses
|
|
72
|
+
* global constraints. Route these through one sandbox run and a paged artifact.
|
|
73
|
+
*/
|
|
74
|
+
export function requestsLongAnalysisArtifact(text: string): boolean {
|
|
75
|
+
const hasManyTargets =
|
|
76
|
+
/\bfor\s+(?:the\s+)?(?:first|last|top)\s+\d+\b|\bfor\s+(?:each|every|all)\b|\b(?:each|every|all)\s+(?:of\s+)?(?:the\s+)?(?:people|participants?|rows?|records?|entries|documents?|items?|responses?)\b/i.test(
|
|
77
|
+
text,
|
|
78
|
+
);
|
|
79
|
+
const transformsEachTarget =
|
|
80
|
+
/\b(?:match(?:es|ed|ing)?|recommend(?:ation|ations|ed|ing)?|explain(?:ed|ing|s)?|summari[sz](?:e|ed|es|ing|ation)|profile(?:d|s|ing)?|assess(?:ed|ment|ments|ing)?|evaluat(?:e|ed|es|ing|ion)|classif(?:y|ied|ies|ication)|score(?:d|s|ing)?|rewrite|generate\s+(?:a\s+)?(?:description|report|answer))\b/i.test(
|
|
81
|
+
text,
|
|
82
|
+
);
|
|
83
|
+
return hasManyTargets && transformsEachTarget;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Preserve explicit batch sizes for sandbox artifacts (larger than chat prose limits). */
|
|
87
|
+
export function requestedLongAnalysisItemCount(text: string): number | undefined {
|
|
88
|
+
const match = text.match(
|
|
89
|
+
/\b(?:first|last|top)\s+(\d{1,5})\b|\bfor\s+(\d{1,5})\s+(?:people|participants?|rows?|records?|entries|documents?|items?|responses?)\b/i,
|
|
64
90
|
);
|
|
91
|
+
const value = Number(match?.[1] ?? match?.[2]);
|
|
92
|
+
return Number.isInteger(value) && value > 0 ? Math.min(value, 5_000) : undefined;
|
|
65
93
|
}
|
|
66
94
|
|
|
67
95
|
/**
|
|
@@ -35,9 +35,15 @@ function formatSeriesLabel(value: string) {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
export function requestsVisualization(text: string): boolean {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
const explicitChart =
|
|
39
|
+
/\b(?:charts?|graphs?|plots?|visuali[sz](?:e|ation)|diagram|histogram|scatter\s*plot|trends?)\b/iu.test(
|
|
40
|
+
text,
|
|
41
|
+
);
|
|
42
|
+
const visualPresentation =
|
|
43
|
+
/\b(?:show|display|draw|render|create|make)\b.{0,40}\b(?:distribution|breakdown|trend|comparison)\b/iu.test(
|
|
44
|
+
text,
|
|
45
|
+
);
|
|
46
|
+
return explicitChart || visualPresentation;
|
|
41
47
|
}
|
|
42
48
|
|
|
43
49
|
function normalizedTerms(value: string) {
|
|
@@ -1192,11 +1192,28 @@ function compactValue(value: unknown, toolName?: string): unknown {
|
|
|
1192
1192
|
totalRows: result.totalRows,
|
|
1193
1193
|
aggregationResults: result.aggregationResults,
|
|
1194
1194
|
};
|
|
1195
|
-
} else if (toolName === 'executeAnalysis') {
|
|
1195
|
+
} else if (toolName === 'executeAnalysis' || toolName === 'analyzeCorpusWithCode') {
|
|
1196
|
+
const presentation =
|
|
1197
|
+
result.presentation && typeof result.presentation === 'object'
|
|
1198
|
+
? (result.presentation as Record<string, any>)
|
|
1199
|
+
: undefined;
|
|
1196
1200
|
compact = {
|
|
1197
1201
|
stdout: typeof result.stdout === 'string' ? result.stdout.slice(0, 4_000) : result.stdout,
|
|
1198
1202
|
stderr: typeof result.stderr === 'string' ? result.stderr.slice(0, 1_000) : result.stderr,
|
|
1199
1203
|
exitCode: result.exitCode,
|
|
1204
|
+
...(presentation
|
|
1205
|
+
? {
|
|
1206
|
+
presentation: {
|
|
1207
|
+
kind: presentation.kind,
|
|
1208
|
+
title: presentation.title,
|
|
1209
|
+
summary: presentation.summary,
|
|
1210
|
+
totalItems: presentation.totalItems,
|
|
1211
|
+
requestedItems: presentation.requestedItems,
|
|
1212
|
+
complete: presentation.complete,
|
|
1213
|
+
...(Array.isArray(presentation.columns) ? { columns: presentation.columns } : {}),
|
|
1214
|
+
},
|
|
1215
|
+
}
|
|
1216
|
+
: {}),
|
|
1200
1217
|
};
|
|
1201
1218
|
} else if (
|
|
1202
1219
|
(toolName === 'inspectPdfPages' || toolName === 'analyzePdfPages') &&
|