@databricks/appkit 0.71.0 → 0.73.0
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/CLAUDE.md +63 -0
- package/NOTICE.md +3 -2
- package/dist/appkit/package.js +1 -1
- package/dist/beta.d.ts +18 -3
- package/dist/beta.js +14 -1
- package/dist/cli/commands/agent/eval.js +120 -0
- package/dist/cli/commands/agent/eval.js.map +1 -0
- package/dist/cli/commands/agent/index.js +18 -0
- package/dist/cli/commands/agent/index.js.map +1 -0
- package/dist/cli/index.js +2 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/connectors/index.js +2 -0
- package/dist/connectors/mlflow/auth.d.ts +28 -0
- package/dist/connectors/mlflow/auth.d.ts.map +1 -0
- package/dist/connectors/mlflow/auth.js +70 -0
- package/dist/connectors/mlflow/auth.js.map +1 -0
- package/dist/connectors/mlflow/client.d.ts +51 -0
- package/dist/connectors/mlflow/client.d.ts.map +1 -0
- package/dist/connectors/mlflow/client.js +93 -0
- package/dist/connectors/mlflow/client.js.map +1 -0
- package/dist/connectors/mlflow/index.d.ts +2 -0
- package/dist/database/errors.js +15 -5
- package/dist/database/errors.js.map +1 -1
- package/dist/database/runtime/data-path.d.ts +7 -0
- package/dist/database/runtime/data-path.d.ts.map +1 -0
- package/dist/database/runtime/data-path.js.map +1 -1
- package/dist/database/runtime/engine/drizzle-data-path.js +7 -5
- package/dist/database/runtime/engine/drizzle-data-path.js.map +1 -1
- package/dist/database/schema-builder/define-schema.d.ts +1 -1
- package/dist/database/schema-builder/define-schema.js +1 -1
- package/dist/database/schema-builder/define-schema.js.map +1 -1
- package/dist/errors/database-validation.d.ts +23 -0
- package/dist/errors/database-validation.d.ts.map +1 -0
- package/dist/errors/database-validation.js +24 -0
- package/dist/errors/database-validation.js.map +1 -0
- package/dist/errors/index.js +1 -0
- package/dist/evals/dataset.d.ts +36 -0
- package/dist/evals/dataset.d.ts.map +1 -0
- package/dist/evals/dataset.js +36 -0
- package/dist/evals/dataset.js.map +1 -0
- package/dist/evals/define-eval.d.ts +26 -0
- package/dist/evals/define-eval.d.ts.map +1 -0
- package/dist/evals/define-eval.js +28 -0
- package/dist/evals/define-eval.js.map +1 -0
- package/dist/evals/discover.d.ts +20 -0
- package/dist/evals/discover.d.ts.map +1 -0
- package/dist/evals/discover.js +49 -0
- package/dist/evals/discover.js.map +1 -0
- package/dist/evals/http-driver.d.ts +33 -0
- package/dist/evals/http-driver.d.ts.map +1 -0
- package/dist/evals/http-driver.js +123 -0
- package/dist/evals/http-driver.js.map +1 -0
- package/dist/evals/index.d.ts +14 -0
- package/dist/evals/index.js +14 -0
- package/dist/evals/judge.d.ts +27 -0
- package/dist/evals/judge.d.ts.map +1 -0
- package/dist/evals/judge.js +77 -0
- package/dist/evals/judge.js.map +1 -0
- package/dist/evals/matchers.d.ts +12 -0
- package/dist/evals/matchers.d.ts.map +1 -0
- package/dist/evals/matchers.js +26 -0
- package/dist/evals/matchers.js.map +1 -0
- package/dist/evals/mlflow-report.d.ts +37 -0
- package/dist/evals/mlflow-report.d.ts.map +1 -0
- package/dist/evals/mlflow-report.js +161 -0
- package/dist/evals/mlflow-report.js.map +1 -0
- package/dist/evals/mlflow-run.d.ts +13 -0
- package/dist/evals/mlflow-run.d.ts.map +1 -0
- package/dist/evals/mlflow-run.js +101 -0
- package/dist/evals/mlflow-run.js.map +1 -0
- package/dist/evals/pool.js +24 -0
- package/dist/evals/pool.js.map +1 -0
- package/dist/evals/report.d.ts +25 -0
- package/dist/evals/report.d.ts.map +1 -0
- package/dist/evals/report.js +57 -0
- package/dist/evals/report.js.map +1 -0
- package/dist/evals/run-eval.d.ts +23 -0
- package/dist/evals/run-eval.d.ts.map +1 -0
- package/dist/evals/run-eval.js +152 -0
- package/dist/evals/run-eval.js.map +1 -0
- package/dist/evals/run-evals.d.ts +94 -0
- package/dist/evals/run-evals.d.ts.map +1 -0
- package/dist/evals/run-evals.js +257 -0
- package/dist/evals/run-evals.js.map +1 -0
- package/dist/evals/types.d.ts +163 -0
- package/dist/evals/types.d.ts.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/plugin/plugin.d.ts.map +1 -1
- package/dist/plugin/plugin.js +1 -1
- package/dist/plugin/plugin.js.map +1 -1
- package/dist/plugins/agents/agents.js +1 -1
- package/dist/plugins/database/crud/contract.js +17 -8
- package/dist/plugins/database/crud/contract.js.map +1 -1
- package/dist/plugins/database/crud/exposure.js +63 -22
- package/dist/plugins/database/crud/exposure.js.map +1 -1
- package/dist/plugins/database/crud/request.js +50 -0
- package/dist/plugins/database/crud/request.js.map +1 -0
- package/dist/plugins/database/crud/response.js +77 -0
- package/dist/plugins/database/crud/response.js.map +1 -0
- package/dist/plugins/database/crud/routes.js +71 -52
- package/dist/plugins/database/crud/routes.js.map +1 -1
- package/dist/plugins/database/database.d.ts +6 -4
- package/dist/plugins/database/database.d.ts.map +1 -1
- package/dist/plugins/database/database.js +46 -16
- package/dist/plugins/database/database.js.map +1 -1
- package/dist/plugins/database/defaults.js +5 -1
- package/dist/plugins/database/defaults.js.map +1 -1
- package/dist/plugins/database/entity-client.js +143 -10
- package/dist/plugins/database/entity-client.js.map +1 -1
- package/dist/plugins/database/entity-types.d.ts +1 -1
- package/dist/plugins/database/hooks.d.ts +38 -0
- package/dist/plugins/database/hooks.d.ts.map +1 -0
- package/dist/plugins/database/index.d.ts +3 -2
- package/dist/plugins/database/lifecycle.js +67 -28
- package/dist/plugins/database/lifecycle.js.map +1 -1
- package/dist/plugins/database/scope.js +58 -0
- package/dist/plugins/database/scope.js.map +1 -0
- package/dist/plugins/database/types.d.ts +40 -12
- package/dist/plugins/database/types.d.ts.map +1 -1
- package/docs/api/appkit/Class.AppKitError.md +1 -0
- package/docs/api/appkit/Class.DatabaseValidationError.md +191 -0
- package/docs/api/appkit/Class.MlflowClient.md +103 -0
- package/docs/api/appkit/Function.buildAssessments.md +16 -0
- package/docs/api/appkit/Function.configureJudge.md +18 -0
- package/docs/api/appkit/Function.createHttpDriver.md +18 -0
- package/docs/api/appkit/Function.defineEval.md +35 -0
- package/docs/api/appkit/Function.defineSchema.md +1 -1
- package/docs/api/appkit/Function.discoverEvalFiles.md +18 -0
- package/docs/api/appkit/Function.equals.md +18 -0
- package/docs/api/appkit/Function.evalGlyph.md +18 -0
- package/docs/api/appkit/Function.formatEvalDetail.md +18 -0
- package/docs/api/appkit/Function.formatEvalHeadline.md +18 -0
- package/docs/api/appkit/Function.formatEvalResults.md +18 -0
- package/docs/api/appkit/Function.formatSummaryLine.md +18 -0
- package/docs/api/appkit/Function.includes.md +18 -0
- package/docs/api/appkit/Function.isJudgeConfigured.md +10 -0
- package/docs/api/appkit/Function.matches.md +18 -0
- package/docs/api/appkit/Function.normalizeHost.md +18 -0
- package/docs/api/appkit/Function.readEvalDataset.md +21 -0
- package/docs/api/appkit/Function.reportToMlflow.md +23 -0
- package/docs/api/appkit/Function.resolveDatabricksAuth.md +16 -0
- package/docs/api/appkit/Function.resolveWorkspaceClient.md +18 -0
- package/docs/api/appkit/Function.runEval.md +19 -0
- package/docs/api/appkit/Function.runEvalsInDir.md +18 -0
- package/docs/api/appkit/Function.summarize.md +16 -0
- package/docs/api/appkit/Interface.AssertionHandle.md +54 -0
- package/docs/api/appkit/Interface.AssertionResult.md +48 -0
- package/docs/api/appkit/Interface.Assessment.md +83 -0
- package/docs/api/appkit/Interface.CustomJudgeSpec.md +30 -0
- package/docs/api/appkit/Interface.DatabaseValidationIssue.md +21 -0
- package/docs/api/appkit/Interface.DatabricksAuth.md +21 -0
- package/docs/api/appkit/Interface.DatasetRow.md +21 -0
- package/docs/api/appkit/Interface.DiscoveredEval.md +36 -0
- package/docs/api/appkit/Interface.DriveResult.md +58 -0
- package/docs/api/appkit/Interface.EntityMutationHooks.md +173 -0
- package/docs/api/appkit/Interface.EvalDefinition.md +74 -0
- package/docs/api/appkit/Interface.EvalDriver.md +37 -0
- package/docs/api/appkit/Interface.EvalResult.md +83 -0
- package/docs/api/appkit/Interface.EvalRunSummary.md +46 -0
- package/docs/api/appkit/Interface.EvalSummary.md +48 -0
- package/docs/api/appkit/Interface.HookApp.md +12 -0
- package/docs/api/appkit/Interface.HookContext.md +21 -0
- package/docs/api/appkit/Interface.HttpDriverOptions.md +67 -0
- package/docs/api/appkit/Interface.JudgeConfig.md +34 -0
- package/docs/api/appkit/Interface.JudgeScore.md +21 -0
- package/docs/api/appkit/Interface.MatchResult.md +34 -0
- package/docs/api/appkit/Interface.PostResult.md +30 -0
- package/docs/api/appkit/Interface.ReadEvalDatasetOptions.md +34 -0
- package/docs/api/appkit/Interface.ReadSerializerContext.md +21 -0
- package/docs/api/appkit/Interface.ReportOutcome.md +53 -0
- package/docs/api/appkit/Interface.ResolveDatabricksAuthOptions.md +34 -0
- package/docs/api/appkit/Interface.RunEvalOptions.md +45 -0
- package/docs/api/appkit/Interface.RunEvalsOptions.md +214 -0
- package/docs/api/appkit/Interface.TestContext.md +245 -0
- package/docs/api/appkit/TypeAlias.DatabaseApiConfig.md +53 -0
- package/docs/api/appkit/TypeAlias.DatabaseApiWriteOperation.md +8 -0
- package/docs/api/appkit/TypeAlias.DatabaseApiWritesConfig.md +49 -0
- package/docs/api/appkit/TypeAlias.DatabaseExports.md +3 -3
- package/docs/api/appkit/TypeAlias.EntityHooks.md +25 -0
- package/docs/api/appkit/TypeAlias.EvalProgress.md +26 -0
- package/docs/api/appkit/TypeAlias.IDatabaseConfig.md +16 -5
- package/docs/api/appkit/TypeAlias.Matcher.md +18 -0
- package/docs/api/appkit/TypeAlias.ReadSerializer.md +19 -0
- package/docs/api/appkit/TypeAlias.Severity.md +8 -0
- package/docs/api/appkit/TypeAlias.TransactionClient.md +19 -0
- package/docs/api/appkit.md +157 -95
- package/docs/plugins/database.md +144 -0
- package/llms.txt +63 -0
- package/package.json +3 -2
- package/sbom.cdx.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"define-schema.js","names":[],"sources":["../../../src/database/schema-builder/define-schema.ts"],"sourcesContent":["import { ColumnBuilder, enumColumn, validateLiteralDefaults } from \"./columns\";\nimport { buildEngineTables } from \"./engine/tables\";\nimport { resolveForeignKeys } from \"./fk\";\nimport { buildRelations } from \"./relations\";\nimport {\n type AppKitTable,\n type ColumnMeta,\n type ColumnRef,\n type DefineSchemaOptions,\n type MutableColumnMeta,\n type ResolvedRelation,\n type Schema,\n SchemaBuildError,\n type TableHandle,\n} from \"./types\";\nimport { deriveInsertSchema, deriveUpdateSchema } from \"./validators\";\n\ninterface RawTable {\n readonly name: string;\n readonly metas: Record<string, MutableColumnMeta>;\n readonly handle: Record<string, ColumnRef>;\n}\n\ninterface FinalizedTableCandidate {\n readonly columns: Readonly<Record<string, ColumnMeta>>;\n readonly engine: Schema[\"$engine\"][string];\n readonly relations: readonly ResolvedRelation[];\n readonly insertSchema: unknown;\n readonly updateSchema: unknown;\n}\n\ninterface DeclarationState {\n readonly raw: Map<string, RawTable>;\n readonly handleNames: Map<object, string>;\n}\n\nexport interface SchemaBuilderContext {\n table<C extends Record<string, ColumnBuilder>>(\n name: string,\n columns: C,\n ): TableHandle<C>;\n enum(name: string, values: readonly string[]): ColumnBuilder;\n}\n\nconst RESERVED_OBJECT_KEYS = new Set([\"__proto__\", \"prototype\", \"constructor\"]);\nconst RESERVED_DATABASE_EXPORT_KEYS = new Set([\"sql\", \"transaction\"]);\n// A global symbol lets typegen recognize schemas loaded through another AppKit instance.\nconst FINALIZED_SCHEMA = Symbol.for(\"@databricks/appkit.database.schema\");\nconst TABLE_METADATA_KEYS = [\n \"$name\",\n \"$schemaName\",\n \"$columns\",\n \"$engine\",\n \"$relations\",\n \"$insertSchema\",\n \"$updateSchema\",\n] as const;\n\nfunction nullRecord<T>(): Record<string, T> {\n return Object.create(null) as Record<string, T>;\n}\n\nfunction assertRecord(value: unknown, label: string): asserts value is object {\n if (value === null || typeof value !== \"object\" || Array.isArray(value)) {\n throw new SchemaBuildError(`${label} must be an object`);\n }\n}\n\nfunction assertName(value: string, label: string): void {\n if (!value || RESERVED_OBJECT_KEYS.has(value)) {\n throw new SchemaBuildError(`${label} \"${value}\" is reserved`);\n }\n}\n\nfunction assertTableName(value: string): void {\n assertName(value, \"Table name\");\n if (RESERVED_DATABASE_EXPORT_KEYS.has(value)) {\n throw new SchemaBuildError(`Table name \"${value}\" is reserved`);\n }\n}\n\nfunction finalizeColumn(meta: MutableColumnMeta): ColumnMeta {\n if (!meta.engineColumn) {\n throw new SchemaBuildError(\n `Engine column \"${meta.columnName}\" is missing during finalization`,\n );\n }\n const { fkRef: _fkRef, ...published } = meta;\n return Object.freeze({\n ...published,\n enumValues: meta.enumValues\n ? Object.freeze([...meta.enumValues])\n : undefined,\n fk: meta.fk ? Object.freeze({ ...meta.fk }) : undefined,\n engineColumn: meta.engineColumn,\n }) as ColumnMeta;\n}\n\nfunction finalizeRelations(\n relations: readonly ResolvedRelation[],\n): readonly ResolvedRelation[] {\n return Object.freeze(\n relations.map((relation) => Object.freeze({ ...relation })),\n );\n}\n\n/** Clone builders into per-table metadata and create stable column handles. */\nfunction declareTable<C extends Record<string, ColumnBuilder>>(\n state: DeclarationState,\n name: string,\n columns: C,\n): TableHandle<C> {\n assertTableName(name);\n if (state.raw.has(name)) {\n throw new SchemaBuildError(`Duplicate table \"${name}\"`);\n }\n assertRecord(columns, `Columns for table \"${name}\"`);\n\n const metas = nullRecord<MutableColumnMeta>();\n const handle = Object.create(null) as Record<string, ColumnRef>;\n for (const [key, column] of Object.entries(columns)) {\n assertName(key, `Column name on table \"${name}\"`);\n if (key.startsWith(\"$\") || key === \"and\" || key === \"or\") {\n throw new SchemaBuildError(\n `Column \"${name}.${key}\" collides with AppKit runtime metadata`,\n );\n }\n if (!(column instanceof ColumnBuilder)) {\n throw new SchemaBuildError(\n `Column \"${name}.${key}\" was not created by an AppKit column builder`,\n );\n }\n\n const meta = column._cloneMeta();\n meta.name = key;\n meta.columnName = key;\n metas[key] = meta;\n\n const reference: ColumnRef = Object.freeze({\n __isColumnRef: true,\n tableName: name,\n columnName: key,\n });\n Object.defineProperty(handle, key, {\n enumerable: true,\n value: reference,\n });\n }\n\n state.raw.set(name, { name, metas, handle });\n state.handleNames.set(handle, name);\n return handle as unknown as TableHandle<C>;\n}\n\nfunction createBuilderContext(state: DeclarationState): SchemaBuilderContext {\n return {\n table(name, columns) {\n return declareTable(state, name, columns);\n },\n enum(name, values) {\n return enumColumn(name, values);\n },\n };\n}\n\n/** Require every exact table handle once under its declared identity. */\nfunction validateReturnedTables(\n returned: unknown,\n state: DeclarationState,\n): void {\n assertRecord(returned, \"defineSchema() return value\");\n const returnedHandles = new Set<object>();\n for (const [key, value] of Object.entries(returned)) {\n const name =\n value !== null && typeof value === \"object\"\n ? state.handleNames.get(value)\n : undefined;\n if (!name) {\n throw new SchemaBuildError(\n `defineSchema returned a value for \"${key}\" that was not produced by ctx.table()`,\n );\n }\n if (key !== name) {\n throw new SchemaBuildError(\n `defineSchema returned table \"${name}\" under key \"${key}\"; aliases are not supported`,\n );\n }\n if (returnedHandles.has(value)) {\n throw new SchemaBuildError(`Table \"${name}\" was returned more than once`);\n }\n returnedHandles.add(value);\n }\n if (returnedHandles.size !== state.raw.size) {\n const omitted = [...state.raw.values()]\n .filter((table) => !returnedHandles.has(table.handle))\n .map((table) => table.name);\n throw new SchemaBuildError(\n `defineSchema omitted declared table${omitted.length === 1 ? \"\" : \"s\"}: ${omitted.join(\", \")}`,\n );\n }\n}\n\n/** Detect declaration-handle tampering before metadata publication. */\nfunction validateHandles(raw: ReadonlyMap<string, RawTable>): void {\n for (const table of raw.values()) {\n const ownKeys = Reflect.ownKeys(table.handle);\n const hasOnlyDeclaredColumns =\n ownKeys.length === Object.keys(table.metas).length &&\n ownKeys.every(\n (key) => typeof key === \"string\" && Object.hasOwn(table.metas, key),\n );\n const hasMetadataCollision = TABLE_METADATA_KEYS.some((key) =>\n Object.hasOwn(table.handle, key),\n );\n if (\n Object.getPrototypeOf(table.handle) !== null ||\n !Object.isExtensible(table.handle) ||\n !hasOnlyDeclaredColumns ||\n hasMetadataCollision\n ) {\n throw new SchemaBuildError(\n `Table handle \"${table.name}\" was modified during schema declaration`,\n );\n }\n }\n}\n\n/** Reject composite primary keys, which keyed operations cannot represent. */\nfunction validatePrimaryKeys(raw: ReadonlyMap<string, RawTable>): void {\n for (const table of raw.values()) {\n const primaryKeys = Object.values(table.metas).filter(\n (meta) => meta.primaryKey,\n );\n if (primaryKeys.length > 1) {\n throw new SchemaBuildError(\n `Table \"${table.name}\" declares multiple primary-key columns; composite primary keys are not supported`,\n );\n }\n }\n}\n\n/** Reject table names that collide with generated Drizzle relation keys. */\nfunction validateRelationKeys(\n raw: ReadonlyMap<string, RawTable>,\n relations: ReadonlyMap<string, readonly ResolvedRelation[]>,\n): void {\n for (const [name, tableRelations] of relations) {\n if (tableRelations.length > 0 && raw.has(`${name}Relations`)) {\n throw new SchemaBuildError(\n `Table \"${name}Relations\" collides with generated relation metadata for \"${name}\"`,\n );\n }\n }\n}\n\n/** Prepare every engine object and validator without mutating handles. */\nfunction prepareTables(\n raw: ReadonlyMap<string, RawTable>,\n schemaName: string,\n relations: ReadonlyMap<string, readonly ResolvedRelation[]>,\n): Map<string, FinalizedTableCandidate> {\n const built = buildEngineTables(raw.values(), schemaName);\n const candidates = new Map<string, FinalizedTableCandidate>();\n for (const table of raw.values()) {\n const builtTable = built.get(table.name);\n if (!builtTable) {\n throw new SchemaBuildError(\n `Engine table \"${table.name}\" was not constructed`,\n );\n }\n const columns = nullRecord<ColumnMeta>();\n for (const [key, meta] of Object.entries(builtTable.columns)) {\n columns[key] = finalizeColumn(meta);\n }\n Object.freeze(columns);\n const tableRelations = finalizeRelations(relations.get(table.name) ?? []);\n const validatorTable = { $columns: columns } as AppKitTable;\n candidates.set(table.name, {\n columns,\n engine: builtTable.engine,\n relations: tableRelations,\n insertSchema: deriveInsertSchema(validatorTable),\n updateSchema: deriveUpdateSchema(validatorTable),\n });\n }\n return candidates;\n}\n\n/** Atomically publish prepared metadata as the final schema transition. */\nfunction publishSchema(\n raw: ReadonlyMap<string, RawTable>,\n candidates: ReadonlyMap<string, FinalizedTableCandidate>,\n schemaName: string,\n): Schema {\n const publications = [...raw.values()].map((table) => {\n const candidate = candidates.get(table.name);\n if (!candidate) {\n throw new SchemaBuildError(\n `Table \"${table.name}\" was not prepared for finalization`,\n );\n }\n return { table, candidate };\n });\n\n const tables = nullRecord<AppKitTable>();\n const engine = nullRecord<Schema[\"$engine\"][string]>();\n for (const { table, candidate } of publications) {\n Object.defineProperties(table.handle, {\n $name: { value: table.name },\n $schemaName: { value: schemaName },\n $columns: { value: candidate.columns },\n $engine: { value: candidate.engine },\n $relations: { value: candidate.relations },\n $insertSchema: { value: candidate.insertSchema },\n $updateSchema: { value: candidate.updateSchema },\n });\n const finalized = Object.freeze(table.handle) as unknown as AppKitTable;\n tables[table.name] = finalized;\n engine[table.name] = candidate.engine;\n }\n\n const schema = {\n $schemaName: schemaName,\n $tables: Object.freeze(tables),\n $engine: Object.freeze(engine),\n };\n Object.defineProperty(schema, FINALIZED_SCHEMA, { value: true });\n return Object.freeze(schema);\n}\n\n/** @internal Reject values that were not finalized by `defineSchema()`. */\nexport function assertFinalizedSchema(value: unknown): asserts value is Schema {\n if (\n value === null ||\n typeof value !== \"object\" ||\n (value as Record<PropertyKey, unknown>)[FINALIZED_SCHEMA] !== true\n ) {\n throw new TypeError(\"Expected a finalized AppKit database schema\");\n }\n}\n\n/**\n * Compile one declared schema. The returned type keeps the table names the\n * builder returned, so `crudRoutes` and `hooks` can name only real tables.\n */\nexport function defineSchema<TTables extends Record<string, AppKitTable>>(\n builder: (context: SchemaBuilderContext) => TTables,\n options?: DefineSchemaOptions,\n): Schema<Extract<keyof TTables, string>> {\n const schemaName = options?.schemaName ?? \"public\";\n if (!schemaName) throw new SchemaBuildError(\"Schema name cannot be empty\");\n\n const state: DeclarationState = {\n raw: new Map(),\n handleNames: new Map(),\n };\n const returned = builder(createBuilderContext(state));\n\n validateReturnedTables(returned, state);\n validateHandles(state.raw);\n validatePrimaryKeys(state.raw);\n resolveForeignKeys(state.raw);\n // FK literals are checked against the inherited target storage, not the placeholder.\n validateLiteralDefaults(state.raw.values());\n\n const relations = buildRelations(state.raw);\n validateRelationKeys(state.raw, relations);\n const candidates = prepareTables(state.raw, schemaName, relations);\n return publishSchema(state.raw, candidates, schemaName);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"define-schema.js","names":[],"sources":["../../../src/database/schema-builder/define-schema.ts"],"sourcesContent":["import { ColumnBuilder, enumColumn, validateLiteralDefaults } from \"./columns\";\nimport { buildEngineTables } from \"./engine/tables\";\nimport { resolveForeignKeys } from \"./fk\";\nimport { buildRelations } from \"./relations\";\nimport {\n type AppKitTable,\n type ColumnMeta,\n type ColumnRef,\n type DefineSchemaOptions,\n type MutableColumnMeta,\n type ResolvedRelation,\n type Schema,\n SchemaBuildError,\n type TableHandle,\n} from \"./types\";\nimport { deriveInsertSchema, deriveUpdateSchema } from \"./validators\";\n\ninterface RawTable {\n readonly name: string;\n readonly metas: Record<string, MutableColumnMeta>;\n readonly handle: Record<string, ColumnRef>;\n}\n\ninterface FinalizedTableCandidate {\n readonly columns: Readonly<Record<string, ColumnMeta>>;\n readonly engine: Schema[\"$engine\"][string];\n readonly relations: readonly ResolvedRelation[];\n readonly insertSchema: unknown;\n readonly updateSchema: unknown;\n}\n\ninterface DeclarationState {\n readonly raw: Map<string, RawTable>;\n readonly handleNames: Map<object, string>;\n}\n\nexport interface SchemaBuilderContext {\n table<C extends Record<string, ColumnBuilder>>(\n name: string,\n columns: C,\n ): TableHandle<C>;\n enum(name: string, values: readonly string[]): ColumnBuilder;\n}\n\nconst RESERVED_OBJECT_KEYS = new Set([\"__proto__\", \"prototype\", \"constructor\"]);\nconst RESERVED_DATABASE_EXPORT_KEYS = new Set([\"sql\", \"transaction\"]);\n// A global symbol lets typegen recognize schemas loaded through another AppKit instance.\nconst FINALIZED_SCHEMA = Symbol.for(\"@databricks/appkit.database.schema\");\nconst TABLE_METADATA_KEYS = [\n \"$name\",\n \"$schemaName\",\n \"$columns\",\n \"$engine\",\n \"$relations\",\n \"$insertSchema\",\n \"$updateSchema\",\n] as const;\n\nfunction nullRecord<T>(): Record<string, T> {\n return Object.create(null) as Record<string, T>;\n}\n\nfunction assertRecord(value: unknown, label: string): asserts value is object {\n if (value === null || typeof value !== \"object\" || Array.isArray(value)) {\n throw new SchemaBuildError(`${label} must be an object`);\n }\n}\n\nfunction assertName(value: string, label: string): void {\n if (!value || RESERVED_OBJECT_KEYS.has(value)) {\n throw new SchemaBuildError(`${label} \"${value}\" is reserved`);\n }\n}\n\nfunction assertTableName(value: string): void {\n assertName(value, \"Table name\");\n if (RESERVED_DATABASE_EXPORT_KEYS.has(value)) {\n throw new SchemaBuildError(`Table name \"${value}\" is reserved`);\n }\n}\n\nfunction finalizeColumn(meta: MutableColumnMeta): ColumnMeta {\n if (!meta.engineColumn) {\n throw new SchemaBuildError(\n `Engine column \"${meta.columnName}\" is missing during finalization`,\n );\n }\n const { fkRef: _fkRef, ...published } = meta;\n return Object.freeze({\n ...published,\n enumValues: meta.enumValues\n ? Object.freeze([...meta.enumValues])\n : undefined,\n fk: meta.fk ? Object.freeze({ ...meta.fk }) : undefined,\n engineColumn: meta.engineColumn,\n }) as ColumnMeta;\n}\n\nfunction finalizeRelations(\n relations: readonly ResolvedRelation[],\n): readonly ResolvedRelation[] {\n return Object.freeze(\n relations.map((relation) => Object.freeze({ ...relation })),\n );\n}\n\n/** Clone builders into per-table metadata and create stable column handles. */\nfunction declareTable<C extends Record<string, ColumnBuilder>>(\n state: DeclarationState,\n name: string,\n columns: C,\n): TableHandle<C> {\n assertTableName(name);\n if (state.raw.has(name)) {\n throw new SchemaBuildError(`Duplicate table \"${name}\"`);\n }\n assertRecord(columns, `Columns for table \"${name}\"`);\n\n const metas = nullRecord<MutableColumnMeta>();\n const handle = Object.create(null) as Record<string, ColumnRef>;\n for (const [key, column] of Object.entries(columns)) {\n assertName(key, `Column name on table \"${name}\"`);\n if (key.startsWith(\"$\") || key === \"and\" || key === \"or\") {\n throw new SchemaBuildError(\n `Column \"${name}.${key}\" collides with AppKit runtime metadata`,\n );\n }\n if (!(column instanceof ColumnBuilder)) {\n throw new SchemaBuildError(\n `Column \"${name}.${key}\" was not created by an AppKit column builder`,\n );\n }\n\n const meta = column._cloneMeta();\n meta.name = key;\n meta.columnName = key;\n metas[key] = meta;\n\n const reference: ColumnRef = Object.freeze({\n __isColumnRef: true,\n tableName: name,\n columnName: key,\n });\n Object.defineProperty(handle, key, {\n enumerable: true,\n value: reference,\n });\n }\n\n state.raw.set(name, { name, metas, handle });\n state.handleNames.set(handle, name);\n return handle as unknown as TableHandle<C>;\n}\n\nfunction createBuilderContext(state: DeclarationState): SchemaBuilderContext {\n return {\n table(name, columns) {\n return declareTable(state, name, columns);\n },\n enum(name, values) {\n return enumColumn(name, values);\n },\n };\n}\n\n/** Require every exact table handle once under its declared identity. */\nfunction validateReturnedTables(\n returned: unknown,\n state: DeclarationState,\n): void {\n assertRecord(returned, \"defineSchema() return value\");\n const returnedHandles = new Set<object>();\n for (const [key, value] of Object.entries(returned)) {\n const name =\n value !== null && typeof value === \"object\"\n ? state.handleNames.get(value)\n : undefined;\n if (!name) {\n throw new SchemaBuildError(\n `defineSchema returned a value for \"${key}\" that was not produced by ctx.table()`,\n );\n }\n if (key !== name) {\n throw new SchemaBuildError(\n `defineSchema returned table \"${name}\" under key \"${key}\"; aliases are not supported`,\n );\n }\n if (returnedHandles.has(value)) {\n throw new SchemaBuildError(`Table \"${name}\" was returned more than once`);\n }\n returnedHandles.add(value);\n }\n if (returnedHandles.size !== state.raw.size) {\n const omitted = [...state.raw.values()]\n .filter((table) => !returnedHandles.has(table.handle))\n .map((table) => table.name);\n throw new SchemaBuildError(\n `defineSchema omitted declared table${omitted.length === 1 ? \"\" : \"s\"}: ${omitted.join(\", \")}`,\n );\n }\n}\n\n/** Detect declaration-handle tampering before metadata publication. */\nfunction validateHandles(raw: ReadonlyMap<string, RawTable>): void {\n for (const table of raw.values()) {\n const ownKeys = Reflect.ownKeys(table.handle);\n const hasOnlyDeclaredColumns =\n ownKeys.length === Object.keys(table.metas).length &&\n ownKeys.every(\n (key) => typeof key === \"string\" && Object.hasOwn(table.metas, key),\n );\n const hasMetadataCollision = TABLE_METADATA_KEYS.some((key) =>\n Object.hasOwn(table.handle, key),\n );\n if (\n Object.getPrototypeOf(table.handle) !== null ||\n !Object.isExtensible(table.handle) ||\n !hasOnlyDeclaredColumns ||\n hasMetadataCollision\n ) {\n throw new SchemaBuildError(\n `Table handle \"${table.name}\" was modified during schema declaration`,\n );\n }\n }\n}\n\n/** Reject composite primary keys, which keyed operations cannot represent. */\nfunction validatePrimaryKeys(raw: ReadonlyMap<string, RawTable>): void {\n for (const table of raw.values()) {\n const primaryKeys = Object.values(table.metas).filter(\n (meta) => meta.primaryKey,\n );\n if (primaryKeys.length > 1) {\n throw new SchemaBuildError(\n `Table \"${table.name}\" declares multiple primary-key columns; composite primary keys are not supported`,\n );\n }\n }\n}\n\n/** Reject table names that collide with generated Drizzle relation keys. */\nfunction validateRelationKeys(\n raw: ReadonlyMap<string, RawTable>,\n relations: ReadonlyMap<string, readonly ResolvedRelation[]>,\n): void {\n for (const [name, tableRelations] of relations) {\n if (tableRelations.length > 0 && raw.has(`${name}Relations`)) {\n throw new SchemaBuildError(\n `Table \"${name}Relations\" collides with generated relation metadata for \"${name}\"`,\n );\n }\n }\n}\n\n/** Prepare every engine object and validator without mutating handles. */\nfunction prepareTables(\n raw: ReadonlyMap<string, RawTable>,\n schemaName: string,\n relations: ReadonlyMap<string, readonly ResolvedRelation[]>,\n): Map<string, FinalizedTableCandidate> {\n const built = buildEngineTables(raw.values(), schemaName);\n const candidates = new Map<string, FinalizedTableCandidate>();\n for (const table of raw.values()) {\n const builtTable = built.get(table.name);\n if (!builtTable) {\n throw new SchemaBuildError(\n `Engine table \"${table.name}\" was not constructed`,\n );\n }\n const columns = nullRecord<ColumnMeta>();\n for (const [key, meta] of Object.entries(builtTable.columns)) {\n columns[key] = finalizeColumn(meta);\n }\n Object.freeze(columns);\n const tableRelations = finalizeRelations(relations.get(table.name) ?? []);\n const validatorTable = { $columns: columns } as AppKitTable;\n candidates.set(table.name, {\n columns,\n engine: builtTable.engine,\n relations: tableRelations,\n insertSchema: deriveInsertSchema(validatorTable),\n updateSchema: deriveUpdateSchema(validatorTable),\n });\n }\n return candidates;\n}\n\n/** Atomically publish prepared metadata as the final schema transition. */\nfunction publishSchema(\n raw: ReadonlyMap<string, RawTable>,\n candidates: ReadonlyMap<string, FinalizedTableCandidate>,\n schemaName: string,\n): Schema {\n const publications = [...raw.values()].map((table) => {\n const candidate = candidates.get(table.name);\n if (!candidate) {\n throw new SchemaBuildError(\n `Table \"${table.name}\" was not prepared for finalization`,\n );\n }\n return { table, candidate };\n });\n\n const tables = nullRecord<AppKitTable>();\n const engine = nullRecord<Schema[\"$engine\"][string]>();\n for (const { table, candidate } of publications) {\n Object.defineProperties(table.handle, {\n $name: { value: table.name },\n $schemaName: { value: schemaName },\n $columns: { value: candidate.columns },\n $engine: { value: candidate.engine },\n $relations: { value: candidate.relations },\n $insertSchema: { value: candidate.insertSchema },\n $updateSchema: { value: candidate.updateSchema },\n });\n const finalized = Object.freeze(table.handle) as unknown as AppKitTable;\n tables[table.name] = finalized;\n engine[table.name] = candidate.engine;\n }\n\n const schema = {\n $schemaName: schemaName,\n $tables: Object.freeze(tables),\n $engine: Object.freeze(engine),\n };\n Object.defineProperty(schema, FINALIZED_SCHEMA, { value: true });\n return Object.freeze(schema);\n}\n\n/** @internal Reject values that were not finalized by `defineSchema()`. */\nexport function assertFinalizedSchema(value: unknown): asserts value is Schema {\n if (\n value === null ||\n typeof value !== \"object\" ||\n (value as Record<PropertyKey, unknown>)[FINALIZED_SCHEMA] !== true\n ) {\n throw new TypeError(\"Expected a finalized AppKit database schema\");\n }\n}\n\n/**\n * Compile one declared schema. The returned type keeps the table names the\n * builder returned, so `api.tables` and `hooks` can name only real tables.\n */\nexport function defineSchema<TTables extends Record<string, AppKitTable>>(\n builder: (context: SchemaBuilderContext) => TTables,\n options?: DefineSchemaOptions,\n): Schema<Extract<keyof TTables, string>> {\n const schemaName = options?.schemaName ?? \"public\";\n if (!schemaName) throw new SchemaBuildError(\"Schema name cannot be empty\");\n\n const state: DeclarationState = {\n raw: new Map(),\n handleNames: new Map(),\n };\n const returned = builder(createBuilderContext(state));\n\n validateReturnedTables(returned, state);\n validateHandles(state.raw);\n validatePrimaryKeys(state.raw);\n resolveForeignKeys(state.raw);\n // FK literals are checked against the inherited target storage, not the placeholder.\n validateLiteralDefaults(state.raw.values());\n\n const relations = buildRelations(state.raw);\n validateRelationKeys(state.raw, relations);\n const candidates = prepareTables(state.raw, schemaName, relations);\n return publishSchema(state.raw, candidates, schemaName);\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { AppKitError } from "./base.js";
|
|
2
|
+
|
|
3
|
+
//#region src/errors/database-validation.d.ts
|
|
4
|
+
/** One rejected field; `path` names public columns, never their values. */
|
|
5
|
+
interface DatabaseValidationIssue {
|
|
6
|
+
readonly path: readonly string[];
|
|
7
|
+
readonly message: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Deliberate validation failure raised by a database mutation hook. Generated
|
|
11
|
+
* routes answer `422` and echo only the issues naming a public column; every
|
|
12
|
+
* other failure raised inside a hook stays an opaque server error.
|
|
13
|
+
*/
|
|
14
|
+
declare class DatabaseValidationError extends AppKitError {
|
|
15
|
+
readonly code = "DATABASE_VALIDATION_ERROR";
|
|
16
|
+
readonly statusCode = 422;
|
|
17
|
+
readonly isRetryable = false;
|
|
18
|
+
readonly issues: readonly DatabaseValidationIssue[];
|
|
19
|
+
constructor(message: string, issues?: readonly DatabaseValidationIssue[]);
|
|
20
|
+
}
|
|
21
|
+
//#endregion
|
|
22
|
+
export { DatabaseValidationError, DatabaseValidationIssue };
|
|
23
|
+
//# sourceMappingURL=database-validation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"database-validation.d.ts","names":[],"sources":["../../src/errors/database-validation.ts"],"mappings":";;;;UAMiB,uBAAA;EAAA,SACN,IAAA;EAAA,SACA,OAAA;AAAA;;;AAQX;;;cAAa,uBAAA,SAAgC,WAAA;EAAA,SAClC,IAAA;EAAA,SACA,UAAA;EAAA,SACA,WAAA;EAAA,SACA,MAAA,WAAiB,uBAAA;cAGxB,OAAA,UACA,MAAA,YAAiB,uBAAA;AAAA"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { AppKitError } from "./base.js";
|
|
2
|
+
|
|
3
|
+
//#region src/errors/database-validation.ts
|
|
4
|
+
/** Cap how many issues one rejection can put on the wire. */
|
|
5
|
+
const MAX_ISSUES = 50;
|
|
6
|
+
/**
|
|
7
|
+
* Deliberate validation failure raised by a database mutation hook. Generated
|
|
8
|
+
* routes answer `422` and echo only the issues naming a public column; every
|
|
9
|
+
* other failure raised inside a hook stays an opaque server error.
|
|
10
|
+
*/
|
|
11
|
+
var DatabaseValidationError = class extends AppKitError {
|
|
12
|
+
code = "DATABASE_VALIDATION_ERROR";
|
|
13
|
+
statusCode = 422;
|
|
14
|
+
isRetryable = false;
|
|
15
|
+
issues;
|
|
16
|
+
constructor(message, issues = []) {
|
|
17
|
+
super(message, { clientMessage: "Database request failed validation" });
|
|
18
|
+
this.issues = issues.slice(0, MAX_ISSUES);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
//#endregion
|
|
23
|
+
export { DatabaseValidationError };
|
|
24
|
+
//# sourceMappingURL=database-validation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"database-validation.js","names":[],"sources":["../../src/errors/database-validation.ts"],"sourcesContent":["import { AppKitError } from \"./base\";\n\n/** Cap how many issues one rejection can put on the wire. */\nconst MAX_ISSUES = 50;\n\n/** One rejected field; `path` names public columns, never their values. */\nexport interface DatabaseValidationIssue {\n readonly path: readonly string[];\n readonly message: string;\n}\n\n/**\n * Deliberate validation failure raised by a database mutation hook. Generated\n * routes answer `422` and echo only the issues naming a public column; every\n * other failure raised inside a hook stays an opaque server error.\n */\nexport class DatabaseValidationError extends AppKitError {\n readonly code = \"DATABASE_VALIDATION_ERROR\";\n readonly statusCode = 422;\n readonly isRetryable = false;\n readonly issues: readonly DatabaseValidationIssue[];\n\n constructor(\n message: string,\n issues: readonly DatabaseValidationIssue[] = [],\n ) {\n super(message, { clientMessage: \"Database request failed validation\" });\n this.issues = issues.slice(0, MAX_ISSUES);\n }\n}\n"],"mappings":";;;;AAGA,MAAM,aAAa;;;;;;AAanB,IAAa,0BAAb,cAA6C,YAAY;CACvD,AAAS,OAAO;CAChB,AAAS,aAAa;CACtB,AAAS,cAAc;CACvB,AAAS;CAET,YACE,SACA,SAA6C,EAAE,EAC/C;AACA,QAAM,SAAS,EAAE,eAAe,sCAAsC,CAAC;AACvE,OAAK,SAAS,OAAO,MAAM,GAAG,WAAW"}
|
package/dist/errors/index.js
CHANGED
|
@@ -2,6 +2,7 @@ import { AppKitError } from "./base.js";
|
|
|
2
2
|
import { AuthenticationError } from "./authentication.js";
|
|
3
3
|
import { ConfigurationError } from "./configuration.js";
|
|
4
4
|
import { ConnectionError } from "./connection.js";
|
|
5
|
+
import { DatabaseValidationError } from "./database-validation.js";
|
|
5
6
|
import { ExecutionError } from "./execution.js";
|
|
6
7
|
import { InitializationError } from "./initialization.js";
|
|
7
8
|
import { ServerError } from "./server.js";
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { WorkspaceClient } from "../workspace-client/index.js";
|
|
2
|
+
|
|
3
|
+
//#region src/evals/dataset.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* One row of a managed evaluation dataset. `inputs` are the kwargs passed to the
|
|
6
|
+
* agent for the turn; `expectations` (when present) is the row's ground truth /
|
|
7
|
+
* guidelines. Mirrors the `{inputs, expectations}` shape of `mlflow.genai`
|
|
8
|
+
* datasets and of the Unity Catalog table backing a managed eval dataset.
|
|
9
|
+
*/
|
|
10
|
+
interface DatasetRow {
|
|
11
|
+
inputs: Record<string, unknown>;
|
|
12
|
+
expectations?: Record<string, unknown>;
|
|
13
|
+
}
|
|
14
|
+
interface ReadEvalDatasetOptions {
|
|
15
|
+
/** Fully-qualified UC table: `catalog.schema.table`. */
|
|
16
|
+
table: string;
|
|
17
|
+
/** SQL warehouse id to run the read against. */
|
|
18
|
+
warehouseId: string;
|
|
19
|
+
/** Optional row cap. */
|
|
20
|
+
limit?: number;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Read a Databricks managed evaluation dataset (a Unity Catalog table with
|
|
24
|
+
* `inputs`/`expectations` columns) into rows, over the public SQL Statement
|
|
25
|
+
* Execution API. Reuses {@link SQLWarehouseConnector} for submit/poll/transform
|
|
26
|
+
* — its result transform already JSON-parses string columns into objects, so
|
|
27
|
+
* `inputs`/`expectations` come back as records whether the table stores them as
|
|
28
|
+
* JSON strings or structs.
|
|
29
|
+
*
|
|
30
|
+
* The Python `mlflow.genai.datasets` API needs a Spark session (no TS
|
|
31
|
+
* equivalent), so we read the backing table directly.
|
|
32
|
+
*/
|
|
33
|
+
declare function readEvalDataset(client: WorkspaceClient, options: ReadEvalDatasetOptions): Promise<DatasetRow[]>;
|
|
34
|
+
//#endregion
|
|
35
|
+
export { DatasetRow, ReadEvalDatasetOptions, readEvalDataset };
|
|
36
|
+
//# sourceMappingURL=dataset.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dataset.d.ts","names":[],"sources":["../../src/evals/dataset.ts"],"mappings":";;;;;AASA;;;;UAAiB,UAAA;EACf,MAAA,EAAQ,MAAA;EACR,YAAA,GAAe,MAAA;AAAA;AAAA,UAGA,sBAAA;EAHM;EAKrB,KAAA;EAFqC;EAIrC,WAAA;EAJqC;EAMrC,KAAA;AAAA;;;;AAyBF;;;;;;;;iBAAsB,eAAA,CACpB,MAAA,EAAQ,eAAA,EACR,OAAA,EAAS,sBAAA,GACR,OAAA,CAAQ,UAAA"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { SQLWarehouseConnector } from "../connectors/sql-warehouse/client.js";
|
|
2
|
+
import "../connectors/index.js";
|
|
3
|
+
|
|
4
|
+
//#region src/evals/dataset.ts
|
|
5
|
+
/** A managed eval dataset is a UC table; only 3-level names are valid. */
|
|
6
|
+
const UC_TABLE = /^[A-Za-z0-9_]+\.[A-Za-z0-9_]+\.[A-Za-z0-9_]+$/;
|
|
7
|
+
/** Coerce a cell (already JSON-parsed by the connector for JSON columns) to a record. */
|
|
8
|
+
function toRecord(value) {
|
|
9
|
+
if (value && typeof value === "object" && !Array.isArray(value)) return value;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Read a Databricks managed evaluation dataset (a Unity Catalog table with
|
|
13
|
+
* `inputs`/`expectations` columns) into rows, over the public SQL Statement
|
|
14
|
+
* Execution API. Reuses {@link SQLWarehouseConnector} for submit/poll/transform
|
|
15
|
+
* — its result transform already JSON-parses string columns into objects, so
|
|
16
|
+
* `inputs`/`expectations` come back as records whether the table stores them as
|
|
17
|
+
* JSON strings or structs.
|
|
18
|
+
*
|
|
19
|
+
* The Python `mlflow.genai.datasets` API needs a Spark session (no TS
|
|
20
|
+
* equivalent), so we read the backing table directly.
|
|
21
|
+
*/
|
|
22
|
+
async function readEvalDataset(client, options) {
|
|
23
|
+
if (!UC_TABLE.test(options.table)) throw new Error(`Invalid dataset table "${options.table}" — expected catalog.schema.table`);
|
|
24
|
+
const limit = typeof options.limit === "number" && options.limit > 0 ? ` LIMIT ${Math.floor(options.limit)}` : "";
|
|
25
|
+
return ((await new SQLWarehouseConnector({}).executeStatement(client, {
|
|
26
|
+
warehouse_id: options.warehouseId,
|
|
27
|
+
statement: `SELECT inputs, expectations FROM ${options.table}${limit}`
|
|
28
|
+
})).result?.data ?? []).map((row) => ({
|
|
29
|
+
inputs: toRecord(row.inputs) ?? {},
|
|
30
|
+
expectations: toRecord(row.expectations)
|
|
31
|
+
}));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
//#endregion
|
|
35
|
+
export { readEvalDataset };
|
|
36
|
+
//# sourceMappingURL=dataset.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dataset.js","names":[],"sources":["../../src/evals/dataset.ts"],"sourcesContent":["import { SQLWarehouseConnector } from \"../connectors\";\nimport type { WorkspaceClient } from \"../workspace-client\";\n\n/**\n * One row of a managed evaluation dataset. `inputs` are the kwargs passed to the\n * agent for the turn; `expectations` (when present) is the row's ground truth /\n * guidelines. Mirrors the `{inputs, expectations}` shape of `mlflow.genai`\n * datasets and of the Unity Catalog table backing a managed eval dataset.\n */\nexport interface DatasetRow {\n inputs: Record<string, unknown>;\n expectations?: Record<string, unknown>;\n}\n\nexport interface ReadEvalDatasetOptions {\n /** Fully-qualified UC table: `catalog.schema.table`. */\n table: string;\n /** SQL warehouse id to run the read against. */\n warehouseId: string;\n /** Optional row cap. */\n limit?: number;\n}\n\n/** A managed eval dataset is a UC table; only 3-level names are valid. */\nconst UC_TABLE = /^[A-Za-z0-9_]+\\.[A-Za-z0-9_]+\\.[A-Za-z0-9_]+$/;\n\n/** Coerce a cell (already JSON-parsed by the connector for JSON columns) to a record. */\nfunction toRecord(value: unknown): Record<string, unknown> | undefined {\n if (value && typeof value === \"object\" && !Array.isArray(value)) {\n return value as Record<string, unknown>;\n }\n return undefined;\n}\n\n/**\n * Read a Databricks managed evaluation dataset (a Unity Catalog table with\n * `inputs`/`expectations` columns) into rows, over the public SQL Statement\n * Execution API. Reuses {@link SQLWarehouseConnector} for submit/poll/transform\n * — its result transform already JSON-parses string columns into objects, so\n * `inputs`/`expectations` come back as records whether the table stores them as\n * JSON strings or structs.\n *\n * The Python `mlflow.genai.datasets` API needs a Spark session (no TS\n * equivalent), so we read the backing table directly.\n */\nexport async function readEvalDataset(\n client: WorkspaceClient,\n options: ReadEvalDatasetOptions,\n): Promise<DatasetRow[]> {\n if (!UC_TABLE.test(options.table)) {\n throw new Error(\n `Invalid dataset table \"${options.table}\" — expected catalog.schema.table`,\n );\n }\n\n const limit =\n typeof options.limit === \"number\" && options.limit > 0\n ? ` LIMIT ${Math.floor(options.limit)}`\n : \"\";\n const connector = new SQLWarehouseConnector({});\n const response = await connector.executeStatement(client, {\n warehouse_id: options.warehouseId,\n statement: `SELECT inputs, expectations FROM ${options.table}${limit}`,\n });\n\n const rows =\n (response.result as { data?: Array<Record<string, unknown>> } | undefined)\n ?.data ?? [];\n\n return rows.map((row) => ({\n inputs: toRecord(row.inputs) ?? {},\n expectations: toRecord(row.expectations),\n }));\n}\n"],"mappings":";;;;;AAwBA,MAAM,WAAW;;AAGjB,SAAS,SAAS,OAAqD;AACrE,KAAI,SAAS,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,MAAM,CAC7D,QAAO;;;;;;;;;;;;;AAgBX,eAAsB,gBACpB,QACA,SACuB;AACvB,KAAI,CAAC,SAAS,KAAK,QAAQ,MAAM,CAC/B,OAAM,IAAI,MACR,0BAA0B,QAAQ,MAAM,mCACzC;CAGH,MAAM,QACJ,OAAO,QAAQ,UAAU,YAAY,QAAQ,QAAQ,IACjD,UAAU,KAAK,MAAM,QAAQ,MAAM,KACnC;AAWN,UATiB,MADC,IAAI,sBAAsB,EAAE,CAAC,CACd,iBAAiB,QAAQ;EACxD,cAAc,QAAQ;EACtB,WAAW,oCAAoC,QAAQ,QAAQ;EAChE,CAAC,EAGU,QACN,QAAQ,EAAE,EAEJ,KAAK,SAAS;EACxB,QAAQ,SAAS,IAAI,OAAO,IAAI,EAAE;EAClC,cAAc,SAAS,IAAI,aAAa;EACzC,EAAE"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { EvalDefinition } from "./types.js";
|
|
2
|
+
|
|
3
|
+
//#region src/evals/define-eval.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Define an agent eval. Default-export the result from a
|
|
6
|
+
* `server/agents/<id>/evals/*.eval.ts` file.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { defineEval, includes } from "@databricks/appkit/beta";
|
|
11
|
+
*
|
|
12
|
+
* export default defineEval({
|
|
13
|
+
* description: "Weather agent basic coverage",
|
|
14
|
+
* async test(t) {
|
|
15
|
+
* await t.send("What's the weather in Brooklyn?");
|
|
16
|
+
* t.succeeded();
|
|
17
|
+
* t.calledTool("get_weather");
|
|
18
|
+
* t.check(t.reply, includes("Sunny"));
|
|
19
|
+
* },
|
|
20
|
+
* });
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
declare function defineEval(def: EvalDefinition): EvalDefinition;
|
|
24
|
+
//#endregion
|
|
25
|
+
export { defineEval };
|
|
26
|
+
//# sourceMappingURL=define-eval.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"define-eval.d.ts","names":[],"sources":["../../src/evals/define-eval.ts"],"mappings":";;;;;AAqBA;;;;;;;;;;;;;;;;;iBAAgB,UAAA,CAAW,GAAA,EAAK,cAAA,GAAiB,cAAA"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
//#region src/evals/define-eval.ts
|
|
2
|
+
/**
|
|
3
|
+
* Define an agent eval. Default-export the result from a
|
|
4
|
+
* `server/agents/<id>/evals/*.eval.ts` file.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* import { defineEval, includes } from "@databricks/appkit/beta";
|
|
9
|
+
*
|
|
10
|
+
* export default defineEval({
|
|
11
|
+
* description: "Weather agent basic coverage",
|
|
12
|
+
* async test(t) {
|
|
13
|
+
* await t.send("What's the weather in Brooklyn?");
|
|
14
|
+
* t.succeeded();
|
|
15
|
+
* t.calledTool("get_weather");
|
|
16
|
+
* t.check(t.reply, includes("Sunny"));
|
|
17
|
+
* },
|
|
18
|
+
* });
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
function defineEval(def) {
|
|
22
|
+
if (typeof def.test !== "function") throw new Error("defineEval: `test` must be a function");
|
|
23
|
+
return def;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
//#endregion
|
|
27
|
+
export { defineEval };
|
|
28
|
+
//# sourceMappingURL=define-eval.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"define-eval.js","names":[],"sources":["../../src/evals/define-eval.ts"],"sourcesContent":["import type { EvalDefinition } from \"./types\";\n\n/**\n * Define an agent eval. Default-export the result from a\n * `server/agents/<id>/evals/*.eval.ts` file.\n *\n * @example\n * ```ts\n * import { defineEval, includes } from \"@databricks/appkit/beta\";\n *\n * export default defineEval({\n * description: \"Weather agent basic coverage\",\n * async test(t) {\n * await t.send(\"What's the weather in Brooklyn?\");\n * t.succeeded();\n * t.calledTool(\"get_weather\");\n * t.check(t.reply, includes(\"Sunny\"));\n * },\n * });\n * ```\n */\nexport function defineEval(def: EvalDefinition): EvalDefinition {\n if (typeof def.test !== \"function\") {\n throw new Error(\"defineEval: `test` must be a function\");\n }\n return def;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAqBA,SAAgB,WAAW,KAAqC;AAC9D,KAAI,OAAO,IAAI,SAAS,WACtB,OAAM,IAAI,MAAM,wCAAwC;AAE1D,QAAO"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
//#region src/evals/discover.d.ts
|
|
2
|
+
/** An eval file found under `server/agents/<agent>/evals/`. */
|
|
3
|
+
interface DiscoveredEval {
|
|
4
|
+
/** Absolute path to the `*.eval.ts` file. */
|
|
5
|
+
file: string;
|
|
6
|
+
/** Id relative to the agent's evals dir, without `.eval.ts` (e.g. `weather/basic`). */
|
|
7
|
+
id: string;
|
|
8
|
+
/** The agent id (the `server/agents/<agent>` directory name). */
|
|
9
|
+
agent: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Discover evals under `<rootDir>/server/agents/<agent>/evals/` — co-located
|
|
13
|
+
* with each agent's `agent.{md,ts}` (same folder-per-agent layout the agents
|
|
14
|
+
* plugin discovers). The agent id is the folder name; the eval id is the file
|
|
15
|
+
* path relative to that evals dir with `.eval.ts` stripped. Sorted + stable.
|
|
16
|
+
*/
|
|
17
|
+
declare function discoverEvalFiles(rootDir: string): DiscoveredEval[];
|
|
18
|
+
//#endregion
|
|
19
|
+
export { DiscoveredEval, discoverEvalFiles };
|
|
20
|
+
//# sourceMappingURL=discover.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discover.d.ts","names":[],"sources":["../../src/evals/discover.ts"],"mappings":";;UAOiB,cAAA;EAAc;EAE7B,IAAA;EAF6B;EAI7B,EAAA;EAAA;EAEA,KAAA;AAAA;;AAoBF;;;;;iBAAgB,iBAAA,CAAkB,OAAA,WAAkB,cAAA"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { agentDirNames } from "../core/agent/agent-dirs.js";
|
|
2
|
+
import { CODE_AGENTS_SOURCE_DIR } from "../core/agent/load-code-agents.js";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { readdirSync } from "node:fs";
|
|
5
|
+
|
|
6
|
+
//#region src/evals/discover.ts
|
|
7
|
+
/** Recursively collect `*.eval.ts` files under `dir`. Empty when `dir` is absent. */
|
|
8
|
+
function evalFilesIn(dir) {
|
|
9
|
+
try {
|
|
10
|
+
return readdirSync(dir, {
|
|
11
|
+
recursive: true,
|
|
12
|
+
withFileTypes: true
|
|
13
|
+
}).filter((e) => e.isFile() && e.name.endsWith(".eval.ts")).map((e) => path.join(e.parentPath, e.name));
|
|
14
|
+
} catch {
|
|
15
|
+
return [];
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Discover evals under `<rootDir>/server/agents/<agent>/evals/` — co-located
|
|
20
|
+
* with each agent's `agent.{md,ts}` (same folder-per-agent layout the agents
|
|
21
|
+
* plugin discovers). The agent id is the folder name; the eval id is the file
|
|
22
|
+
* path relative to that evals dir with `.eval.ts` stripped. Sorted + stable.
|
|
23
|
+
*/
|
|
24
|
+
function discoverEvalFiles(rootDir) {
|
|
25
|
+
const agentsDir = path.join(rootDir, CODE_AGENTS_SOURCE_DIR);
|
|
26
|
+
const out = [];
|
|
27
|
+
let entries;
|
|
28
|
+
try {
|
|
29
|
+
entries = readdirSync(agentsDir, { withFileTypes: true });
|
|
30
|
+
} catch {
|
|
31
|
+
return out;
|
|
32
|
+
}
|
|
33
|
+
for (const agent of agentDirNames(entries)) {
|
|
34
|
+
const evalsDir = path.join(agentsDir, agent, "evals");
|
|
35
|
+
for (const file of evalFilesIn(evalsDir)) {
|
|
36
|
+
const id = path.relative(evalsDir, file).replace(/\.eval\.ts$/, "").split(path.sep).join("/");
|
|
37
|
+
out.push({
|
|
38
|
+
file,
|
|
39
|
+
id,
|
|
40
|
+
agent
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return out.sort((a, b) => a.agent.localeCompare(b.agent) || a.id.localeCompare(b.id));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
//#endregion
|
|
48
|
+
export { discoverEvalFiles };
|
|
49
|
+
//# sourceMappingURL=discover.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discover.js","names":[],"sources":["../../src/evals/discover.ts"],"sourcesContent":["import { type Dirent, readdirSync } from \"node:fs\";\nimport path from \"node:path\";\n\nimport { agentDirNames } from \"../core/agent/agent-dirs\";\nimport { CODE_AGENTS_SOURCE_DIR } from \"../core/agent/load-code-agents\";\n\n/** An eval file found under `server/agents/<agent>/evals/`. */\nexport interface DiscoveredEval {\n /** Absolute path to the `*.eval.ts` file. */\n file: string;\n /** Id relative to the agent's evals dir, without `.eval.ts` (e.g. `weather/basic`). */\n id: string;\n /** The agent id (the `server/agents/<agent>` directory name). */\n agent: string;\n}\n\n/** Recursively collect `*.eval.ts` files under `dir`. Empty when `dir` is absent. */\nfunction evalFilesIn(dir: string): string[] {\n try {\n return readdirSync(dir, { recursive: true, withFileTypes: true })\n .filter((e) => e.isFile() && e.name.endsWith(\".eval.ts\"))\n .map((e) => path.join(e.parentPath, e.name));\n } catch {\n return [];\n }\n}\n\n/**\n * Discover evals under `<rootDir>/server/agents/<agent>/evals/` — co-located\n * with each agent's `agent.{md,ts}` (same folder-per-agent layout the agents\n * plugin discovers). The agent id is the folder name; the eval id is the file\n * path relative to that evals dir with `.eval.ts` stripped. Sorted + stable.\n */\nexport function discoverEvalFiles(rootDir: string): DiscoveredEval[] {\n const agentsDir = path.join(rootDir, CODE_AGENTS_SOURCE_DIR);\n const out: DiscoveredEval[] = [];\n\n let entries: Dirent[];\n try {\n entries = readdirSync(agentsDir, { withFileTypes: true });\n } catch {\n return out;\n }\n\n for (const agent of agentDirNames(entries)) {\n const evalsDir = path.join(agentsDir, agent, \"evals\");\n for (const file of evalFilesIn(evalsDir)) {\n const id = path\n .relative(evalsDir, file)\n .replace(/\\.eval\\.ts$/, \"\")\n .split(path.sep)\n .join(\"/\");\n out.push({ file, id, agent });\n }\n }\n\n return out.sort(\n (a, b) => a.agent.localeCompare(b.agent) || a.id.localeCompare(b.id),\n );\n}\n"],"mappings":";;;;;;;AAiBA,SAAS,YAAY,KAAuB;AAC1C,KAAI;AACF,SAAO,YAAY,KAAK;GAAE,WAAW;GAAM,eAAe;GAAM,CAAC,CAC9D,QAAQ,MAAM,EAAE,QAAQ,IAAI,EAAE,KAAK,SAAS,WAAW,CAAC,CACxD,KAAK,MAAM,KAAK,KAAK,EAAE,YAAY,EAAE,KAAK,CAAC;SACxC;AACN,SAAO,EAAE;;;;;;;;;AAUb,SAAgB,kBAAkB,SAAmC;CACnE,MAAM,YAAY,KAAK,KAAK,SAAS,uBAAuB;CAC5D,MAAM,MAAwB,EAAE;CAEhC,IAAI;AACJ,KAAI;AACF,YAAU,YAAY,WAAW,EAAE,eAAe,MAAM,CAAC;SACnD;AACN,SAAO;;AAGT,MAAK,MAAM,SAAS,cAAc,QAAQ,EAAE;EAC1C,MAAM,WAAW,KAAK,KAAK,WAAW,OAAO,QAAQ;AACrD,OAAK,MAAM,QAAQ,YAAY,SAAS,EAAE;GACxC,MAAM,KAAK,KACR,SAAS,UAAU,KAAK,CACxB,QAAQ,eAAe,GAAG,CAC1B,MAAM,KAAK,IAAI,CACf,KAAK,IAAI;AACZ,OAAI,KAAK;IAAE;IAAM;IAAI;IAAO,CAAC;;;AAIjC,QAAO,IAAI,MACR,GAAG,MAAM,EAAE,MAAM,cAAc,EAAE,MAAM,IAAI,EAAE,GAAG,cAAc,EAAE,GAAG,CACrE"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { EvalDriver } from "./types.js";
|
|
2
|
+
|
|
3
|
+
//#region src/evals/http-driver.d.ts
|
|
4
|
+
interface HttpDriverOptions {
|
|
5
|
+
/** Base URL of the running app, e.g. `http://localhost:3000`. */
|
|
6
|
+
baseUrl: string;
|
|
7
|
+
/** Agent alias to target. Omit to use the app's default agent. */
|
|
8
|
+
agent?: string;
|
|
9
|
+
/** Extra request headers (e.g. auth for a deployed app). */
|
|
10
|
+
headers?: Record<string, string>;
|
|
11
|
+
/** Chat endpoint path. Defaults to `/api/agents/chat`. */
|
|
12
|
+
path?: string;
|
|
13
|
+
/** MLflow run id to link each turn's trace to (for evaluation runs). */
|
|
14
|
+
mlflowRunId?: string;
|
|
15
|
+
/**
|
|
16
|
+
* Max wall-clock time for a single turn before it is abandoned as a failed
|
|
17
|
+
* turn (`succeeded: false`). Without this a hung agent — a blocked tool, a
|
|
18
|
+
* stalled model — never ends the SSE stream (heartbeats keep it alive), so
|
|
19
|
+
* the read loop spins forever and wedges the whole sequential suite.
|
|
20
|
+
* Defaults to 120s.
|
|
21
|
+
*/
|
|
22
|
+
timeoutMs?: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Drives an agent by POSTing to a running app's chat endpoint and parsing the
|
|
26
|
+
* SSE response. Keeps the thread id across `send`s so multi-turn evals share a
|
|
27
|
+
* conversation. Agent/stream errors surface as `succeeded: false` rather than
|
|
28
|
+
* throwing, so `t.succeeded()` can assert on them.
|
|
29
|
+
*/
|
|
30
|
+
declare function createHttpDriver(options: HttpDriverOptions): EvalDriver;
|
|
31
|
+
//#endregion
|
|
32
|
+
export { HttpDriverOptions, createHttpDriver };
|
|
33
|
+
//# sourceMappingURL=http-driver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-driver.d.ts","names":[],"sources":["../../src/evals/http-driver.ts"],"mappings":";;;UAGiB,iBAAA;;EAEf,OAAA;EAFgC;EAIhC,KAAA;EAEgB;EAAhB,OAAA,GAAU,MAAA;EAFV;EAIA,IAAA;EAFU;EAIV,WAAA;EAAA;;;;AA6GF;;;EApGE,SAAA;AAAA;;;;;;;iBAoGc,gBAAA,CAAiB,OAAA,EAAS,iBAAA,GAAoB,UAAA"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { readSseEvents } from "../stream/sse-reader.js";
|
|
2
|
+
|
|
3
|
+
//#region src/evals/http-driver.ts
|
|
4
|
+
/** Record a `function_call` output item once per call id (deduped). */
|
|
5
|
+
function recordToolCall(item, state) {
|
|
6
|
+
if (item?.type !== "function_call" || !item.name) return;
|
|
7
|
+
const key = item.call_id ?? item.name;
|
|
8
|
+
if (state.seen.has(key)) return;
|
|
9
|
+
state.seen.add(key);
|
|
10
|
+
state.toolCalls.push(item.name);
|
|
11
|
+
}
|
|
12
|
+
/** Apply an `appkit.metadata` event's thread/trace ids. */
|
|
13
|
+
function applyMetadata(data, state, setThread) {
|
|
14
|
+
if (data?.threadId) setThread(data.threadId);
|
|
15
|
+
if (data?.mlflowTraceId) state.traceId = data.mlflowTraceId;
|
|
16
|
+
}
|
|
17
|
+
/** Parse a single Responses-API SSE `data:` payload into the running totals. */
|
|
18
|
+
function applyEvent(event, state, setThread) {
|
|
19
|
+
const type = event.type;
|
|
20
|
+
if (type === "response.output_text.delta") {
|
|
21
|
+
if (typeof event.delta === "string") state.reply += event.delta;
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (type === "response.output_item.added" || type === "response.output_item.done") {
|
|
25
|
+
const item = event.item;
|
|
26
|
+
recordToolCall(item, state);
|
|
27
|
+
if (type === "response.output_item.done" && item.type === "message") {
|
|
28
|
+
const text = (item.content ?? []).map((c) => c.text ?? "").join("");
|
|
29
|
+
if (text) state.reply = text;
|
|
30
|
+
}
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (type === "error" || type === "response.failed") {
|
|
34
|
+
state.ok = false;
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (type === "appkit.metadata") applyMetadata(event.data, state, setThread);
|
|
38
|
+
}
|
|
39
|
+
/** Build the chat request payload, including only the optional fields that are set. */
|
|
40
|
+
function buildRequestBody(message, options, threadId) {
|
|
41
|
+
return JSON.stringify({
|
|
42
|
+
message,
|
|
43
|
+
...options.agent ? { agent: options.agent } : {},
|
|
44
|
+
...threadId ? { threadId } : {},
|
|
45
|
+
...options.mlflowRunId ? { mlflowRunId: options.mlflowRunId } : {}
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Drives an agent by POSTing to a running app's chat endpoint and parsing the
|
|
50
|
+
* SSE response. Keeps the thread id across `send`s so multi-turn evals share a
|
|
51
|
+
* conversation. Agent/stream errors surface as `succeeded: false` rather than
|
|
52
|
+
* throwing, so `t.succeeded()` can assert on them.
|
|
53
|
+
*/
|
|
54
|
+
function createHttpDriver(options) {
|
|
55
|
+
const chatPath = options.path ?? "/api/agents/chat";
|
|
56
|
+
const timeoutMs = options.timeoutMs ?? 12e4;
|
|
57
|
+
let threadId;
|
|
58
|
+
return {
|
|
59
|
+
reset() {
|
|
60
|
+
threadId = void 0;
|
|
61
|
+
},
|
|
62
|
+
async send(message) {
|
|
63
|
+
const signal = AbortSignal.timeout(timeoutMs);
|
|
64
|
+
let res;
|
|
65
|
+
try {
|
|
66
|
+
res = await fetch(`${options.baseUrl}${chatPath}`, {
|
|
67
|
+
method: "POST",
|
|
68
|
+
headers: {
|
|
69
|
+
"content-type": "application/json",
|
|
70
|
+
...options.headers
|
|
71
|
+
},
|
|
72
|
+
body: buildRequestBody(message, options, threadId),
|
|
73
|
+
redirect: "manual",
|
|
74
|
+
signal
|
|
75
|
+
});
|
|
76
|
+
} catch {
|
|
77
|
+
return {
|
|
78
|
+
reply: "",
|
|
79
|
+
toolCalls: [],
|
|
80
|
+
succeeded: false
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
if (!res.ok || !res.body) return {
|
|
84
|
+
reply: "",
|
|
85
|
+
toolCalls: [],
|
|
86
|
+
succeeded: false,
|
|
87
|
+
sessionId: threadId
|
|
88
|
+
};
|
|
89
|
+
const state = {
|
|
90
|
+
reply: "",
|
|
91
|
+
toolCalls: [],
|
|
92
|
+
seen: /* @__PURE__ */ new Set(),
|
|
93
|
+
ok: true
|
|
94
|
+
};
|
|
95
|
+
const setThread = (id) => {
|
|
96
|
+
threadId = id;
|
|
97
|
+
};
|
|
98
|
+
try {
|
|
99
|
+
for await (const { event, data } of readSseEvents(res.body, signal)) {
|
|
100
|
+
if (data === "[DONE]") continue;
|
|
101
|
+
if (event === "error") state.ok = false;
|
|
102
|
+
try {
|
|
103
|
+
applyEvent(JSON.parse(data), state, setThread);
|
|
104
|
+
} catch {}
|
|
105
|
+
}
|
|
106
|
+
} catch {
|
|
107
|
+
state.ok = false;
|
|
108
|
+
}
|
|
109
|
+
if (signal.aborted) state.ok = false;
|
|
110
|
+
return {
|
|
111
|
+
reply: state.reply,
|
|
112
|
+
toolCalls: state.toolCalls,
|
|
113
|
+
succeeded: state.ok,
|
|
114
|
+
sessionId: threadId,
|
|
115
|
+
traceId: state.traceId
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
//#endregion
|
|
122
|
+
export { createHttpDriver };
|
|
123
|
+
//# sourceMappingURL=http-driver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-driver.js","names":[],"sources":["../../src/evals/http-driver.ts"],"sourcesContent":["import { readSseEvents } from \"../stream/sse-reader\";\nimport type { DriveResult, EvalDriver } from \"./types\";\n\nexport interface HttpDriverOptions {\n /** Base URL of the running app, e.g. `http://localhost:3000`. */\n baseUrl: string;\n /** Agent alias to target. Omit to use the app's default agent. */\n agent?: string;\n /** Extra request headers (e.g. auth for a deployed app). */\n headers?: Record<string, string>;\n /** Chat endpoint path. Defaults to `/api/agents/chat`. */\n path?: string;\n /** MLflow run id to link each turn's trace to (for evaluation runs). */\n mlflowRunId?: string;\n /**\n * Max wall-clock time for a single turn before it is abandoned as a failed\n * turn (`succeeded: false`). Without this a hung agent — a blocked tool, a\n * stalled model — never ends the SSE stream (heartbeats keep it alive), so\n * the read loop spins forever and wedges the whole sequential suite.\n * Defaults to 120s.\n */\n // ponytail: total per-turn cap; switch to an idle timeout if long legit turns get killed.\n timeoutMs?: number;\n}\n\n/** Mutable running totals accumulated while draining one turn's SSE stream. */\ninterface DriveState {\n reply: string;\n toolCalls: string[];\n seen: Set<string>;\n ok: boolean;\n traceId?: string;\n}\n\n/** Record a `function_call` output item once per call id (deduped). */\nfunction recordToolCall(\n item: { type?: string; name?: string; call_id?: string } | undefined,\n state: DriveState,\n): void {\n if (item?.type !== \"function_call\" || !item.name) return;\n const key = item.call_id ?? item.name;\n if (state.seen.has(key)) return;\n state.seen.add(key);\n state.toolCalls.push(item.name);\n}\n\n/** Apply an `appkit.metadata` event's thread/trace ids. */\nfunction applyMetadata(\n data: { threadId?: string; mlflowTraceId?: string } | undefined,\n state: DriveState,\n setThread: (id: string) => void,\n): void {\n if (data?.threadId) setThread(data.threadId);\n if (data?.mlflowTraceId) state.traceId = data.mlflowTraceId;\n}\n\n/** Parse a single Responses-API SSE `data:` payload into the running totals. */\nfunction applyEvent(\n event: Record<string, unknown>,\n state: DriveState,\n setThread: (id: string) => void,\n): void {\n const type = event.type;\n if (type === \"response.output_text.delta\") {\n if (typeof event.delta === \"string\") state.reply += event.delta;\n return;\n }\n if (\n type === \"response.output_item.added\" ||\n type === \"response.output_item.done\"\n ) {\n const item = event.item as {\n type?: string;\n name?: string;\n call_id?: string;\n content?: Array<{ text?: string }>;\n };\n recordToolCall(item, state);\n // A terminal `message` item carries the full reply text and *replaces*\n // the accumulated deltas — the server emits no delta for a full message\n // (event-translator `handleFullMessage`), so a non-streaming adapter's\n // whole reply arrives only here. Guard on non-empty so a streaming turn's\n // trailing done event can't clobber the deltas with \"\".\n if (type === \"response.output_item.done\" && item.type === \"message\") {\n const text = (item.content ?? []).map((c) => c.text ?? \"\").join(\"\");\n if (text) state.reply = text;\n }\n return;\n }\n if (type === \"error\" || type === \"response.failed\") {\n state.ok = false;\n return;\n }\n if (type === \"appkit.metadata\") {\n applyMetadata(\n event.data as { threadId?: string; mlflowTraceId?: string },\n state,\n setThread,\n );\n }\n}\n\n/** Build the chat request payload, including only the optional fields that are set. */\nfunction buildRequestBody(\n message: string,\n options: HttpDriverOptions,\n threadId: string | undefined,\n): string {\n return JSON.stringify({\n message,\n ...(options.agent ? { agent: options.agent } : {}),\n ...(threadId ? { threadId } : {}),\n ...(options.mlflowRunId ? { mlflowRunId: options.mlflowRunId } : {}),\n });\n}\n\n/**\n * Drives an agent by POSTing to a running app's chat endpoint and parsing the\n * SSE response. Keeps the thread id across `send`s so multi-turn evals share a\n * conversation. Agent/stream errors surface as `succeeded: false` rather than\n * throwing, so `t.succeeded()` can assert on them.\n */\nexport function createHttpDriver(options: HttpDriverOptions): EvalDriver {\n const chatPath = options.path ?? \"/api/agents/chat\";\n const timeoutMs = options.timeoutMs ?? 120_000;\n let threadId: string | undefined;\n\n return {\n reset(): void {\n threadId = undefined;\n },\n async send(message: string): Promise<DriveResult> {\n // Bounds connect + the entire read below. Passed to both the fetch and\n // the SSE reader: on expiry the reader is cancelled and the turn fails.\n const signal = AbortSignal.timeout(timeoutMs);\n let res: Response;\n try {\n res = await fetch(`${options.baseUrl}${chatPath}`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\", ...options.headers },\n body: buildRequestBody(message, options, threadId),\n // A chat endpoint never needs a redirect; following one would replay\n // custom auth headers (`options.headers`) to the redirect target.\n // A 3xx becomes an opaque response (res.ok === false), handled as a\n // failed turn by the !res.ok guard below.\n redirect: \"manual\",\n signal,\n });\n } catch {\n return { reply: \"\", toolCalls: [], succeeded: false };\n }\n\n if (!res.ok || !res.body) {\n return {\n reply: \"\",\n toolCalls: [],\n succeeded: false,\n sessionId: threadId,\n };\n }\n\n const state: DriveState = {\n reply: \"\",\n toolCalls: [],\n seen: new Set<string>(),\n ok: true,\n };\n const setThread = (id: string) => {\n threadId = id;\n };\n try {\n for await (const { event, data } of readSseEvents(res.body, signal)) {\n if (data === \"[DONE]\") continue;\n // A stream-level error frame (a thrown exception in the generator)\n // is framed as `event: error` with a payload that carries no `type`,\n // so the SSE event name — not the payload — is the real signal.\n if (event === \"error\") state.ok = false;\n try {\n applyEvent(\n JSON.parse(data) as Record<string, unknown>,\n state,\n setThread,\n );\n } catch {\n // skip malformed event payloads\n }\n }\n } catch {\n // mid-stream transport error or DoS-guard throw: a broken turn.\n state.ok = false;\n }\n // A timeout ends the iterator cleanly (reader cancel) rather than\n // throwing, so mark an aborted turn failed explicitly.\n if (signal.aborted) state.ok = false;\n\n return {\n reply: state.reply,\n toolCalls: state.toolCalls,\n succeeded: state.ok,\n sessionId: threadId,\n traceId: state.traceId,\n };\n },\n };\n}\n"],"mappings":";;;;AAmCA,SAAS,eACP,MACA,OACM;AACN,KAAI,MAAM,SAAS,mBAAmB,CAAC,KAAK,KAAM;CAClD,MAAM,MAAM,KAAK,WAAW,KAAK;AACjC,KAAI,MAAM,KAAK,IAAI,IAAI,CAAE;AACzB,OAAM,KAAK,IAAI,IAAI;AACnB,OAAM,UAAU,KAAK,KAAK,KAAK;;;AAIjC,SAAS,cACP,MACA,OACA,WACM;AACN,KAAI,MAAM,SAAU,WAAU,KAAK,SAAS;AAC5C,KAAI,MAAM,cAAe,OAAM,UAAU,KAAK;;;AAIhD,SAAS,WACP,OACA,OACA,WACM;CACN,MAAM,OAAO,MAAM;AACnB,KAAI,SAAS,8BAA8B;AACzC,MAAI,OAAO,MAAM,UAAU,SAAU,OAAM,SAAS,MAAM;AAC1D;;AAEF,KACE,SAAS,gCACT,SAAS,6BACT;EACA,MAAM,OAAO,MAAM;AAMnB,iBAAe,MAAM,MAAM;AAM3B,MAAI,SAAS,+BAA+B,KAAK,SAAS,WAAW;GACnE,MAAM,QAAQ,KAAK,WAAW,EAAE,EAAE,KAAK,MAAM,EAAE,QAAQ,GAAG,CAAC,KAAK,GAAG;AACnE,OAAI,KAAM,OAAM,QAAQ;;AAE1B;;AAEF,KAAI,SAAS,WAAW,SAAS,mBAAmB;AAClD,QAAM,KAAK;AACX;;AAEF,KAAI,SAAS,kBACX,eACE,MAAM,MACN,OACA,UACD;;;AAKL,SAAS,iBACP,SACA,SACA,UACQ;AACR,QAAO,KAAK,UAAU;EACpB;EACA,GAAI,QAAQ,QAAQ,EAAE,OAAO,QAAQ,OAAO,GAAG,EAAE;EACjD,GAAI,WAAW,EAAE,UAAU,GAAG,EAAE;EAChC,GAAI,QAAQ,cAAc,EAAE,aAAa,QAAQ,aAAa,GAAG,EAAE;EACpE,CAAC;;;;;;;;AASJ,SAAgB,iBAAiB,SAAwC;CACvE,MAAM,WAAW,QAAQ,QAAQ;CACjC,MAAM,YAAY,QAAQ,aAAa;CACvC,IAAI;AAEJ,QAAO;EACL,QAAc;AACZ,cAAW;;EAEb,MAAM,KAAK,SAAuC;GAGhD,MAAM,SAAS,YAAY,QAAQ,UAAU;GAC7C,IAAI;AACJ,OAAI;AACF,UAAM,MAAM,MAAM,GAAG,QAAQ,UAAU,YAAY;KACjD,QAAQ;KACR,SAAS;MAAE,gBAAgB;MAAoB,GAAG,QAAQ;MAAS;KACnE,MAAM,iBAAiB,SAAS,SAAS,SAAS;KAKlD,UAAU;KACV;KACD,CAAC;WACI;AACN,WAAO;KAAE,OAAO;KAAI,WAAW,EAAE;KAAE,WAAW;KAAO;;AAGvD,OAAI,CAAC,IAAI,MAAM,CAAC,IAAI,KAClB,QAAO;IACL,OAAO;IACP,WAAW,EAAE;IACb,WAAW;IACX,WAAW;IACZ;GAGH,MAAM,QAAoB;IACxB,OAAO;IACP,WAAW,EAAE;IACb,sBAAM,IAAI,KAAa;IACvB,IAAI;IACL;GACD,MAAM,aAAa,OAAe;AAChC,eAAW;;AAEb,OAAI;AACF,eAAW,MAAM,EAAE,OAAO,UAAU,cAAc,IAAI,MAAM,OAAO,EAAE;AACnE,SAAI,SAAS,SAAU;AAIvB,SAAI,UAAU,QAAS,OAAM,KAAK;AAClC,SAAI;AACF,iBACE,KAAK,MAAM,KAAK,EAChB,OACA,UACD;aACK;;WAIJ;AAEN,UAAM,KAAK;;AAIb,OAAI,OAAO,QAAS,OAAM,KAAK;AAE/B,UAAO;IACL,OAAO,MAAM;IACb,WAAW,MAAM;IACjB,WAAW,MAAM;IACjB,WAAW;IACX,SAAS,MAAM;IAChB;;EAEJ"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { DatabricksAuth, ResolveDatabricksAuthOptions, resolveDatabricksAuth, resolveWorkspaceClient } from "../connectors/mlflow/auth.js";
|
|
2
|
+
import { MlflowClient, PostResult, normalizeHost } from "../connectors/mlflow/client.js";
|
|
3
|
+
import "../connectors/mlflow/index.js";
|
|
4
|
+
import { DatasetRow, ReadEvalDatasetOptions, readEvalDataset } from "./dataset.js";
|
|
5
|
+
import { AssertionHandle, AssertionResult, CustomJudgeSpec, DriveResult, EvalDefinition, EvalDriver, EvalResult, MatchResult, Matcher, Severity, TestContext } from "./types.js";
|
|
6
|
+
import { defineEval } from "./define-eval.js";
|
|
7
|
+
import { DiscoveredEval, discoverEvalFiles } from "./discover.js";
|
|
8
|
+
import { HttpDriverOptions, createHttpDriver } from "./http-driver.js";
|
|
9
|
+
import { JudgeConfig, JudgeScore, configureJudge, isJudgeConfigured } from "./judge.js";
|
|
10
|
+
import { equals, includes, matches } from "./matchers.js";
|
|
11
|
+
import { Assessment, ReportOutcome, buildAssessments, reportToMlflow } from "./mlflow-report.js";
|
|
12
|
+
import { EvalSummary, evalGlyph, formatEvalDetail, formatEvalHeadline, formatEvalResults, formatSummaryLine, summarize } from "./report.js";
|
|
13
|
+
import { RunEvalOptions, runEval } from "./run-eval.js";
|
|
14
|
+
import { EvalProgress, EvalRunSummary, RunEvalsOptions, runEvalsInDir } from "./run-evals.js";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { resolveDatabricksAuth, resolveWorkspaceClient } from "../connectors/mlflow/auth.js";
|
|
2
|
+
import { MlflowClient, normalizeHost } from "../connectors/mlflow/client.js";
|
|
3
|
+
import { readEvalDataset } from "./dataset.js";
|
|
4
|
+
import { defineEval } from "./define-eval.js";
|
|
5
|
+
import { discoverEvalFiles } from "./discover.js";
|
|
6
|
+
import { createHttpDriver } from "./http-driver.js";
|
|
7
|
+
import { configureJudge, isJudgeConfigured } from "./judge.js";
|
|
8
|
+
import { equals, includes, matches } from "./matchers.js";
|
|
9
|
+
import { buildAssessments, reportToMlflow } from "./mlflow-report.js";
|
|
10
|
+
import { evalGlyph, formatEvalDetail, formatEvalHeadline, formatEvalResults, formatSummaryLine, summarize } from "./report.js";
|
|
11
|
+
import { runEval } from "./run-eval.js";
|
|
12
|
+
import { runEvalsInDir } from "./run-evals.js";
|
|
13
|
+
|
|
14
|
+
export { };
|