inspect-ai 0.3.103__py3-none-any.whl → 0.3.105__py3-none-any.whl

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.
Files changed (134) hide show
  1. inspect_ai/_cli/common.py +2 -1
  2. inspect_ai/_cli/eval.py +2 -2
  3. inspect_ai/_display/core/active.py +3 -0
  4. inspect_ai/_display/core/config.py +1 -0
  5. inspect_ai/_display/core/panel.py +21 -13
  6. inspect_ai/_display/core/results.py +3 -7
  7. inspect_ai/_display/core/rich.py +3 -5
  8. inspect_ai/_display/log/__init__.py +0 -0
  9. inspect_ai/_display/log/display.py +173 -0
  10. inspect_ai/_display/plain/display.py +2 -2
  11. inspect_ai/_display/rich/display.py +2 -4
  12. inspect_ai/_display/textual/app.py +1 -6
  13. inspect_ai/_display/textual/widgets/task_detail.py +3 -14
  14. inspect_ai/_display/textual/widgets/tasks.py +1 -1
  15. inspect_ai/_eval/eval.py +1 -1
  16. inspect_ai/_eval/evalset.py +3 -3
  17. inspect_ai/_eval/registry.py +6 -1
  18. inspect_ai/_eval/run.py +5 -1
  19. inspect_ai/_eval/task/constants.py +1 -0
  20. inspect_ai/_eval/task/log.py +2 -0
  21. inspect_ai/_eval/task/run.py +65 -39
  22. inspect_ai/_util/citation.py +88 -0
  23. inspect_ai/_util/content.py +24 -2
  24. inspect_ai/_util/json.py +17 -2
  25. inspect_ai/_util/registry.py +19 -4
  26. inspect_ai/_view/schema.py +0 -6
  27. inspect_ai/_view/server.py +17 -0
  28. inspect_ai/_view/www/dist/assets/index.css +93 -31
  29. inspect_ai/_view/www/dist/assets/index.js +10639 -10011
  30. inspect_ai/_view/www/log-schema.json +418 -1
  31. inspect_ai/_view/www/node_modules/flatted/python/flatted.py +149 -0
  32. inspect_ai/_view/www/node_modules/katex/src/fonts/generate_fonts.py +58 -0
  33. inspect_ai/_view/www/node_modules/katex/src/metrics/extract_tfms.py +114 -0
  34. inspect_ai/_view/www/node_modules/katex/src/metrics/extract_ttfs.py +122 -0
  35. inspect_ai/_view/www/node_modules/katex/src/metrics/format_json.py +28 -0
  36. inspect_ai/_view/www/node_modules/katex/src/metrics/parse_tfm.py +211 -0
  37. inspect_ai/_view/www/package.json +2 -2
  38. inspect_ai/_view/www/src/@types/log.d.ts +140 -39
  39. inspect_ai/_view/www/src/app/content/RecordTree.tsx +13 -0
  40. inspect_ai/_view/www/src/app/log-view/LogView.tsx +1 -1
  41. inspect_ai/_view/www/src/app/routing/logNavigation.ts +31 -0
  42. inspect_ai/_view/www/src/app/routing/{navigationHooks.ts → sampleNavigation.ts} +39 -86
  43. inspect_ai/_view/www/src/app/samples/SampleDialog.tsx +1 -1
  44. inspect_ai/_view/www/src/app/samples/SampleDisplay.tsx +1 -1
  45. inspect_ai/_view/www/src/app/samples/chat/ChatMessage.module.css +4 -0
  46. inspect_ai/_view/www/src/app/samples/chat/ChatMessage.tsx +17 -0
  47. inspect_ai/_view/www/src/app/samples/chat/MessageCitations.module.css +16 -0
  48. inspect_ai/_view/www/src/app/samples/chat/MessageCitations.tsx +63 -0
  49. inspect_ai/_view/www/src/app/samples/chat/MessageContent.module.css +6 -0
  50. inspect_ai/_view/www/src/app/samples/chat/MessageContent.tsx +174 -25
  51. inspect_ai/_view/www/src/app/samples/chat/MessageContents.tsx +21 -3
  52. inspect_ai/_view/www/src/app/samples/chat/content-data/ContentDataView.module.css +7 -0
  53. inspect_ai/_view/www/src/app/samples/chat/content-data/ContentDataView.tsx +111 -0
  54. inspect_ai/_view/www/src/app/samples/chat/content-data/WebSearch.module.css +10 -0
  55. inspect_ai/_view/www/src/app/samples/chat/content-data/WebSearch.tsx +14 -0
  56. inspect_ai/_view/www/src/app/samples/chat/content-data/WebSearchResults.module.css +19 -0
  57. inspect_ai/_view/www/src/app/samples/chat/content-data/WebSearchResults.tsx +49 -0
  58. inspect_ai/_view/www/src/app/samples/chat/messages.ts +7 -1
  59. inspect_ai/_view/www/src/app/samples/chat/tools/ToolCallView.tsx +12 -2
  60. inspect_ai/_view/www/src/app/samples/chat/types.ts +4 -0
  61. inspect_ai/_view/www/src/app/samples/list/SampleList.tsx +1 -1
  62. inspect_ai/_view/www/src/app/samples/sample-tools/filters.ts +26 -0
  63. inspect_ai/_view/www/src/app/samples/sample-tools/sample-filter/SampleFilter.tsx +14 -3
  64. inspect_ai/_view/www/src/app/samples/sample-tools/sample-filter/completions.ts +359 -7
  65. inspect_ai/_view/www/src/app/samples/sample-tools/sample-filter/language.ts +6 -0
  66. inspect_ai/_view/www/src/app/samples/sampleLimit.ts +2 -2
  67. inspect_ai/_view/www/src/app/samples/transcript/ModelEventView.tsx +1 -1
  68. inspect_ai/_view/www/src/app/samples/transcript/SampleLimitEventView.tsx +4 -4
  69. inspect_ai/_view/www/src/app/samples/transcript/outline/OutlineRow.tsx +1 -1
  70. inspect_ai/_view/www/src/app/samples/transcript/outline/TranscriptOutline.tsx +1 -1
  71. inspect_ai/_view/www/src/client/api/api-browser.ts +25 -0
  72. inspect_ai/_view/www/src/client/api/api-http.ts +3 -0
  73. inspect_ai/_view/www/src/client/api/api-vscode.ts +6 -0
  74. inspect_ai/_view/www/src/client/api/client-api.ts +3 -0
  75. inspect_ai/_view/www/src/client/api/jsonrpc.ts +1 -0
  76. inspect_ai/_view/www/src/client/api/types.ts +3 -0
  77. inspect_ai/_view/www/src/components/MarkdownDiv.tsx +15 -2
  78. inspect_ai/_view/www/src/state/samplePolling.ts +17 -1
  79. inspect_ai/_view/www/src/tests/README.md +2 -2
  80. inspect_ai/_view/www/src/utils/git.ts +3 -1
  81. inspect_ai/_view/www/src/utils/html.ts +6 -0
  82. inspect_ai/agent/_handoff.py +8 -5
  83. inspect_ai/agent/_react.py +5 -5
  84. inspect_ai/dataset/_dataset.py +1 -1
  85. inspect_ai/log/_condense.py +5 -0
  86. inspect_ai/log/_file.py +4 -1
  87. inspect_ai/log/_log.py +9 -4
  88. inspect_ai/log/_recorders/json.py +4 -2
  89. inspect_ai/log/_samples.py +5 -0
  90. inspect_ai/log/_util.py +2 -0
  91. inspect_ai/model/__init__.py +14 -0
  92. inspect_ai/model/_call_tools.py +17 -8
  93. inspect_ai/model/_chat_message.py +3 -0
  94. inspect_ai/model/_openai_responses.py +80 -34
  95. inspect_ai/model/_providers/_anthropic_citations.py +158 -0
  96. inspect_ai/model/_providers/_google_citations.py +100 -0
  97. inspect_ai/model/_providers/anthropic.py +219 -36
  98. inspect_ai/model/_providers/google.py +98 -22
  99. inspect_ai/model/_providers/mistral.py +20 -7
  100. inspect_ai/model/_providers/openai.py +11 -10
  101. inspect_ai/model/_providers/openai_compatible.py +3 -2
  102. inspect_ai/model/_providers/openai_responses.py +2 -5
  103. inspect_ai/model/_providers/perplexity.py +123 -0
  104. inspect_ai/model/_providers/providers.py +13 -2
  105. inspect_ai/model/_providers/vertex.py +3 -0
  106. inspect_ai/model/_trim.py +5 -0
  107. inspect_ai/tool/__init__.py +14 -0
  108. inspect_ai/tool/_mcp/_mcp.py +5 -2
  109. inspect_ai/tool/_mcp/sampling.py +19 -3
  110. inspect_ai/tool/_mcp/server.py +1 -1
  111. inspect_ai/tool/_tool.py +10 -1
  112. inspect_ai/tool/_tools/_web_search/_base_http_provider.py +104 -0
  113. inspect_ai/tool/_tools/_web_search/_exa.py +78 -0
  114. inspect_ai/tool/_tools/_web_search/_google.py +22 -25
  115. inspect_ai/tool/_tools/_web_search/_tavily.py +47 -65
  116. inspect_ai/tool/_tools/_web_search/_web_search.py +83 -36
  117. inspect_ai/tool/_tools/_web_search/_web_search_provider.py +7 -0
  118. inspect_ai/util/__init__.py +8 -0
  119. inspect_ai/util/_background.py +64 -0
  120. inspect_ai/util/_display.py +11 -2
  121. inspect_ai/util/_limit.py +72 -5
  122. inspect_ai/util/_sandbox/__init__.py +2 -0
  123. inspect_ai/util/_sandbox/docker/compose.py +2 -2
  124. inspect_ai/util/_sandbox/service.py +28 -7
  125. inspect_ai/util/_span.py +12 -1
  126. inspect_ai/util/_subprocess.py +51 -38
  127. {inspect_ai-0.3.103.dist-info → inspect_ai-0.3.105.dist-info}/METADATA +2 -2
  128. {inspect_ai-0.3.103.dist-info → inspect_ai-0.3.105.dist-info}/RECORD +134 -109
  129. /inspect_ai/model/{_openai_computer_use.py → _providers/_openai_computer_use.py} +0 -0
  130. /inspect_ai/model/{_openai_web_search.py → _providers/_openai_web_search.py} +0 -0
  131. {inspect_ai-0.3.103.dist-info → inspect_ai-0.3.105.dist-info}/WHEEL +0 -0
  132. {inspect_ai-0.3.103.dist-info → inspect_ai-0.3.105.dist-info}/entry_points.txt +0 -0
  133. {inspect_ai-0.3.103.dist-info → inspect_ai-0.3.105.dist-info}/licenses/LICENSE +0 -0
  134. {inspect_ai-0.3.103.dist-info → inspect_ai-0.3.105.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,111 @@
1
+ import { FC, ReactNode } from "react";
2
+ import { WebSearch } from "./WebSearch";
3
+
4
+ import clsx from "clsx";
5
+ import { ContentData } from "../../../../@types/log";
6
+ import { RecordTree } from "../../../content/RecordTree";
7
+ import styles from "./ContentDataView.module.css";
8
+ import { WebSearchContentData, WebSearchResults } from "./WebSearchResults";
9
+
10
+ export interface ContentDataProps {
11
+ id: string;
12
+ contentData: ContentData;
13
+ }
14
+
15
+ interface RenderableData {
16
+ type: string;
17
+ name?: string;
18
+ [key: string]: any;
19
+ }
20
+
21
+ export const ContentDataView: FC<ContentDataProps> = ({ id, contentData }) => {
22
+ const renderableData = contentData.data as RenderableData;
23
+
24
+ const renderer = contentDataRenderers.find((r) =>
25
+ r.canRender(renderableData),
26
+ );
27
+
28
+ if (!renderer) {
29
+ const { encrypted_content, ...record } = renderableData;
30
+ return (
31
+ <div className={clsx(styles.contentData)}>
32
+ <RecordTree
33
+ id={`${id}-tree`}
34
+ record={record}
35
+ className={clsx(styles.data)}
36
+ defaultExpandLevel={0}
37
+ />
38
+ </div>
39
+ );
40
+ }
41
+
42
+ return (
43
+ <div className={clsx(styles.contentData)}>
44
+ {renderer.render(renderableData)}
45
+ </div>
46
+ );
47
+ };
48
+
49
+ // The following handles rendering of the content data based on its type
50
+ // and name, allowing for different renderers to be used for different types of content data.
51
+
52
+ interface ContentDataRenderer {
53
+ name: string;
54
+ canRender: (data: RenderableData) => boolean;
55
+ render: (data: RenderableData) => ReactNode;
56
+ }
57
+
58
+ const webSearchServerToolRenderer: ContentDataRenderer = {
59
+ name: "WebSearch",
60
+ canRender: (data: RenderableData) => {
61
+ return data.type === "server_tool_use" && data.name === "web_search";
62
+ },
63
+ render: (data: RenderableData): ReactNode => {
64
+ return <WebSearch query={data.input.query} />;
65
+ },
66
+ };
67
+
68
+ const webSearchResultsServerToolRenderer: ContentDataRenderer = {
69
+ name: "WebSearchResults",
70
+ canRender: (data: RenderableData) => {
71
+ return (
72
+ data.type === "web_search_tool_result" && Array.isArray(data.content)
73
+ );
74
+ },
75
+ render: (data: RenderableData): ReactNode => {
76
+ const results: WebSearchContentData[] =
77
+ data.content as WebSearchContentData[];
78
+ return <WebSearchResults results={results} />;
79
+ },
80
+ };
81
+
82
+ const serverToolRenderer: ContentDataRenderer = {
83
+ name: "ServerTool",
84
+ canRender: (data: RenderableData) => data.type === "server_tool_use",
85
+ render: (data: RenderableData): ReactNode => {
86
+ return (
87
+ <>
88
+ <div
89
+ className={clsx(
90
+ "text-style-label",
91
+ "text-style-secondary",
92
+ "text-size-smaller",
93
+ )}
94
+ >
95
+ Server Tool
96
+ </div>
97
+ <RecordTree
98
+ id={data.name || "server-tool"}
99
+ record={data}
100
+ className={clsx(styles.data)}
101
+ />
102
+ </>
103
+ );
104
+ },
105
+ };
106
+
107
+ export const contentDataRenderers: ContentDataRenderer[] = [
108
+ webSearchServerToolRenderer,
109
+ webSearchResultsServerToolRenderer,
110
+ serverToolRenderer,
111
+ ];
@@ -0,0 +1,10 @@
1
+ .webSearch {
2
+ display: grid;
3
+ grid-template-columns: max-content 1fr;
4
+ column-gap: 0.5em;
5
+ align-items: baseline;
6
+ }
7
+
8
+ .query {
9
+ font-family: var(--bs-font-monospace);
10
+ }
@@ -0,0 +1,14 @@
1
+ import clsx from "clsx";
2
+ import { FC } from "react";
3
+ import styles from "./WebSearch.module.css";
4
+
5
+ export const WebSearch: FC<{ query: string }> = ({ query }) => {
6
+ return (
7
+ <div className={clsx(styles.webSearch, "text-size-smaller")}>
8
+ <span className={clsx("text-style-label", "text-style-secondary")}>
9
+ Web Search:
10
+ </span>
11
+ <span className={clsx(styles.query, "text-size-smallest")}>{query}</span>
12
+ </div>
13
+ );
14
+ };
@@ -0,0 +1,19 @@
1
+ .webSearch {
2
+ display: grid;
3
+ grid-template-columns: max-content 1fr;
4
+ column-gap: 0.5em;
5
+ align-items: baseline;
6
+ }
7
+
8
+ .query {
9
+ font-family: var(--bs-font-monospace);
10
+ }
11
+
12
+ .result a:hover {
13
+ text-decoration: underline;
14
+ }
15
+
16
+ .result a {
17
+ opacity: 0.8;
18
+ text-decoration: none;
19
+ }
@@ -0,0 +1,49 @@
1
+ import clsx from "clsx";
2
+ import { FC } from "react";
3
+ import styles from "./WebSearchResults.module.css";
4
+
5
+ export interface WebSearchContentData {
6
+ title: string;
7
+ url: string;
8
+ page_age: string;
9
+ }
10
+
11
+ export const WebSearchResults: FC<{ results: WebSearchContentData[] }> = ({
12
+ results,
13
+ }) => {
14
+ return (
15
+ <>
16
+ <div
17
+ className={clsx(
18
+ styles.label,
19
+ "text-style-label",
20
+ "text-style-secondary",
21
+ "text-size-smaller",
22
+ )}
23
+ >
24
+ Results
25
+ </div>
26
+
27
+ <ol className={clsx(styles.results, "text-size-smaller")}>
28
+ {results.map((result, index) => (
29
+ <li
30
+ key={index}
31
+ className={clsx(styles.result, "text-style-secondary")}
32
+ >
33
+ <a
34
+ href={result.url}
35
+ target="_blank"
36
+ rel="noopener noreferrer"
37
+ title={
38
+ result.url +
39
+ (result.page_age ? `\n(Age: ${result.page_age})` : "")
40
+ }
41
+ >
42
+ {result.title}
43
+ </a>
44
+ </li>
45
+ ))}
46
+ </ol>
47
+ </>
48
+ );
49
+ };
@@ -4,6 +4,7 @@ import {
4
4
  ChatMessageTool,
5
5
  ChatMessageUser,
6
6
  ContentAudio,
7
+ ContentData,
7
8
  ContentImage,
8
9
  ContentReasoning,
9
10
  ContentText,
@@ -64,6 +65,7 @@ export const resolveMessages = (messages: Messages) => {
64
65
  | ContentAudio
65
66
  | ContentVideo
66
67
  | ContentReasoning
68
+ | ContentData
67
69
  )[] = [];
68
70
  for (const systemMessage of systemMessages) {
69
71
  const contents = Array.isArray(systemMessage.content)
@@ -78,6 +80,7 @@ export const resolveMessages = (messages: Messages) => {
78
80
  content: systemContent,
79
81
  source: "input",
80
82
  internal: null,
83
+ metadata: null,
81
84
  };
82
85
 
83
86
  // Converge them
@@ -117,19 +120,22 @@ const normalizeContent = (
117
120
  | ContentAudio
118
121
  | ContentVideo
119
122
  | ContentReasoning
123
+ | ContentData
120
124
  | string,
121
125
  ):
122
126
  | ContentText
123
127
  | ContentImage
124
128
  | ContentAudio
125
129
  | ContentVideo
126
- | ContentReasoning => {
130
+ | ContentReasoning
131
+ | ContentData => {
127
132
  if (typeof content === "string") {
128
133
  return {
129
134
  type: "text",
130
135
  text: content,
131
136
  refusal: null,
132
137
  internal: null,
138
+ citations: null,
133
139
  };
134
140
  } else {
135
141
  return content;
@@ -2,6 +2,7 @@ import clsx from "clsx";
2
2
  import { FC, useMemo } from "react";
3
3
  import {
4
4
  ContentAudio,
5
+ ContentData,
5
6
  ContentImage,
6
7
  ContentReasoning,
7
8
  ContentText,
@@ -11,6 +12,7 @@ import {
11
12
  import { ContentTool } from "../../../../app/types";
12
13
  import ExpandablePanel from "../../../../components/ExpandablePanel";
13
14
  import { MessageContent } from "../MessageContent";
15
+ import { defaultContext } from "../MessageContents";
14
16
  import styles from "./ToolCallView.module.css";
15
17
  import { ToolInput } from "./ToolInput";
16
18
  import { ToolTitle } from "./ToolTitle";
@@ -31,6 +33,7 @@ interface ToolCallViewProps {
31
33
  | ContentVideo
32
34
  | ContentTool
33
35
  | ContentReasoning
36
+ | ContentData
34
37
  | (
35
38
  | ContentText
36
39
  | ContentAudio
@@ -38,6 +41,7 @@ interface ToolCallViewProps {
38
41
  | ContentVideo
39
42
  | ContentTool
40
43
  | ContentReasoning
44
+ | ContentData
41
45
  )[];
42
46
  mode?: "compact";
43
47
  }
@@ -65,7 +69,8 @@ export const ToolCallView: FC<ToolCallViewProps> = ({
65
69
  | ContentImage
66
70
  | ContentVideo
67
71
  | ContentTool
68
- | ContentReasoning,
72
+ | ContentReasoning
73
+ | ContentData,
69
74
  ) {
70
75
  if (value && typeof value === "object") {
71
76
  if (value.type === "image") {
@@ -105,6 +110,7 @@ export const ToolCallView: FC<ToolCallViewProps> = ({
105
110
  });
106
111
 
107
112
  const contents = mode !== "compact" ? input : input || functionCall;
113
+ const context = defaultContext();
108
114
  return (
109
115
  <div className={clsx(styles.toolCallView)}>
110
116
  <div>
@@ -127,7 +133,7 @@ export const ToolCallView: FC<ToolCallViewProps> = ({
127
133
  lines={15}
128
134
  className={clsx("text-size-small")}
129
135
  >
130
- <MessageContent contents={normalizedContent} />
136
+ <MessageContent contents={normalizedContent} context={context} />
131
137
  </ExpandablePanel>
132
138
  ) : undefined}
133
139
  </div>
@@ -148,6 +154,7 @@ const normalizeContent = (
148
154
  | ContentVideo
149
155
  | ContentTool
150
156
  | ContentReasoning
157
+ | ContentData
151
158
  | (
152
159
  | ContentText
153
160
  | ContentImage
@@ -155,6 +162,7 @@ const normalizeContent = (
155
162
  | ContentVideo
156
163
  | ContentTool
157
164
  | ContentReasoning
165
+ | ContentData
158
166
  )[],
159
167
  ): (
160
168
  | ContentText
@@ -163,6 +171,7 @@ const normalizeContent = (
163
171
  | ContentVideo
164
172
  | ContentTool
165
173
  | ContentReasoning
174
+ | ContentData
166
175
  )[] => {
167
176
  if (Array.isArray(output)) {
168
177
  return output;
@@ -176,6 +185,7 @@ const normalizeContent = (
176
185
  text: String(output),
177
186
  refusal: null,
178
187
  internal: null,
188
+ citations: null,
179
189
  },
180
190
  ],
181
191
  },
@@ -1 +1,5 @@
1
+ import { Citations } from "../../../@types/log";
2
+
1
3
  export type ChatViewToolCallStyle = "compact" | "complete" | "omit";
4
+
5
+ export type Citation = NonNullable<Citations>[number];
@@ -20,7 +20,7 @@ import clsx from "clsx";
20
20
  import { useProperty, useSampleDescriptor } from "../../../state/hooks";
21
21
  import { useVirtuosoState } from "../../../state/scrolling";
22
22
  import { useStore } from "../../../state/store";
23
- import { useSampleNavigation } from "../../routing/navigationHooks";
23
+ import { useSampleNavigation } from "../../routing/sampleNavigation";
24
24
  import { SampleFooter } from "./SampleFooter";
25
25
  import { SampleHeader } from "./SampleHeader";
26
26
  import styles from "./SampleList.module.css";
@@ -5,6 +5,7 @@ import { SampleSummary } from "../../../client/api/types";
5
5
  import { kScoreTypeBoolean } from "../../../constants";
6
6
  import { inputString } from "../../../utils/format";
7
7
  import { EvalDescriptor, ScoreDescriptor } from "../descriptor/types";
8
+ import { kSampleMetadataPrefix } from "./sample-filter/language";
8
9
 
9
10
  export interface SampleFilterItem {
10
11
  shortName?: string;
@@ -100,10 +101,25 @@ const scoreVariables = (
100
101
  return variables;
101
102
  };
102
103
 
104
+ const getNestedPropertyValue = (obj: any, path: string): any => {
105
+ const keys = path.split(".");
106
+ let current = obj;
107
+ for (const key of keys) {
108
+ if (current && typeof current === "object" && key in current) {
109
+ current = current[key];
110
+ } else {
111
+ return undefined;
112
+ }
113
+ }
114
+ return current;
115
+ };
116
+
103
117
  const sampleVariables = (sample: SampleSummary): Record<string, unknown> => {
104
118
  return {
105
119
  has_error: !!sample.error,
106
120
  has_retries: sample.retries !== undefined && sample.retries > 0,
121
+ id: sample.id,
122
+ metadata: sample.metadata,
107
123
  };
108
124
  };
109
125
 
@@ -217,6 +233,12 @@ export const filterExpression = (
217
233
  const value = get(name);
218
234
  return value;
219
235
  }
236
+ // Handle metadata property access
237
+ if (name.startsWith(kSampleMetadataPrefix)) {
238
+ const propertyPath = name.substring(kSampleMetadataPrefix.length);
239
+ const metadata = sample.metadata || {};
240
+ return getNestedPropertyValue(metadata, propertyPath);
241
+ }
220
242
  // Score variables exist only if the sample completed successfully.
221
243
  return sample.error ? undefined : get(name);
222
244
  };
@@ -240,6 +262,10 @@ export const filterExpression = (
240
262
  const errorObj = error as any as Record<string, unknown>;
241
263
  const propertyName: string = (errorObj["propertyName"] as string) || "";
242
264
  if (propertyName) {
265
+ // Don't show errors for metadata properties - they might not exist in all samples
266
+ if (propertyName.startsWith(kSampleMetadataPrefix)) {
267
+ return { matches: false, error: undefined };
268
+ }
243
269
  const regex = new RegExp(`\\b${propertyName}\\b`);
244
270
  const match = regex.exec(filterValue);
245
271
  if (match) {
@@ -34,6 +34,8 @@ Filter samples by:
34
34
  • Samples with errors: has_error
35
35
  • Input, target and error regex search: input_contains, target_contains, error_contains
36
36
  • Samples that have been retried: has_retries
37
+ • Sample Id: e.g. "id == 'sample123'"
38
+ • Sample metadata: e.g. "metadata.key == 'value'"
37
39
 
38
40
  Supported expressions:
39
41
  • Arithmetic: +, -, *, /, mod, ^
@@ -93,6 +95,12 @@ const editorTheme = EditorView.theme({
93
95
  ".cm-scroller": {
94
96
  overflow: "hidden",
95
97
  },
98
+ ".cm-line": {
99
+ "font-size": "var(--inspect-font-size-smallest) !important",
100
+ },
101
+ ".token": {
102
+ "font-size": "var(--inspect-font-size-smallest) !important",
103
+ },
96
104
  });
97
105
 
98
106
  const ensureOneLine = (tr: Transaction): TransactionSpec => {
@@ -145,6 +153,9 @@ export const SampleFilter: FC<SampleFilterProps> = () => {
145
153
 
146
154
  const filter = useStore((state) => state.log.filter);
147
155
  const filterError = useStore((state) => state.log.filterError);
156
+ const samples = useStore(
157
+ (state) => state.log.selectedLogSummary?.sampleSummaries,
158
+ );
148
159
  const setFilter = useStore((state) => state.logActions.setFilter);
149
160
 
150
161
  const handleFocus = useCallback((event: FocusEvent, view: EditorView) => {
@@ -156,10 +167,10 @@ export const SampleFilter: FC<SampleFilterProps> = () => {
156
167
  const makeAutocompletion = useCallback(
157
168
  () =>
158
169
  autocompletion({
159
- override: [(context) => getCompletions(context, filterItems)],
170
+ override: [(context) => getCompletions(context, filterItems, samples)],
160
171
  activateOnCompletion: (c) => c.label.endsWith(" "),
161
172
  }),
162
- [],
173
+ [filterItems, samples],
163
174
  );
164
175
 
165
176
  const makeLinter = useCallback(
@@ -240,7 +251,7 @@ export const SampleFilter: FC<SampleFilterProps> = () => {
240
251
  effects:
241
252
  autocompletionCompartment.current.reconfigure(makeAutocompletion()),
242
253
  });
243
- }, [filterItems]);
254
+ }, [filterItems, samples]);
244
255
 
245
256
  useEffect(() => {
246
257
  editorViewRef.current?.dispatch({