@proofhound/core 0.1.6
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/LICENSE +190 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/infra/index.d.ts +12 -0
- package/dist/infra/index.d.ts.map +1 -0
- package/dist/infra/index.js +27 -0
- package/dist/infra/index.js.map +1 -0
- package/dist/server/channels/mcp/annotation.tools.d.ts +4 -0
- package/dist/server/channels/mcp/annotation.tools.d.ts.map +1 -0
- package/dist/server/channels/mcp/annotation.tools.js +132 -0
- package/dist/server/channels/mcp/annotation.tools.js.map +1 -0
- package/dist/server/channels/mcp/canary-release.tools.d.ts +4 -0
- package/dist/server/channels/mcp/canary-release.tools.d.ts.map +1 -0
- package/dist/server/channels/mcp/canary-release.tools.js +279 -0
- package/dist/server/channels/mcp/canary-release.tools.js.map +1 -0
- package/dist/server/channels/mcp/connector.tools.d.ts +4 -0
- package/dist/server/channels/mcp/connector.tools.d.ts.map +1 -0
- package/dist/server/channels/mcp/connector.tools.js +211 -0
- package/dist/server/channels/mcp/connector.tools.js.map +1 -0
- package/dist/server/channels/mcp/dataset-import.tools.d.ts +4 -0
- package/dist/server/channels/mcp/dataset-import.tools.d.ts.map +1 -0
- package/dist/server/channels/mcp/dataset-import.tools.js +107 -0
- package/dist/server/channels/mcp/dataset-import.tools.js.map +1 -0
- package/dist/server/channels/mcp/dataset.tools.d.ts +4 -0
- package/dist/server/channels/mcp/dataset.tools.d.ts.map +1 -0
- package/dist/server/channels/mcp/dataset.tools.js +166 -0
- package/dist/server/channels/mcp/dataset.tools.js.map +1 -0
- package/dist/server/channels/mcp/experiment.tools.d.ts +4 -0
- package/dist/server/channels/mcp/experiment.tools.d.ts.map +1 -0
- package/dist/server/channels/mcp/experiment.tools.js +133 -0
- package/dist/server/channels/mcp/experiment.tools.js.map +1 -0
- package/dist/server/channels/mcp/index.d.ts +19 -0
- package/dist/server/channels/mcp/index.d.ts.map +1 -0
- package/dist/server/channels/mcp/index.js +35 -0
- package/dist/server/channels/mcp/index.js.map +1 -0
- package/dist/server/channels/mcp/mcp-context.d.ts +30 -0
- package/dist/server/channels/mcp/mcp-context.d.ts.map +1 -0
- package/dist/server/channels/mcp/mcp-context.js +110 -0
- package/dist/server/channels/mcp/mcp-context.js.map +1 -0
- package/dist/server/channels/mcp/mcp-server.factory.d.ts +13 -0
- package/dist/server/channels/mcp/mcp-server.factory.d.ts.map +1 -0
- package/dist/server/channels/mcp/mcp-server.factory.js +42 -0
- package/dist/server/channels/mcp/mcp-server.factory.js.map +1 -0
- package/dist/server/channels/mcp/mcp.controller.d.ts +10 -0
- package/dist/server/channels/mcp/mcp.controller.d.ts.map +1 -0
- package/dist/server/channels/mcp/mcp.controller.js +67 -0
- package/dist/server/channels/mcp/mcp.controller.js.map +1 -0
- package/dist/server/channels/mcp/mcp.module.d.ts +3 -0
- package/dist/server/channels/mcp/mcp.module.d.ts.map +1 -0
- package/dist/server/channels/mcp/mcp.module.js +114 -0
- package/dist/server/channels/mcp/mcp.module.js.map +1 -0
- package/dist/server/channels/mcp/mcp.tokens.d.ts +2 -0
- package/dist/server/channels/mcp/mcp.tokens.d.ts.map +1 -0
- package/dist/server/channels/mcp/mcp.tokens.js +8 -0
- package/dist/server/channels/mcp/mcp.tokens.js.map +1 -0
- package/dist/server/channels/mcp/mcp.transport.d.ts +11 -0
- package/dist/server/channels/mcp/mcp.transport.d.ts.map +1 -0
- package/dist/server/channels/mcp/mcp.transport.js +71 -0
- package/dist/server/channels/mcp/mcp.transport.js.map +1 -0
- package/dist/server/channels/mcp/mcp.types.d.ts +16 -0
- package/dist/server/channels/mcp/mcp.types.d.ts.map +1 -0
- package/dist/server/channels/mcp/mcp.types.js +3 -0
- package/dist/server/channels/mcp/mcp.types.js.map +1 -0
- package/dist/server/channels/mcp/model.tools.d.ts +4 -0
- package/dist/server/channels/mcp/model.tools.d.ts.map +1 -0
- package/dist/server/channels/mcp/model.tools.js +215 -0
- package/dist/server/channels/mcp/model.tools.js.map +1 -0
- package/dist/server/channels/mcp/monitoring.tools.d.ts +4 -0
- package/dist/server/channels/mcp/monitoring.tools.d.ts.map +1 -0
- package/dist/server/channels/mcp/monitoring.tools.js +81 -0
- package/dist/server/channels/mcp/monitoring.tools.js.map +1 -0
- package/dist/server/channels/mcp/optimization.tools.d.ts +4 -0
- package/dist/server/channels/mcp/optimization.tools.d.ts.map +1 -0
- package/dist/server/channels/mcp/optimization.tools.js +104 -0
- package/dist/server/channels/mcp/optimization.tools.js.map +1 -0
- package/dist/server/channels/mcp/prompt.tools.d.ts +5 -0
- package/dist/server/channels/mcp/prompt.tools.d.ts.map +1 -0
- package/dist/server/channels/mcp/prompt.tools.js +258 -0
- package/dist/server/channels/mcp/prompt.tools.js.map +1 -0
- package/dist/server/channels/mcp/quick-start.tools.d.ts +4 -0
- package/dist/server/channels/mcp/quick-start.tools.d.ts.map +1 -0
- package/dist/server/channels/mcp/quick-start.tools.js +48 -0
- package/dist/server/channels/mcp/quick-start.tools.js.map +1 -0
- package/dist/server/channels/mcp/release-line.tools.d.ts +4 -0
- package/dist/server/channels/mcp/release-line.tools.d.ts.map +1 -0
- package/dist/server/channels/mcp/release-line.tools.js +101 -0
- package/dist/server/channels/mcp/release-line.tools.js.map +1 -0
- package/dist/server/channels/mcp/run-result.tools.d.ts +4 -0
- package/dist/server/channels/mcp/run-result.tools.d.ts.map +1 -0
- package/dist/server/channels/mcp/run-result.tools.js +116 -0
- package/dist/server/channels/mcp/run-result.tools.js.map +1 -0
- package/dist/server/channels/mcp/token.tools.d.ts +4 -0
- package/dist/server/channels/mcp/token.tools.d.ts.map +1 -0
- package/dist/server/channels/mcp/token.tools.js +87 -0
- package/dist/server/channels/mcp/token.tools.js.map +1 -0
- package/dist/server/common/access-control.d.ts +5 -0
- package/dist/server/common/access-control.d.ts.map +1 -0
- package/dist/server/common/access-control.js +25 -0
- package/dist/server/common/access-control.js.map +1 -0
- package/dist/server/common/actor-context.d.ts +11 -0
- package/dist/server/common/actor-context.d.ts.map +1 -0
- package/dist/server/common/actor-context.js +8 -0
- package/dist/server/common/actor-context.js.map +1 -0
- package/dist/server/common/contracts/access-control.service.d.ts +6 -0
- package/dist/server/common/contracts/access-control.service.d.ts.map +1 -0
- package/dist/server/common/contracts/access-control.service.js +13 -0
- package/dist/server/common/contracts/access-control.service.js.map +1 -0
- package/dist/server/common/contracts/actor-context.resolver.d.ts +7 -0
- package/dist/server/common/contracts/actor-context.resolver.d.ts.map +1 -0
- package/dist/server/common/contracts/actor-context.resolver.js +15 -0
- package/dist/server/common/contracts/actor-context.resolver.js.map +1 -0
- package/dist/server/common/contracts/connector-context.resolver.d.ts +19 -0
- package/dist/server/common/contracts/connector-context.resolver.d.ts.map +1 -0
- package/dist/server/common/contracts/connector-context.resolver.js +23 -0
- package/dist/server/common/contracts/connector-context.resolver.js.map +1 -0
- package/dist/server/common/contracts/http-actor.guard.d.ts +13 -0
- package/dist/server/common/contracts/http-actor.guard.d.ts.map +1 -0
- package/dist/server/common/contracts/http-actor.guard.js +87 -0
- package/dist/server/common/contracts/http-actor.guard.js.map +1 -0
- package/dist/server/common/contracts/index.d.ts +23 -0
- package/dist/server/common/contracts/index.d.ts.map +1 -0
- package/dist/server/common/contracts/index.js +45 -0
- package/dist/server/common/contracts/index.js.map +1 -0
- package/dist/server/common/contracts/limiter-key.strategy.d.ts +8 -0
- package/dist/server/common/contracts/limiter-key.strategy.d.ts.map +1 -0
- package/dist/server/common/contracts/limiter-key.strategy.js +24 -0
- package/dist/server/common/contracts/limiter-key.strategy.js.map +1 -0
- package/dist/server/common/contracts/local-access-control.service.d.ts +7 -0
- package/dist/server/common/contracts/local-access-control.service.d.ts.map +1 -0
- package/dist/server/common/contracts/local-access-control.service.js +41 -0
- package/dist/server/common/contracts/local-access-control.service.js.map +1 -0
- package/dist/server/common/contracts/local-actor-context.resolver.d.ts +13 -0
- package/dist/server/common/contracts/local-actor-context.resolver.d.ts.map +1 -0
- package/dist/server/common/contracts/local-actor-context.resolver.js +102 -0
- package/dist/server/common/contracts/local-actor-context.resolver.js.map +1 -0
- package/dist/server/common/contracts/local-contracts.module.d.ts +3 -0
- package/dist/server/common/contracts/local-contracts.module.d.ts.map +1 -0
- package/dist/server/common/contracts/local-contracts.module.js +78 -0
- package/dist/server/common/contracts/local-contracts.module.js.map +1 -0
- package/dist/server/common/contracts/local-mcp-auth.resolver.d.ts +12 -0
- package/dist/server/common/contracts/local-mcp-auth.resolver.d.ts.map +1 -0
- package/dist/server/common/contracts/local-mcp-auth.resolver.js +66 -0
- package/dist/server/common/contracts/local-mcp-auth.resolver.js.map +1 -0
- package/dist/server/common/contracts/local-project-context.resolver.d.ts +8 -0
- package/dist/server/common/contracts/local-project-context.resolver.d.ts.map +1 -0
- package/dist/server/common/contracts/local-project-context.resolver.js +27 -0
- package/dist/server/common/contracts/local-project-context.resolver.js.map +1 -0
- package/dist/server/common/contracts/local-user-token.verifier.d.ts +26 -0
- package/dist/server/common/contracts/local-user-token.verifier.d.ts.map +1 -0
- package/dist/server/common/contracts/local-user-token.verifier.js +96 -0
- package/dist/server/common/contracts/local-user-token.verifier.js.map +1 -0
- package/dist/server/common/contracts/mcp-auth.resolver.d.ts +7 -0
- package/dist/server/common/contracts/mcp-auth.resolver.d.ts.map +1 -0
- package/dist/server/common/contracts/mcp-auth.resolver.js +12 -0
- package/dist/server/common/contracts/mcp-auth.resolver.js.map +1 -0
- package/dist/server/common/contracts/project-context.resolver.d.ts +11 -0
- package/dist/server/common/contracts/project-context.resolver.d.ts.map +1 -0
- package/dist/server/common/contracts/project-context.resolver.js +22 -0
- package/dist/server/common/contracts/project-context.resolver.js.map +1 -0
- package/dist/server/common/contracts/quota-policy.hook.d.ts +24 -0
- package/dist/server/common/contracts/quota-policy.hook.d.ts.map +1 -0
- package/dist/server/common/contracts/quota-policy.hook.js +22 -0
- package/dist/server/common/contracts/quota-policy.hook.js.map +1 -0
- package/dist/server/common/contracts/runtime-limits.provider.d.ts +17 -0
- package/dist/server/common/contracts/runtime-limits.provider.d.ts.map +1 -0
- package/dist/server/common/contracts/runtime-limits.provider.js +23 -0
- package/dist/server/common/contracts/runtime-limits.provider.js.map +1 -0
- package/dist/server/common/contracts/token.service.d.ts +11 -0
- package/dist/server/common/contracts/token.service.d.ts.map +1 -0
- package/dist/server/common/contracts/token.service.js +12 -0
- package/dist/server/common/contracts/token.service.js.map +1 -0
- package/dist/server/common/contracts/types.d.ts +45 -0
- package/dist/server/common/contracts/types.d.ts.map +1 -0
- package/dist/server/common/contracts/types.js +8 -0
- package/dist/server/common/contracts/types.js.map +1 -0
- package/dist/server/common/contracts/workflow-authorization.hook.d.ts +9 -0
- package/dist/server/common/contracts/workflow-authorization.hook.d.ts.map +1 -0
- package/dist/server/common/contracts/workflow-authorization.hook.js +27 -0
- package/dist/server/common/contracts/workflow-authorization.hook.js.map +1 -0
- package/dist/server/common/decorators/current-project.decorator.d.ts +5 -0
- package/dist/server/common/decorators/current-project.decorator.d.ts.map +1 -0
- package/dist/server/common/decorators/current-project.decorator.js +22 -0
- package/dist/server/common/decorators/current-project.decorator.js.map +1 -0
- package/dist/server/common/decorators/current-user.decorator.d.ts +13 -0
- package/dist/server/common/decorators/current-user.decorator.d.ts.map +1 -0
- package/dist/server/common/decorators/current-user.decorator.js +9 -0
- package/dist/server/common/decorators/current-user.decorator.js.map +1 -0
- package/dist/server/common/errors/db-error.d.ts +2 -0
- package/dist/server/common/errors/db-error.d.ts.map +1 -0
- package/dist/server/common/errors/db-error.js +12 -0
- package/dist/server/common/errors/db-error.js.map +1 -0
- package/dist/server/common/project-context.d.ts +22 -0
- package/dist/server/common/project-context.d.ts.map +1 -0
- package/dist/server/common/project-context.js +60 -0
- package/dist/server/common/project-context.js.map +1 -0
- package/dist/server/common/project-context.module.d.ts +3 -0
- package/dist/server/common/project-context.module.d.ts.map +1 -0
- package/dist/server/common/project-context.module.js +22 -0
- package/dist/server/common/project-context.module.js.map +1 -0
- package/dist/server/index.d.ts +4 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +8 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/infrastructure/llm/run-result-writer.d.ts +10 -0
- package/dist/server/infrastructure/llm/run-result-writer.d.ts.map +1 -0
- package/dist/server/infrastructure/llm/run-result-writer.js +103 -0
- package/dist/server/infrastructure/llm/run-result-writer.js.map +1 -0
- package/dist/server/infrastructure/orchestration/bullmq.module.d.ts +3 -0
- package/dist/server/infrastructure/orchestration/bullmq.module.d.ts.map +1 -0
- package/dist/server/infrastructure/orchestration/bullmq.module.js +36 -0
- package/dist/server/infrastructure/orchestration/bullmq.module.js.map +1 -0
- package/dist/server/infrastructure/orchestration/bullmq.service.d.ts +10 -0
- package/dist/server/infrastructure/orchestration/bullmq.service.d.ts.map +1 -0
- package/dist/server/infrastructure/orchestration/bullmq.service.js +44 -0
- package/dist/server/infrastructure/orchestration/bullmq.service.js.map +1 -0
- package/dist/server/infrastructure/orchestration/dbos.module.d.ts +3 -0
- package/dist/server/infrastructure/orchestration/dbos.module.d.ts.map +1 -0
- package/dist/server/infrastructure/orchestration/dbos.module.js +21 -0
- package/dist/server/infrastructure/orchestration/dbos.module.js.map +1 -0
- package/dist/server/infrastructure/orchestration/dbos.service.d.ts +7 -0
- package/dist/server/infrastructure/orchestration/dbos.service.d.ts.map +1 -0
- package/dist/server/infrastructure/orchestration/dbos.service.js +43 -0
- package/dist/server/infrastructure/orchestration/dbos.service.js.map +1 -0
- package/dist/server/infrastructure/orchestration/index.d.ts +6 -0
- package/dist/server/infrastructure/orchestration/index.d.ts.map +1 -0
- package/dist/server/infrastructure/orchestration/index.js +14 -0
- package/dist/server/infrastructure/orchestration/index.js.map +1 -0
- package/dist/server/infrastructure/orchestration/orchestration.constants.d.ts +2 -0
- package/dist/server/infrastructure/orchestration/orchestration.constants.d.ts.map +1 -0
- package/dist/server/infrastructure/orchestration/orchestration.constants.js +5 -0
- package/dist/server/infrastructure/orchestration/orchestration.constants.js.map +1 -0
- package/dist/server/infrastructure/orchestration/orchestration.module.d.ts +3 -0
- package/dist/server/infrastructure/orchestration/orchestration.module.d.ts.map +1 -0
- package/dist/server/infrastructure/orchestration/orchestration.module.js +22 -0
- package/dist/server/infrastructure/orchestration/orchestration.module.js.map +1 -0
- package/dist/server/modules/annotation/annotation.controller.d.ts +245 -0
- package/dist/server/modules/annotation/annotation.controller.d.ts.map +1 -0
- package/dist/server/modules/annotation/annotation.controller.js +159 -0
- package/dist/server/modules/annotation/annotation.controller.js.map +1 -0
- package/dist/server/modules/annotation/annotation.module.d.ts +3 -0
- package/dist/server/modules/annotation/annotation.module.d.ts.map +1 -0
- package/dist/server/modules/annotation/annotation.module.js +26 -0
- package/dist/server/modules/annotation/annotation.module.js.map +1 -0
- package/dist/server/modules/annotation/annotation.repository.d.ts +28 -0
- package/dist/server/modules/annotation/annotation.repository.d.ts.map +1 -0
- package/dist/server/modules/annotation/annotation.repository.js +634 -0
- package/dist/server/modules/annotation/annotation.repository.js.map +1 -0
- package/dist/server/modules/annotation/annotation.service.d.ts +26 -0
- package/dist/server/modules/annotation/annotation.service.d.ts.map +1 -0
- package/dist/server/modules/annotation/annotation.service.js +144 -0
- package/dist/server/modules/annotation/annotation.service.js.map +1 -0
- package/dist/server/modules/canary-release/canary-release.controller.d.ts +629 -0
- package/dist/server/modules/canary-release/canary-release.controller.d.ts.map +1 -0
- package/dist/server/modules/canary-release/canary-release.controller.js +244 -0
- package/dist/server/modules/canary-release/canary-release.controller.js.map +1 -0
- package/dist/server/modules/canary-release/canary-release.module.d.ts +3 -0
- package/dist/server/modules/canary-release/canary-release.module.d.ts.map +1 -0
- package/dist/server/modules/canary-release/canary-release.module.js +27 -0
- package/dist/server/modules/canary-release/canary-release.module.js.map +1 -0
- package/dist/server/modules/canary-release/canary-release.repository.d.ts +686 -0
- package/dist/server/modules/canary-release/canary-release.repository.d.ts.map +1 -0
- package/dist/server/modules/canary-release/canary-release.repository.js +673 -0
- package/dist/server/modules/canary-release/canary-release.repository.js.map +1 -0
- package/dist/server/modules/canary-release/canary-release.service.d.ts +55 -0
- package/dist/server/modules/canary-release/canary-release.service.d.ts.map +1 -0
- package/dist/server/modules/canary-release/canary-release.service.js +573 -0
- package/dist/server/modules/canary-release/canary-release.service.js.map +1 -0
- package/dist/server/modules/canary-release/canary-runtime.d.ts +60 -0
- package/dist/server/modules/canary-release/canary-runtime.d.ts.map +1 -0
- package/dist/server/modules/canary-release/canary-runtime.js +328 -0
- package/dist/server/modules/canary-release/canary-runtime.js.map +1 -0
- package/dist/server/modules/connector/connector.controller.d.ts +446 -0
- package/dist/server/modules/connector/connector.controller.d.ts.map +1 -0
- package/dist/server/modules/connector/connector.controller.js +235 -0
- package/dist/server/modules/connector/connector.controller.js.map +1 -0
- package/dist/server/modules/connector/connector.driver-factory.d.ts +66 -0
- package/dist/server/modules/connector/connector.driver-factory.d.ts.map +1 -0
- package/dist/server/modules/connector/connector.driver-factory.js +314 -0
- package/dist/server/modules/connector/connector.driver-factory.js.map +1 -0
- package/dist/server/modules/connector/connector.module.d.ts +3 -0
- package/dist/server/modules/connector/connector.module.d.ts.map +1 -0
- package/dist/server/modules/connector/connector.module.js +28 -0
- package/dist/server/modules/connector/connector.module.js.map +1 -0
- package/dist/server/modules/connector/connector.repository.d.ts +369 -0
- package/dist/server/modules/connector/connector.repository.d.ts.map +1 -0
- package/dist/server/modules/connector/connector.repository.js +323 -0
- package/dist/server/modules/connector/connector.repository.js.map +1 -0
- package/dist/server/modules/connector/connector.service.d.ts +60 -0
- package/dist/server/modules/connector/connector.service.d.ts.map +1 -0
- package/dist/server/modules/connector/connector.service.js +682 -0
- package/dist/server/modules/connector/connector.service.js.map +1 -0
- package/dist/server/modules/dataset/dataset-field-schema.util.d.ts +4 -0
- package/dist/server/modules/dataset/dataset-field-schema.util.d.ts.map +1 -0
- package/dist/server/modules/dataset/dataset-field-schema.util.js +57 -0
- package/dist/server/modules/dataset/dataset-field-schema.util.js.map +1 -0
- package/dist/server/modules/dataset/dataset-import.controller.d.ts +79 -0
- package/dist/server/modules/dataset/dataset-import.controller.d.ts.map +1 -0
- package/dist/server/modules/dataset/dataset-import.controller.js +124 -0
- package/dist/server/modules/dataset/dataset-import.controller.js.map +1 -0
- package/dist/server/modules/dataset/dataset-import.repository.d.ts +63 -0
- package/dist/server/modules/dataset/dataset-import.repository.d.ts.map +1 -0
- package/dist/server/modules/dataset/dataset-import.repository.js +176 -0
- package/dist/server/modules/dataset/dataset-import.repository.js.map +1 -0
- package/dist/server/modules/dataset/dataset-import.service.d.ts +36 -0
- package/dist/server/modules/dataset/dataset-import.service.d.ts.map +1 -0
- package/dist/server/modules/dataset/dataset-import.service.js +227 -0
- package/dist/server/modules/dataset/dataset-import.service.js.map +1 -0
- package/dist/server/modules/dataset/dataset.controller.d.ts +127 -0
- package/dist/server/modules/dataset/dataset.controller.d.ts.map +1 -0
- package/dist/server/modules/dataset/dataset.controller.js +168 -0
- package/dist/server/modules/dataset/dataset.controller.js.map +1 -0
- package/dist/server/modules/dataset/dataset.module.d.ts +3 -0
- package/dist/server/modules/dataset/dataset.module.d.ts.map +1 -0
- package/dist/server/modules/dataset/dataset.module.js +29 -0
- package/dist/server/modules/dataset/dataset.module.js.map +1 -0
- package/dist/server/modules/dataset/dataset.repository.d.ts +75 -0
- package/dist/server/modules/dataset/dataset.repository.d.ts.map +1 -0
- package/dist/server/modules/dataset/dataset.repository.js +238 -0
- package/dist/server/modules/dataset/dataset.repository.js.map +1 -0
- package/dist/server/modules/dataset/dataset.service.d.ts +50 -0
- package/dist/server/modules/dataset/dataset.service.d.ts.map +1 -0
- package/dist/server/modules/dataset/dataset.service.js +343 -0
- package/dist/server/modules/dataset/dataset.service.js.map +1 -0
- package/dist/server/modules/experiment/experiment.aggregator.d.ts +14 -0
- package/dist/server/modules/experiment/experiment.aggregator.d.ts.map +1 -0
- package/dist/server/modules/experiment/experiment.aggregator.js +39 -0
- package/dist/server/modules/experiment/experiment.aggregator.js.map +1 -0
- package/dist/server/modules/experiment/experiment.controller.d.ts +361 -0
- package/dist/server/modules/experiment/experiment.controller.d.ts.map +1 -0
- package/dist/server/modules/experiment/experiment.controller.js +162 -0
- package/dist/server/modules/experiment/experiment.controller.js.map +1 -0
- package/dist/server/modules/experiment/experiment.launcher.d.ts +14 -0
- package/dist/server/modules/experiment/experiment.launcher.d.ts.map +1 -0
- package/dist/server/modules/experiment/experiment.launcher.js +54 -0
- package/dist/server/modules/experiment/experiment.launcher.js.map +1 -0
- package/dist/server/modules/experiment/experiment.module.d.ts +3 -0
- package/dist/server/modules/experiment/experiment.module.d.ts.map +1 -0
- package/dist/server/modules/experiment/experiment.module.js +38 -0
- package/dist/server/modules/experiment/experiment.module.js.map +1 -0
- package/dist/server/modules/experiment/experiment.recovery.d.ts +15 -0
- package/dist/server/modules/experiment/experiment.recovery.d.ts.map +1 -0
- package/dist/server/modules/experiment/experiment.recovery.js +80 -0
- package/dist/server/modules/experiment/experiment.recovery.js.map +1 -0
- package/dist/server/modules/experiment/experiment.renderer.d.ts +2 -0
- package/dist/server/modules/experiment/experiment.renderer.d.ts.map +1 -0
- package/dist/server/modules/experiment/experiment.renderer.js +6 -0
- package/dist/server/modules/experiment/experiment.renderer.js.map +1 -0
- package/dist/server/modules/experiment/experiment.repository.d.ts +88 -0
- package/dist/server/modules/experiment/experiment.repository.d.ts.map +1 -0
- package/dist/server/modules/experiment/experiment.repository.js +173 -0
- package/dist/server/modules/experiment/experiment.repository.js.map +1 -0
- package/dist/server/modules/experiment/experiment.service.d.ts +57 -0
- package/dist/server/modules/experiment/experiment.service.d.ts.map +1 -0
- package/dist/server/modules/experiment/experiment.service.js +539 -0
- package/dist/server/modules/experiment/experiment.service.js.map +1 -0
- package/dist/server/modules/experiment/experiment.workflow.d.ts +51 -0
- package/dist/server/modules/experiment/experiment.workflow.d.ts.map +1 -0
- package/dist/server/modules/experiment/experiment.workflow.js +465 -0
- package/dist/server/modules/experiment/experiment.workflow.js.map +1 -0
- package/dist/server/modules/model/model.controller.d.ts +23 -0
- package/dist/server/modules/model/model.controller.d.ts.map +1 -0
- package/dist/server/modules/model/model.controller.js +76 -0
- package/dist/server/modules/model/model.controller.js.map +1 -0
- package/dist/server/modules/model/model.module.d.ts +3 -0
- package/dist/server/modules/model/model.module.d.ts.map +1 -0
- package/dist/server/modules/model/model.module.js +29 -0
- package/dist/server/modules/model/model.module.js.map +1 -0
- package/dist/server/modules/model/model.repository.d.ts +511 -0
- package/dist/server/modules/model/model.repository.d.ts.map +1 -0
- package/dist/server/modules/model/model.repository.js +181 -0
- package/dist/server/modules/model/model.repository.js.map +1 -0
- package/dist/server/modules/model/model.service.d.ts +79 -0
- package/dist/server/modules/model/model.service.d.ts.map +1 -0
- package/dist/server/modules/model/model.service.js +643 -0
- package/dist/server/modules/model/model.service.js.map +1 -0
- package/dist/server/modules/model/project-model.controller.d.ts +261 -0
- package/dist/server/modules/model/project-model.controller.d.ts.map +1 -0
- package/dist/server/modules/model/project-model.controller.js +198 -0
- package/dist/server/modules/model/project-model.controller.js.map +1 -0
- package/dist/server/modules/monitoring/monitoring.controller.d.ts +204 -0
- package/dist/server/modules/monitoring/monitoring.controller.d.ts.map +1 -0
- package/dist/server/modules/monitoring/monitoring.controller.js +129 -0
- package/dist/server/modules/monitoring/monitoring.controller.js.map +1 -0
- package/dist/server/modules/monitoring/monitoring.module.d.ts +3 -0
- package/dist/server/modules/monitoring/monitoring.module.d.ts.map +1 -0
- package/dist/server/modules/monitoring/monitoring.module.js +26 -0
- package/dist/server/modules/monitoring/monitoring.module.js.map +1 -0
- package/dist/server/modules/monitoring/monitoring.repository.d.ts +15 -0
- package/dist/server/modules/monitoring/monitoring.repository.d.ts.map +1 -0
- package/dist/server/modules/monitoring/monitoring.repository.js +618 -0
- package/dist/server/modules/monitoring/monitoring.repository.js.map +1 -0
- package/dist/server/modules/monitoring/monitoring.service.d.ts +15 -0
- package/dist/server/modules/monitoring/monitoring.service.d.ts.map +1 -0
- package/dist/server/modules/monitoring/monitoring.service.js +70 -0
- package/dist/server/modules/monitoring/monitoring.service.js.map +1 -0
- package/dist/server/modules/optimization/optimization.controller.d.ts +579 -0
- package/dist/server/modules/optimization/optimization.controller.d.ts.map +1 -0
- package/dist/server/modules/optimization/optimization.controller.js +119 -0
- package/dist/server/modules/optimization/optimization.controller.js.map +1 -0
- package/dist/server/modules/optimization/optimization.launcher.d.ts +14 -0
- package/dist/server/modules/optimization/optimization.launcher.d.ts.map +1 -0
- package/dist/server/modules/optimization/optimization.launcher.js +56 -0
- package/dist/server/modules/optimization/optimization.launcher.js.map +1 -0
- package/dist/server/modules/optimization/optimization.module.d.ts +3 -0
- package/dist/server/modules/optimization/optimization.module.d.ts.map +1 -0
- package/dist/server/modules/optimization/optimization.module.js +42 -0
- package/dist/server/modules/optimization/optimization.module.js.map +1 -0
- package/dist/server/modules/optimization/optimization.recovery.d.ts +15 -0
- package/dist/server/modules/optimization/optimization.recovery.d.ts.map +1 -0
- package/dist/server/modules/optimization/optimization.recovery.js +80 -0
- package/dist/server/modules/optimization/optimization.recovery.js.map +1 -0
- package/dist/server/modules/optimization/optimization.repository.d.ts +375 -0
- package/dist/server/modules/optimization/optimization.repository.d.ts.map +1 -0
- package/dist/server/modules/optimization/optimization.repository.js +849 -0
- package/dist/server/modules/optimization/optimization.repository.js.map +1 -0
- package/dist/server/modules/optimization/optimization.service.d.ts +74 -0
- package/dist/server/modules/optimization/optimization.service.d.ts.map +1 -0
- package/dist/server/modules/optimization/optimization.service.js +1753 -0
- package/dist/server/modules/optimization/optimization.service.js.map +1 -0
- package/dist/server/modules/optimization/optimization.workflow.d.ts +118 -0
- package/dist/server/modules/optimization/optimization.workflow.d.ts.map +1 -0
- package/dist/server/modules/optimization/optimization.workflow.js +2007 -0
- package/dist/server/modules/optimization/optimization.workflow.js.map +1 -0
- package/dist/server/modules/production-release/production-release.controller.d.ts +125 -0
- package/dist/server/modules/production-release/production-release.controller.d.ts.map +1 -0
- package/dist/server/modules/production-release/production-release.controller.js +107 -0
- package/dist/server/modules/production-release/production-release.controller.js.map +1 -0
- package/dist/server/modules/production-release/production-release.module.d.ts +3 -0
- package/dist/server/modules/production-release/production-release.module.d.ts.map +1 -0
- package/dist/server/modules/production-release/production-release.module.js +27 -0
- package/dist/server/modules/production-release/production-release.module.js.map +1 -0
- package/dist/server/modules/production-release/production-release.repository.d.ts +109 -0
- package/dist/server/modules/production-release/production-release.repository.d.ts.map +1 -0
- package/dist/server/modules/production-release/production-release.repository.js +372 -0
- package/dist/server/modules/production-release/production-release.repository.js.map +1 -0
- package/dist/server/modules/production-release/production-release.service.d.ts +33 -0
- package/dist/server/modules/production-release/production-release.service.d.ts.map +1 -0
- package/dist/server/modules/production-release/production-release.service.js +370 -0
- package/dist/server/modules/production-release/production-release.service.js.map +1 -0
- package/dist/server/modules/prompt/prompt-try-run.service.d.ts +26 -0
- package/dist/server/modules/prompt/prompt-try-run.service.d.ts.map +1 -0
- package/dist/server/modules/prompt/prompt-try-run.service.js +213 -0
- package/dist/server/modules/prompt/prompt-try-run.service.js.map +1 -0
- package/dist/server/modules/prompt/prompt.controller.d.ts +489 -0
- package/dist/server/modules/prompt/prompt.controller.d.ts.map +1 -0
- package/dist/server/modules/prompt/prompt.controller.js +236 -0
- package/dist/server/modules/prompt/prompt.controller.js.map +1 -0
- package/dist/server/modules/prompt/prompt.module.d.ts +3 -0
- package/dist/server/modules/prompt/prompt.module.d.ts.map +1 -0
- package/dist/server/modules/prompt/prompt.module.js +28 -0
- package/dist/server/modules/prompt/prompt.module.js.map +1 -0
- package/dist/server/modules/prompt/prompt.repository.d.ts +190 -0
- package/dist/server/modules/prompt/prompt.repository.d.ts.map +1 -0
- package/dist/server/modules/prompt/prompt.repository.js +522 -0
- package/dist/server/modules/prompt/prompt.repository.js.map +1 -0
- package/dist/server/modules/prompt/prompt.service.d.ts +46 -0
- package/dist/server/modules/prompt/prompt.service.d.ts.map +1 -0
- package/dist/server/modules/prompt/prompt.service.js +508 -0
- package/dist/server/modules/prompt/prompt.service.js.map +1 -0
- package/dist/server/modules/quick-start/quick-start.controller.d.ts +75 -0
- package/dist/server/modules/quick-start/quick-start.controller.d.ts.map +1 -0
- package/dist/server/modules/quick-start/quick-start.controller.js +86 -0
- package/dist/server/modules/quick-start/quick-start.controller.js.map +1 -0
- package/dist/server/modules/quick-start/quick-start.module.d.ts +3 -0
- package/dist/server/modules/quick-start/quick-start.module.d.ts.map +1 -0
- package/dist/server/modules/quick-start/quick-start.module.js +27 -0
- package/dist/server/modules/quick-start/quick-start.module.js.map +1 -0
- package/dist/server/modules/quick-start/quick-start.service.d.ts +77 -0
- package/dist/server/modules/quick-start/quick-start.service.d.ts.map +1 -0
- package/dist/server/modules/quick-start/quick-start.service.js +130 -0
- package/dist/server/modules/quick-start/quick-start.service.js.map +1 -0
- package/dist/server/modules/release-line/release-line.controller.d.ts +749 -0
- package/dist/server/modules/release-line/release-line.controller.d.ts.map +1 -0
- package/dist/server/modules/release-line/release-line.controller.js +108 -0
- package/dist/server/modules/release-line/release-line.controller.js.map +1 -0
- package/dist/server/modules/release-line/release-line.module.d.ts +3 -0
- package/dist/server/modules/release-line/release-line.module.d.ts.map +1 -0
- package/dist/server/modules/release-line/release-line.module.js +31 -0
- package/dist/server/modules/release-line/release-line.module.js.map +1 -0
- package/dist/server/modules/release-line/release-line.repository.d.ts +89 -0
- package/dist/server/modules/release-line/release-line.repository.d.ts.map +1 -0
- package/dist/server/modules/release-line/release-line.repository.js +834 -0
- package/dist/server/modules/release-line/release-line.repository.js.map +1 -0
- package/dist/server/modules/release-line/release-line.service.d.ts +109 -0
- package/dist/server/modules/release-line/release-line.service.d.ts.map +1 -0
- package/dist/server/modules/release-line/release-line.service.js +287 -0
- package/dist/server/modules/release-line/release-line.service.js.map +1 -0
- package/dist/server/modules/release-line/release-runner.repository.d.ts +89 -0
- package/dist/server/modules/release-line/release-runner.repository.d.ts.map +1 -0
- package/dist/server/modules/release-line/release-runner.repository.js +449 -0
- package/dist/server/modules/release-line/release-runner.repository.js.map +1 -0
- package/dist/server/modules/release-line/release-runner.service.d.ts +40 -0
- package/dist/server/modules/release-line/release-runner.service.d.ts.map +1 -0
- package/dist/server/modules/release-line/release-runner.service.js +417 -0
- package/dist/server/modules/release-line/release-runner.service.js.map +1 -0
- package/dist/server/modules/run-result/run-result.controller.d.ts +129 -0
- package/dist/server/modules/run-result/run-result.controller.d.ts.map +1 -0
- package/dist/server/modules/run-result/run-result.controller.js +103 -0
- package/dist/server/modules/run-result/run-result.controller.js.map +1 -0
- package/dist/server/modules/run-result/run-result.module.d.ts +3 -0
- package/dist/server/modules/run-result/run-result.module.d.ts.map +1 -0
- package/dist/server/modules/run-result/run-result.module.js +26 -0
- package/dist/server/modules/run-result/run-result.module.js.map +1 -0
- package/dist/server/modules/run-result/run-result.repository.d.ts +27 -0
- package/dist/server/modules/run-result/run-result.repository.d.ts.map +1 -0
- package/dist/server/modules/run-result/run-result.repository.js +521 -0
- package/dist/server/modules/run-result/run-result.repository.js.map +1 -0
- package/dist/server/modules/run-result/run-result.service.d.ts +22 -0
- package/dist/server/modules/run-result/run-result.service.d.ts.map +1 -0
- package/dist/server/modules/run-result/run-result.service.js +61 -0
- package/dist/server/modules/run-result/run-result.service.js.map +1 -0
- package/dist/server/modules/token/token.controller.d.ts +57 -0
- package/dist/server/modules/token/token.controller.d.ts.map +1 -0
- package/dist/server/modules/token/token.controller.js +99 -0
- package/dist/server/modules/token/token.controller.js.map +1 -0
- package/dist/server/modules/token/token.module.d.ts +3 -0
- package/dist/server/modules/token/token.module.d.ts.map +1 -0
- package/dist/server/modules/token/token.module.js +22 -0
- package/dist/server/modules/token/token.module.js.map +1 -0
- package/dist/server/modules/token/token.repository.d.ts +264 -0
- package/dist/server/modules/token/token.repository.d.ts.map +1 -0
- package/dist/server/modules/token/token.repository.js +97 -0
- package/dist/server/modules/token/token.repository.js.map +1 -0
- package/dist/server/modules/token/token.service.d.ts +23 -0
- package/dist/server/modules/token/token.service.d.ts.map +1 -0
- package/dist/server/modules/token/token.service.js +124 -0
- package/dist/server/modules/token/token.service.js.map +1 -0
- package/dist/server/proofhound-server.module.d.ts +7 -0
- package/dist/server/proofhound-server.module.d.ts.map +1 -0
- package/dist/server/proofhound-server.module.js +72 -0
- package/dist/server/proofhound-server.module.js.map +1 -0
- package/dist/shared/config/config.module.d.ts +3 -0
- package/dist/shared/config/config.module.d.ts.map +1 -0
- package/dist/shared/config/config.module.js +33 -0
- package/dist/shared/config/config.module.js.map +1 -0
- package/dist/shared/crypto/crypto.module.d.ts +3 -0
- package/dist/shared/crypto/crypto.module.d.ts.map +1 -0
- package/dist/shared/crypto/crypto.module.js +24 -0
- package/dist/shared/crypto/crypto.module.js.map +1 -0
- package/dist/shared/crypto/crypto.service.d.ts +9 -0
- package/dist/shared/crypto/crypto.service.d.ts.map +1 -0
- package/dist/shared/crypto/crypto.service.js +37 -0
- package/dist/shared/crypto/crypto.service.js.map +1 -0
- package/dist/shared/database/database.constants.d.ts +2 -0
- package/dist/shared/database/database.constants.d.ts.map +1 -0
- package/dist/shared/database/database.constants.js +5 -0
- package/dist/shared/database/database.constants.js.map +1 -0
- package/dist/shared/database/database.module.d.ts +3 -0
- package/dist/shared/database/database.module.d.ts.map +1 -0
- package/dist/shared/database/database.module.js +27 -0
- package/dist/shared/database/database.module.js.map +1 -0
- package/dist/shared/filters/pino-exception.filter.d.ts +9 -0
- package/dist/shared/filters/pino-exception.filter.d.ts.map +1 -0
- package/dist/shared/filters/pino-exception.filter.js +58 -0
- package/dist/shared/filters/pino-exception.filter.js.map +1 -0
- package/dist/shared/health/health.controller.d.ts +11 -0
- package/dist/shared/health/health.controller.d.ts.map +1 -0
- package/dist/shared/health/health.controller.js +51 -0
- package/dist/shared/health/health.controller.js.map +1 -0
- package/dist/shared/health/health.service.d.ts +25 -0
- package/dist/shared/health/health.service.d.ts.map +1 -0
- package/dist/shared/health/health.service.js +74 -0
- package/dist/shared/health/health.service.js.map +1 -0
- package/dist/shared/llm/runtime-limits.d.ts +4 -0
- package/dist/shared/llm/runtime-limits.d.ts.map +1 -0
- package/dist/shared/llm/runtime-limits.js +23 -0
- package/dist/shared/llm/runtime-limits.js.map +1 -0
- package/dist/shared/redis/redis-mutex.service.d.ts +20 -0
- package/dist/shared/redis/redis-mutex.service.d.ts.map +1 -0
- package/dist/shared/redis/redis-mutex.service.js +75 -0
- package/dist/shared/redis/redis-mutex.service.js.map +1 -0
- package/dist/shared/redis/redis.constants.d.ts +3 -0
- package/dist/shared/redis/redis.constants.d.ts.map +1 -0
- package/dist/shared/redis/redis.constants.js +6 -0
- package/dist/shared/redis/redis.constants.js.map +1 -0
- package/dist/shared/redis/redis.module.d.ts +8 -0
- package/dist/shared/redis/redis.module.d.ts.map +1 -0
- package/dist/shared/redis/redis.module.js +54 -0
- package/dist/shared/redis/redis.module.js.map +1 -0
- package/dist/shared/runtime-module-options.d.ts +5 -0
- package/dist/shared/runtime-module-options.d.ts.map +1 -0
- package/dist/shared/runtime-module-options.js +3 -0
- package/dist/shared/runtime-module-options.js.map +1 -0
- package/dist/webhook/channels/webhook/local-connector-context.resolver.d.ts +8 -0
- package/dist/webhook/channels/webhook/local-connector-context.resolver.d.ts.map +1 -0
- package/dist/webhook/channels/webhook/local-connector-context.resolver.js +63 -0
- package/dist/webhook/channels/webhook/local-connector-context.resolver.js.map +1 -0
- package/dist/webhook/channels/webhook/webhook-token.util.d.ts +4 -0
- package/dist/webhook/channels/webhook/webhook-token.util.d.ts.map +1 -0
- package/dist/webhook/channels/webhook/webhook-token.util.js +26 -0
- package/dist/webhook/channels/webhook/webhook-token.util.js.map +1 -0
- package/dist/webhook/channels/webhook/webhook.controller.d.ts +226 -0
- package/dist/webhook/channels/webhook/webhook.controller.d.ts.map +1 -0
- package/dist/webhook/channels/webhook/webhook.controller.js +121 -0
- package/dist/webhook/channels/webhook/webhook.controller.js.map +1 -0
- package/dist/webhook/channels/webhook/webhook.module.d.ts +3 -0
- package/dist/webhook/channels/webhook/webhook.module.d.ts.map +1 -0
- package/dist/webhook/channels/webhook/webhook.module.js +27 -0
- package/dist/webhook/channels/webhook/webhook.module.js.map +1 -0
- package/dist/webhook/channels/webhook/webhook.repository.d.ts +85 -0
- package/dist/webhook/channels/webhook/webhook.repository.d.ts.map +1 -0
- package/dist/webhook/channels/webhook/webhook.repository.js +268 -0
- package/dist/webhook/channels/webhook/webhook.repository.js.map +1 -0
- package/dist/webhook/channels/webhook/webhook.service.d.ts +144 -0
- package/dist/webhook/channels/webhook/webhook.service.d.ts.map +1 -0
- package/dist/webhook/channels/webhook/webhook.service.js +494 -0
- package/dist/webhook/channels/webhook/webhook.service.js.map +1 -0
- package/dist/webhook/index.d.ts +4 -0
- package/dist/webhook/index.d.ts.map +1 -0
- package/dist/webhook/index.js +8 -0
- package/dist/webhook/index.js.map +1 -0
- package/dist/webhook/infrastructure/orchestration/bullmq.module.d.ts +3 -0
- package/dist/webhook/infrastructure/orchestration/bullmq.module.d.ts.map +1 -0
- package/dist/webhook/infrastructure/orchestration/bullmq.module.js +34 -0
- package/dist/webhook/infrastructure/orchestration/bullmq.module.js.map +1 -0
- package/dist/webhook/infrastructure/orchestration/bullmq.service.d.ts +10 -0
- package/dist/webhook/infrastructure/orchestration/bullmq.service.d.ts.map +1 -0
- package/dist/webhook/infrastructure/orchestration/bullmq.service.js +42 -0
- package/dist/webhook/infrastructure/orchestration/bullmq.service.js.map +1 -0
- package/dist/webhook/proofhound-webhook.module.d.ts +7 -0
- package/dist/webhook/proofhound-webhook.module.d.ts.map +1 -0
- package/dist/webhook/proofhound-webhook.module.js +31 -0
- package/dist/webhook/proofhound-webhook.module.js.map +1 -0
- package/dist/worker/config/worker-concurrency.d.ts +3 -0
- package/dist/worker/config/worker-concurrency.d.ts.map +1 -0
- package/dist/worker/config/worker-concurrency.js +10 -0
- package/dist/worker/config/worker-concurrency.js.map +1 -0
- package/dist/worker/consumers/llm.consumer.d.ts +29 -0
- package/dist/worker/consumers/llm.consumer.d.ts.map +1 -0
- package/dist/worker/consumers/llm.consumer.js +234 -0
- package/dist/worker/consumers/llm.consumer.js.map +1 -0
- package/dist/worker/consumers/probe.consumer.d.ts +16 -0
- package/dist/worker/consumers/probe.consumer.d.ts.map +1 -0
- package/dist/worker/consumers/probe.consumer.js +76 -0
- package/dist/worker/consumers/probe.consumer.js.map +1 -0
- package/dist/worker/index.d.ts +4 -0
- package/dist/worker/index.d.ts.map +1 -0
- package/dist/worker/index.js +9 -0
- package/dist/worker/index.js.map +1 -0
- package/dist/worker/infrastructure/llm/model-secret.provider.d.ts +4 -0
- package/dist/worker/infrastructure/llm/model-secret.provider.d.ts.map +1 -0
- package/dist/worker/infrastructure/llm/model-secret.provider.js +14 -0
- package/dist/worker/infrastructure/llm/model-secret.provider.js.map +1 -0
- package/dist/worker/proofhound-worker.module.d.ts +7 -0
- package/dist/worker/proofhound-worker.module.d.ts.map +1 -0
- package/dist/worker/proofhound-worker.module.js +44 -0
- package/dist/worker/proofhound-worker.module.js.map +1 -0
- package/dist/worker/runners/llm-runner.d.ts +42 -0
- package/dist/worker/runners/llm-runner.d.ts.map +1 -0
- package/dist/worker/runners/llm-runner.js +178 -0
- package/dist/worker/runners/llm-runner.js.map +1 -0
- package/dist/worker/runners/model-secret.d.ts +11 -0
- package/dist/worker/runners/model-secret.d.ts.map +1 -0
- package/dist/worker/runners/model-secret.js +14 -0
- package/dist/worker/runners/model-secret.js.map +1 -0
- package/dist/worker/runners/probe-runner.d.ts +22 -0
- package/dist/worker/runners/probe-runner.d.ts.map +1 -0
- package/dist/worker/runners/probe-runner.js +52 -0
- package/dist/worker/runners/probe-runner.js.map +1 -0
- package/dist/worker/runners/run-result-writer.d.ts +10 -0
- package/dist/worker/runners/run-result-writer.d.ts.map +1 -0
- package/dist/worker/runners/run-result-writer.js +84 -0
- package/dist/worker/runners/run-result-writer.js.map +1 -0
- package/dist/worker/scripts/probe-model-from-env.d.ts +2 -0
- package/dist/worker/scripts/probe-model-from-env.d.ts.map +1 -0
- package/dist/worker/scripts/probe-model-from-env.js +130 -0
- package/dist/worker/scripts/probe-model-from-env.js.map +1 -0
- package/package.json +93 -0
|
@@ -0,0 +1,2007 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.OptimizationWorkflowRegistrar = exports.SYSTEM_ACTOR_OPTIMIZATION = void 0;
|
|
16
|
+
exports.reconstructAnalysisFromRunResult = reconstructAnalysisFromRunResult;
|
|
17
|
+
exports.reconstructGenerateFromRunResult = reconstructGenerateFromRunResult;
|
|
18
|
+
exports.extractAppliedTipsFromGenerateParsedOutput = extractAppliedTipsFromGenerateParsedOutput;
|
|
19
|
+
exports.parseChildRunConfigFromOptimization = parseChildRunConfigFromOptimization;
|
|
20
|
+
exports.toLoopFieldWhitelist = toLoopFieldWhitelist;
|
|
21
|
+
exports.parseVariables = parseVariables;
|
|
22
|
+
exports.isPromptBaselineBootstrapNeeded = isPromptBaselineBootstrapNeeded;
|
|
23
|
+
exports.mapFirstVersionErrorReason = mapFirstVersionErrorReason;
|
|
24
|
+
exports.pickRandomSamples = pickRandomSamples;
|
|
25
|
+
exports.computeOptimizationBaselineExperimentId = computeOptimizationBaselineExperimentId;
|
|
26
|
+
exports.buildOptimizationExperimentName = buildOptimizationExperimentName;
|
|
27
|
+
exports.deriveJudgmentRulesFromOutputSchema = deriveJudgmentRulesFromOutputSchema;
|
|
28
|
+
exports.readExpectedField = readExpectedField;
|
|
29
|
+
exports.buildSamplesForStrategy = buildSamplesForStrategy;
|
|
30
|
+
exports.computeOptimizationVersionId = computeOptimizationVersionId;
|
|
31
|
+
exports.computeOptimizationExperimentId = computeOptimizationExperimentId;
|
|
32
|
+
const node_crypto_1 = require("node:crypto");
|
|
33
|
+
const common_1 = require("@nestjs/common");
|
|
34
|
+
const dbos_sdk_1 = require("@dbos-inc/dbos-sdk");
|
|
35
|
+
const optimization_strategy_1 = require("@proofhound/optimization-strategy");
|
|
36
|
+
const db_1 = require("@proofhound/db");
|
|
37
|
+
const limiter_1 = require("@proofhound/limiter");
|
|
38
|
+
const logger_1 = require("@proofhound/logger");
|
|
39
|
+
const shared_1 = require("@proofhound/shared");
|
|
40
|
+
const drizzle_orm_1 = require("drizzle-orm");
|
|
41
|
+
const zod_1 = require("zod");
|
|
42
|
+
const limiter_key_strategy_1 = require("../../common/contracts/limiter-key.strategy");
|
|
43
|
+
const quota_policy_hook_1 = require("../../common/contracts/quota-policy.hook");
|
|
44
|
+
const runtime_limits_provider_1 = require("../../common/contracts/runtime-limits.provider");
|
|
45
|
+
const crypto_service_1 = require("../../../shared/crypto/crypto.service");
|
|
46
|
+
const database_constants_1 = require("../../../shared/database/database.constants");
|
|
47
|
+
const run_result_writer_1 = require("../../infrastructure/llm/run-result-writer");
|
|
48
|
+
const redis_constants_1 = require("../../../shared/redis/redis.constants");
|
|
49
|
+
const runtime_limits_1 = require("../../../shared/llm/runtime-limits");
|
|
50
|
+
const experiment_service_1 = require("../experiment/experiment.service");
|
|
51
|
+
const experiment_workflow_1 = require("../experiment/experiment.workflow");
|
|
52
|
+
const prompt_repository_1 = require("../prompt/prompt.repository");
|
|
53
|
+
const optimization_repository_1 = require("./optimization.repository");
|
|
54
|
+
const { models, runResults, experiments } = db_1.schema;
|
|
55
|
+
// Namespace UUID (chosen randomly and pinned to stay stable across restarts) — distinct from ExperimentWorkflow to avoid hash collisions
|
|
56
|
+
const OPTIMIZATION_NS = '4a3f1b9e-5d7c-4f2a-9e1b-3c2d8e7a4f01';
|
|
57
|
+
const POLL_SLEEP_SCHEDULE_SEC = [3, 3, 5, 8, 10, 15];
|
|
58
|
+
const POLL_TIMEOUT_SEC = 60 * 60; // 1h default cap for a single round of child experiment; long-running runs can override in runConfig
|
|
59
|
+
const OPTIMIZATION_EXPERIMENT_NAME_MAX_LENGTH = 200;
|
|
60
|
+
const OPTIMIZATION_EXPERIMENT_NAME_SEPARATOR = ' · ';
|
|
61
|
+
const OPTIMIZATION_EXPERIMENT_NAME_FALLBACK = 'optimization';
|
|
62
|
+
// System actor: used by workflow / service to represent "the system" when calling ExperimentService.controlExperiment.
|
|
63
|
+
// The OSS edition does not maintain user / audit tables; keep a stable actor id for log and business-field traceability.
|
|
64
|
+
exports.SYSTEM_ACTOR_OPTIMIZATION = {
|
|
65
|
+
sub: '00000000-0000-0000-0000-000000000000',
|
|
66
|
+
email: 'system@proofhound.local',
|
|
67
|
+
isSuperAdmin: true,
|
|
68
|
+
isActive: true,
|
|
69
|
+
};
|
|
70
|
+
let OptimizationWorkflowRegistrar = class OptimizationWorkflowRegistrar extends dbos_sdk_1.ConfiguredInstance {
|
|
71
|
+
constructor(db, repo, promptRepo, experimentWorkflow, experimentService, crypto, limiter, runResultWriter, limiterKeyStrategy, runtimeLimitsProvider, quotaPolicy) {
|
|
72
|
+
super('optimization-workflow');
|
|
73
|
+
this.db = db;
|
|
74
|
+
this.repo = repo;
|
|
75
|
+
this.promptRepo = promptRepo;
|
|
76
|
+
this.experimentWorkflow = experimentWorkflow;
|
|
77
|
+
this.experimentService = experimentService;
|
|
78
|
+
this.crypto = crypto;
|
|
79
|
+
this.limiter = limiter;
|
|
80
|
+
this.runResultWriter = runResultWriter;
|
|
81
|
+
this.limiterKeyStrategy = limiterKeyStrategy;
|
|
82
|
+
this.runtimeLimitsProvider = runtimeLimitsProvider;
|
|
83
|
+
this.quotaPolicy = quotaPolicy;
|
|
84
|
+
this.logger = (0, logger_1.createLogger)('optimization.workflow', { service: 'server' });
|
|
85
|
+
this.llmLogger = (0, logger_1.createLogger)('optimization.workflow.llm', {
|
|
86
|
+
service: 'server',
|
|
87
|
+
});
|
|
88
|
+
this.loadConfigStep = dbos_sdk_1.DBOS.registerStep(this.loadConfigImpl.bind(this), {
|
|
89
|
+
name: 'optimization.loadConfig',
|
|
90
|
+
});
|
|
91
|
+
this.markStartedStep = dbos_sdk_1.DBOS.registerStep(this.markStartedImpl.bind(this), {
|
|
92
|
+
name: 'optimization.markStarted',
|
|
93
|
+
});
|
|
94
|
+
this.generateFirstVersionStep = dbos_sdk_1.DBOS.registerStep(this.generateFirstVersionImpl.bind(this), {
|
|
95
|
+
name: 'optimization.generateFirstVersion',
|
|
96
|
+
});
|
|
97
|
+
this.preparePromptBaselineStep = dbos_sdk_1.DBOS.registerStep(this.preparePromptBaselineImpl.bind(this), {
|
|
98
|
+
name: 'optimization.preparePromptBaseline',
|
|
99
|
+
});
|
|
100
|
+
this.recordBaselineWorkflowIdStep = dbos_sdk_1.DBOS.registerStep(this.recordBaselineWorkflowIdImpl.bind(this), {
|
|
101
|
+
name: 'optimization.recordBaselineWorkflowId',
|
|
102
|
+
});
|
|
103
|
+
this.markPromptBaselineFailedStep = dbos_sdk_1.DBOS.registerStep(this.markPromptBaselineFailedImpl.bind(this), {
|
|
104
|
+
name: 'optimization.markPromptBaselineFailed',
|
|
105
|
+
});
|
|
106
|
+
this.finalizePromptBaselineStep = dbos_sdk_1.DBOS.registerStep(this.finalizePromptBaselineImpl.bind(this), {
|
|
107
|
+
name: 'optimization.finalizePromptBaseline',
|
|
108
|
+
});
|
|
109
|
+
this.readStateStep = dbos_sdk_1.DBOS.registerStep(this.readStateImpl.bind(this), {
|
|
110
|
+
name: 'optimization.readState',
|
|
111
|
+
});
|
|
112
|
+
this.clearResumeStep = dbos_sdk_1.DBOS.registerStep(this.clearResumeImpl.bind(this), {
|
|
113
|
+
name: 'optimization.clearResume',
|
|
114
|
+
});
|
|
115
|
+
this.prepareRoundStep = dbos_sdk_1.DBOS.registerStep(this.prepareRoundImpl.bind(this), {
|
|
116
|
+
name: 'optimization.prepareRound',
|
|
117
|
+
});
|
|
118
|
+
this.finalizeRoundStep = dbos_sdk_1.DBOS.registerStep(this.finalizeRoundImpl.bind(this), {
|
|
119
|
+
name: 'optimization.finalizeRound',
|
|
120
|
+
});
|
|
121
|
+
this.markChildLaunchFailedStep = dbos_sdk_1.DBOS.registerStep(this.markChildLaunchFailedImpl.bind(this), {
|
|
122
|
+
name: 'optimization.markChildLaunchFailed',
|
|
123
|
+
});
|
|
124
|
+
this.finalizeStep = dbos_sdk_1.DBOS.registerStep(this.finalizeImpl.bind(this), {
|
|
125
|
+
name: 'optimization.finalize',
|
|
126
|
+
});
|
|
127
|
+
this.loadRoundHistoryStep = dbos_sdk_1.DBOS.registerStep(this.loadRoundHistoryImpl.bind(this), {
|
|
128
|
+
name: 'optimization.loadRoundHistory',
|
|
129
|
+
});
|
|
130
|
+
this.peekOptimizationRunResultStep = dbos_sdk_1.DBOS.registerStep(this.peekOptimizationRunResultImpl.bind(this), {
|
|
131
|
+
name: 'optimization.peekRunResult',
|
|
132
|
+
});
|
|
133
|
+
this.controlChildExperimentStep = dbos_sdk_1.DBOS.registerStep(this.controlChildExperimentImpl.bind(this), {
|
|
134
|
+
name: 'optimization.controlChildExperiment',
|
|
135
|
+
});
|
|
136
|
+
this.queryChildExperimentStatusStep = dbos_sdk_1.DBOS.registerStep(this.queryChildExperimentStatusImpl.bind(this), {
|
|
137
|
+
name: 'optimization.queryChildExperimentStatus',
|
|
138
|
+
});
|
|
139
|
+
this.runWorkflow = dbos_sdk_1.DBOS.registerWorkflow(this.runImpl.bind(this), {
|
|
140
|
+
name: 'OptimizationWorkflow',
|
|
141
|
+
});
|
|
142
|
+
this.logger.info({}, 'optimization_workflow_registered');
|
|
143
|
+
}
|
|
144
|
+
async runImpl(optimizationId, orgId) {
|
|
145
|
+
this.logger.debug({ optimizationId }, 'workflow_run_start');
|
|
146
|
+
try {
|
|
147
|
+
let snapshot;
|
|
148
|
+
try {
|
|
149
|
+
snapshot = await this.loadConfigStep(optimizationId, orgId);
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
await this.finalizeStep(optimizationId, 'failed', {
|
|
153
|
+
reason: `load_config_failed: ${error.message}`,
|
|
154
|
+
});
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (!snapshot.ok) {
|
|
158
|
+
await this.finalizeStep(optimizationId, 'failed', { reason: snapshot.reason });
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
await this.markStartedStep(optimizationId);
|
|
162
|
+
// SPEC 25 §2.1: from_dataset_only start; the first prompt version is generated by generateFirstVersionStep
|
|
163
|
+
// (calls analysisModel to randomly sample from the dataset and induce prompt body / variables / outputSchema).
|
|
164
|
+
// After the step succeeds, base_version_id is backfilled; reload loadConfig to get a snapshot carrying the new baseVersionId;
|
|
165
|
+
// subsequent ensurePromptBaseline follows the same baseline-experiment path as from_prompt_version.
|
|
166
|
+
if (snapshot.startingMode === 'from_dataset_only' && !snapshot.baseVersionId) {
|
|
167
|
+
try {
|
|
168
|
+
await this.generateFirstVersionStep(optimizationId, snapshot.orgId);
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
const reason = mapFirstVersionErrorReason(error);
|
|
172
|
+
await this.finalizeStep(optimizationId, 'failed', { reason });
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
const reloaded = await this.loadConfigStep(optimizationId, orgId);
|
|
176
|
+
if (!reloaded.ok) {
|
|
177
|
+
await this.finalizeStep(optimizationId, 'failed', {
|
|
178
|
+
reason: reloaded.reason ?? 'reload_after_first_version_failed',
|
|
179
|
+
});
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
snapshot = reloaded;
|
|
183
|
+
}
|
|
184
|
+
const baseline = await this.ensurePromptBaseline(optimizationId, snapshot);
|
|
185
|
+
if (baseline.kind === 'exit')
|
|
186
|
+
return;
|
|
187
|
+
if (baseline.kind === 'fatal') {
|
|
188
|
+
await this.finalizeStep(optimizationId, 'failed', {
|
|
189
|
+
reason: baseline.errorMessage,
|
|
190
|
+
});
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
snapshot = baseline.snapshot;
|
|
194
|
+
// Pre-loop goal check (from_experiment start may already be at goal; finalize immediately)
|
|
195
|
+
if (snapshot.bestVersion && (0, optimization_strategy_1.allGoalsMet)(snapshot.goals, snapshot.bestMetrics)) {
|
|
196
|
+
await this.finalizeStep(optimizationId, 'success', { reason: 'goals_met' });
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
for (let n = snapshot.nextRound; n <= snapshot.maxRounds; n++) {
|
|
200
|
+
const state = await this.readStateStep(optimizationId);
|
|
201
|
+
// service performs preemptive terminal-state writes (on stop/cancel, writes status=stopped/cancelled directly);
|
|
202
|
+
// when the workflow observes status no longer running, it exits immediately — does not call finalize to overwrite,
|
|
203
|
+
// does not start a child experiment, does not write round_steps.
|
|
204
|
+
if (!state || state.status !== 'running') {
|
|
205
|
+
this.logger.info({ optimizationId, status: state?.status ?? 'not_found' }, 'optimization_workflow_exit_preempted');
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
if (state.controlState === 'cancel') {
|
|
209
|
+
await this.finalizeStep(optimizationId, 'cancelled', { reason: 'control_cancel' });
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
if (state.controlState === 'stop') {
|
|
213
|
+
await this.finalizeStep(optimizationId, 'stopped', { reason: 'control_stop' });
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
if (state.controlState === 'resume') {
|
|
217
|
+
await this.clearResumeStep(optimizationId);
|
|
218
|
+
}
|
|
219
|
+
// SPEC 25 §7 resume granularity: whether this loop iteration enters the "continue interrupted child experiment" branch
|
|
220
|
+
// True only when n == snapshot.nextRound; the next round (n+1) goes through normal prepare + startWorkflow
|
|
221
|
+
const isResumeRound = n === snapshot.nextRound && snapshot.resumeChildExpId !== null;
|
|
222
|
+
const prepare = await this.prepareRoundStep(optimizationId, n);
|
|
223
|
+
if (prepare.kind === 'fatal') {
|
|
224
|
+
await this.finalizeStep(optimizationId, 'failed', {
|
|
225
|
+
reason: prepare.errorMessage ?? 'round_fatal_error',
|
|
226
|
+
analysisFailureReason: prepare.analysisFailure ? prepare.errorMessage : undefined,
|
|
227
|
+
});
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
if (isResumeRound) {
|
|
231
|
+
// The child experiment already exists (prepare's ON CONFLICT DO NOTHING also guarantees no overwrite);
|
|
232
|
+
// skip DBOS.startWorkflow (to avoid ambiguity from re-dispatching the same id),
|
|
233
|
+
// if the child experiment is stopped → call service to resume it (new child workflow id)
|
|
234
|
+
const childStatus = await this.queryChildExperimentStatusStep(prepare.experimentId);
|
|
235
|
+
if (!childStatus) {
|
|
236
|
+
this.logger.warn({ optimizationId, roundNumber: n, experimentId: prepare.experimentId }, 'optimization_resume_child_missing');
|
|
237
|
+
await this.finalizeStep(optimizationId, 'failed', {
|
|
238
|
+
reason: 'resume_child_missing',
|
|
239
|
+
});
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
if (childStatus.status === 'stopped') {
|
|
243
|
+
await this.controlChildExperimentStep(snapshot.projectId, prepare.experimentId, 'resume');
|
|
244
|
+
this.logger.info({ optimizationId, roundNumber: n, experimentId: prepare.experimentId }, 'optimization_child_resumed');
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
this.logger.info({
|
|
248
|
+
optimizationId,
|
|
249
|
+
roundNumber: n,
|
|
250
|
+
experimentId: prepare.experimentId,
|
|
251
|
+
childStatus: childStatus.status,
|
|
252
|
+
}, 'optimization_resume_round_child_already_active');
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
// ---- Workflow-layer startup of child ExperimentWorkflow (SPEC 03 §3.2) ----
|
|
257
|
+
// DBOS does not allow calling startWorkflow inside a step, so the launch must happen at the workflow layer.
|
|
258
|
+
// startWorkflow with the same workflowId and same function is idempotent success (replay-safe);
|
|
259
|
+
// only throws DBOSConflictingWorkflowError when the same id has a different function / class name — any catch indicates
|
|
260
|
+
// a real launch failure: set the child experiment to failed; skip this round as continue (SPEC 25 §7); do not block the whole optimization.
|
|
261
|
+
const expWorkflowId = `optimization:${optimizationId}:round:${n}:exp`;
|
|
262
|
+
try {
|
|
263
|
+
await dbos_sdk_1.DBOS.startWorkflow(this.experimentWorkflow.runWorkflow, {
|
|
264
|
+
workflowID: expWorkflowId,
|
|
265
|
+
})(prepare.experimentId, snapshot.orgId);
|
|
266
|
+
}
|
|
267
|
+
catch (error) {
|
|
268
|
+
const message = error.message;
|
|
269
|
+
this.logger.warn({ optimizationId, roundNumber: n, expWorkflowId, error: message }, 'child_experiment_launch_failed');
|
|
270
|
+
await this.markChildLaunchFailedStep(prepare.experimentId, message, optimizationId, n);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
const outcome = await this.finalizeRoundStep(optimizationId, n, prepare.experimentId);
|
|
274
|
+
if (outcome.kind === 'fatal') {
|
|
275
|
+
await this.finalizeStep(optimizationId, 'failed', {
|
|
276
|
+
reason: outcome.errorMessage ?? 'round_fatal_error',
|
|
277
|
+
analysisFailureReason: outcome.analysisFailure ? outcome.errorMessage : undefined,
|
|
278
|
+
});
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
if (outcome.kind === 'goals_met') {
|
|
282
|
+
await this.finalizeStep(optimizationId, 'success', { reason: 'goals_met' });
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
await this.finalizeStep(optimizationId, 'failed', { reason: 'max_rounds' });
|
|
287
|
+
}
|
|
288
|
+
catch (error) {
|
|
289
|
+
// Fallback: any uncaught step exception writes status=failed, to prevent the application table from being stuck in running
|
|
290
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
291
|
+
this.logger.error({ optimizationId, err: message }, 'workflow_unhandled_error');
|
|
292
|
+
try {
|
|
293
|
+
await this.finalizeStep(optimizationId, 'failed', {
|
|
294
|
+
reason: `unhandled_workflow_error: ${message}`,
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
catch (finalizeError) {
|
|
298
|
+
this.logger.error({
|
|
299
|
+
optimizationId,
|
|
300
|
+
err: finalizeError instanceof Error ? finalizeError.message : String(finalizeError),
|
|
301
|
+
}, 'workflow_finalize_after_unhandled_failed');
|
|
302
|
+
}
|
|
303
|
+
throw error;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
async ensurePromptBaseline(optimizationId, snapshot) {
|
|
307
|
+
if (!isPromptBaselineBootstrapNeeded(snapshot.startingMode)) {
|
|
308
|
+
return { kind: 'ready', snapshot };
|
|
309
|
+
}
|
|
310
|
+
const prepare = await this.preparePromptBaselineStep(optimizationId);
|
|
311
|
+
if (prepare.kind === 'skip') {
|
|
312
|
+
return { kind: 'ready', snapshot };
|
|
313
|
+
}
|
|
314
|
+
if (prepare.kind === 'failed') {
|
|
315
|
+
return { kind: 'fatal', errorMessage: prepare.errorMessage ?? 'baseline_experiment_failed' };
|
|
316
|
+
}
|
|
317
|
+
if (prepare.kind === 'cancelled') {
|
|
318
|
+
await this.finalizeStep(optimizationId, 'cancelled', {
|
|
319
|
+
reason: prepare.errorMessage ?? 'baseline_experiment_cancelled',
|
|
320
|
+
});
|
|
321
|
+
return { kind: 'exit' };
|
|
322
|
+
}
|
|
323
|
+
if (prepare.kind === 'stopped') {
|
|
324
|
+
const state = await this.readStateStep(optimizationId);
|
|
325
|
+
if (state?.controlState === 'resume' && prepare.experimentId) {
|
|
326
|
+
await this.controlChildExperimentStep(snapshot.projectId, prepare.experimentId, 'resume');
|
|
327
|
+
await this.clearResumeStep(optimizationId);
|
|
328
|
+
}
|
|
329
|
+
else {
|
|
330
|
+
await this.finalizeStep(optimizationId, 'stopped', {
|
|
331
|
+
reason: prepare.errorMessage ?? 'baseline_experiment_stopped',
|
|
332
|
+
});
|
|
333
|
+
return { kind: 'exit' };
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
if (prepare.kind === 'launch' && prepare.experimentId) {
|
|
337
|
+
const expWorkflowId = `optimization:${optimizationId}:baseline:exp`;
|
|
338
|
+
try {
|
|
339
|
+
await dbos_sdk_1.DBOS.startWorkflow(this.experimentWorkflow.runWorkflow, {
|
|
340
|
+
workflowID: expWorkflowId,
|
|
341
|
+
})(prepare.experimentId, snapshot.orgId);
|
|
342
|
+
await this.recordBaselineWorkflowIdStep(prepare.experimentId, expWorkflowId);
|
|
343
|
+
}
|
|
344
|
+
catch (error) {
|
|
345
|
+
const message = error.message;
|
|
346
|
+
this.logger.warn({ optimizationId, experimentId: prepare.experimentId, expWorkflowId, error: message }, 'prompt_baseline_experiment_launch_failed');
|
|
347
|
+
await this.markPromptBaselineFailedStep(prepare.experimentId, `launch_failed: ${message}`);
|
|
348
|
+
return { kind: 'fatal', errorMessage: `baseline_launch_failed: ${message}` };
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
const experimentId = prepare.experimentId ?? snapshot.sourceExperimentId;
|
|
352
|
+
if (!experimentId) {
|
|
353
|
+
return { kind: 'fatal', errorMessage: 'baseline_experiment_missing' };
|
|
354
|
+
}
|
|
355
|
+
const finalized = await this.finalizePromptBaselineStep(optimizationId, experimentId);
|
|
356
|
+
if (finalized.kind === 'failed') {
|
|
357
|
+
return { kind: 'fatal', errorMessage: finalized.errorMessage ?? 'baseline_experiment_failed' };
|
|
358
|
+
}
|
|
359
|
+
if (finalized.kind === 'stopped') {
|
|
360
|
+
await this.finalizeStep(optimizationId, 'stopped', {
|
|
361
|
+
reason: finalized.errorMessage ?? 'baseline_experiment_stopped',
|
|
362
|
+
});
|
|
363
|
+
return { kind: 'exit' };
|
|
364
|
+
}
|
|
365
|
+
if (finalized.kind === 'cancelled') {
|
|
366
|
+
await this.finalizeStep(optimizationId, 'cancelled', {
|
|
367
|
+
reason: finalized.errorMessage ?? 'baseline_experiment_cancelled',
|
|
368
|
+
});
|
|
369
|
+
return { kind: 'exit' };
|
|
370
|
+
}
|
|
371
|
+
const reloaded = await this.loadConfigStep(optimizationId);
|
|
372
|
+
if (!reloaded.ok) {
|
|
373
|
+
return { kind: 'fatal', errorMessage: reloaded.reason ?? 'baseline_snapshot_reload_failed' };
|
|
374
|
+
}
|
|
375
|
+
return { kind: 'ready', snapshot: reloaded };
|
|
376
|
+
}
|
|
377
|
+
// ---------- step impls ----------
|
|
378
|
+
async loadConfigImpl(optimizationId, orgId) {
|
|
379
|
+
const ctx = await this.repo.loadWorkflowContext(optimizationId);
|
|
380
|
+
if (!ctx) {
|
|
381
|
+
return invalidSnapshot('optimization_not_found');
|
|
382
|
+
}
|
|
383
|
+
if (!ctx.promptId) {
|
|
384
|
+
// All starting modes have ensured promptId is persisted at createOptimization time (from_dataset_only auto-creates an empty prompt)
|
|
385
|
+
return invalidSnapshot('prompt_id_missing_for_starting_mode');
|
|
386
|
+
}
|
|
387
|
+
const isDatasetOnly = ctx.startingMode === 'from_dataset_only';
|
|
388
|
+
// SPEC 25 §2.1: from_dataset_only allows baseVersionId to be null; the workflow's
|
|
389
|
+
// generateFirstVersionStep backfills it after generating the first version; other modes must already have baseVersionId.
|
|
390
|
+
if (!isDatasetOnly && !ctx.baseVersionId) {
|
|
391
|
+
return invalidSnapshot('base_version_id_required');
|
|
392
|
+
}
|
|
393
|
+
if (!isDatasetOnly && !['from_experiment', 'from_prompt_version'].includes(ctx.startingMode)) {
|
|
394
|
+
return invalidSnapshot('starting_mode_unsupported_v1');
|
|
395
|
+
}
|
|
396
|
+
const goalsParsed = zod_1.z.array(shared_1.optimizationGoalSchema).safeParse(ctx.goals ?? []);
|
|
397
|
+
if (!goalsParsed.success || goalsParsed.data.length === 0) {
|
|
398
|
+
return invalidSnapshot('goals_invalid');
|
|
399
|
+
}
|
|
400
|
+
const fwParsed = shared_1.optimizationFieldWhitelistSchema.safeParse(ctx.fieldWhitelist ?? {});
|
|
401
|
+
if (!fwParsed.success) {
|
|
402
|
+
return invalidSnapshot('field_whitelist_invalid');
|
|
403
|
+
}
|
|
404
|
+
let strategyConfig;
|
|
405
|
+
try {
|
|
406
|
+
strategyConfig = optimization_strategy_1.errorPatternAnalysisConfigSchema.parse(ctx.strategyConfig ?? {});
|
|
407
|
+
}
|
|
408
|
+
catch {
|
|
409
|
+
strategyConfig = optimization_strategy_1.DEFAULT_ERROR_PATTERN_ANALYSIS_CONFIG;
|
|
410
|
+
}
|
|
411
|
+
const analysisModel = await this.loadModelInvocationConfig(ctx.analysisModelId);
|
|
412
|
+
if (!analysisModel)
|
|
413
|
+
return invalidSnapshot('analysis_model_unavailable');
|
|
414
|
+
const taskModel = await this.loadModelInvocationConfig(ctx.experimentModelId);
|
|
415
|
+
if (!taskModel)
|
|
416
|
+
return invalidSnapshot('task_model_unavailable');
|
|
417
|
+
const promptLanguage = parsePromptLanguage(ctx.promptLanguage);
|
|
418
|
+
const variables = parseVariables(ctx.baseVersionVariables);
|
|
419
|
+
// SPEC 25 §2.1: before the first version is generated in from_dataset_only, baseVersionId is null and basePromptVersion is also null;
|
|
420
|
+
// after generateFirstVersionStep completes, loadConfigStep re-runs and ctx.baseVersionId is backfilled, so a normal ref is constructed here.
|
|
421
|
+
const basePromptVersion = ctx.baseVersionId
|
|
422
|
+
? {
|
|
423
|
+
id: ctx.baseVersionId,
|
|
424
|
+
promptId: ctx.promptId,
|
|
425
|
+
versionNumber: ctx.baseVersionNumber ?? 0,
|
|
426
|
+
body: ctx.baseVersionBody ?? '',
|
|
427
|
+
outputSchema: ctx.baseVersionOutputSchema,
|
|
428
|
+
promptLanguage: parsePromptLanguage(ctx.baseVersionPromptLanguage),
|
|
429
|
+
judgmentRules: ctx.baseVersionJudgmentRules && typeof ctx.baseVersionJudgmentRules === 'object'
|
|
430
|
+
? {
|
|
431
|
+
ruleName: 'default',
|
|
432
|
+
config: ctx.baseVersionJudgmentRules,
|
|
433
|
+
}
|
|
434
|
+
: undefined,
|
|
435
|
+
// Preserve the full PromptVariableDto (type/required/datasetField) — otherwise downstream parseVariables, due to missing fields against
|
|
436
|
+
// promptVariableSchema, will safeParse each item and discard them, and the written new version ends up with variables=[],
|
|
437
|
+
// causing the experiment renderer's buildInputVariables to get an empty object, leaving {{text}} unsubstituted as a literal.
|
|
438
|
+
variables,
|
|
439
|
+
}
|
|
440
|
+
: null;
|
|
441
|
+
// Completed rounds + current best
|
|
442
|
+
// SPEC 25 §7: new rule for deriving nextRound
|
|
443
|
+
// - success/failed → treated as completed, advance to the next round
|
|
444
|
+
// - stopped/running → treated as "interrupted", do not advance the round index + carry resumeChildExpId
|
|
445
|
+
// letting runImpl skip startWorkflow and switch to the continue-child branch
|
|
446
|
+
const completedRounds = await this.repo.listRoundExperimentsForOptimization(optimizationId);
|
|
447
|
+
let bestVersion = ctx.bestVersionId ? null : basePromptVersion;
|
|
448
|
+
let bestMetrics = this.toMetricSnapshot(ctx.bestMetrics) ?? emptyMetrics();
|
|
449
|
+
let nextRound = 1;
|
|
450
|
+
let resumeChildExpId = null;
|
|
451
|
+
const sortedRounds = completedRounds.slice().sort((a, b) => a.roundIndex - b.roundIndex);
|
|
452
|
+
for (const round of sortedRounds) {
|
|
453
|
+
if (round.status === 'success' || round.status === 'failed') {
|
|
454
|
+
nextRound = Math.max(nextRound, round.roundIndex + 1);
|
|
455
|
+
}
|
|
456
|
+
else if (round.status === 'stopped' || round.status === 'running') {
|
|
457
|
+
// Interrupted round → re-run this round (prepare goes through LLM reuse + child experiment ON CONFLICT does not recreate); carry the child expId
|
|
458
|
+
nextRound = round.roundIndex;
|
|
459
|
+
resumeChildExpId = round.experimentId;
|
|
460
|
+
break; // Later rounds (if any) should not have been persisted by design — defensive break
|
|
461
|
+
}
|
|
462
|
+
// Other statuses (cancelled / queued / future extensions) are treated as "does not affect nextRound"
|
|
463
|
+
}
|
|
464
|
+
if (ctx.bestVersionId) {
|
|
465
|
+
const [bestVer] = await this.db
|
|
466
|
+
.select({
|
|
467
|
+
id: db_1.schema.promptVersions.id,
|
|
468
|
+
promptId: db_1.schema.promptVersions.promptId,
|
|
469
|
+
versionNumber: db_1.schema.promptVersions.versionNumber,
|
|
470
|
+
body: db_1.schema.promptVersions.body,
|
|
471
|
+
outputSchema: db_1.schema.promptVersions.outputSchema,
|
|
472
|
+
promptLanguage: db_1.schema.promptVersions.promptLanguage,
|
|
473
|
+
judgmentRules: db_1.schema.promptVersions.judgmentRules,
|
|
474
|
+
variables: db_1.schema.promptVersions.variables,
|
|
475
|
+
})
|
|
476
|
+
.from(db_1.schema.promptVersions)
|
|
477
|
+
.where((0, drizzle_orm_1.eq)(db_1.schema.promptVersions.id, ctx.bestVersionId))
|
|
478
|
+
.limit(1);
|
|
479
|
+
if (bestVer) {
|
|
480
|
+
bestVersion = {
|
|
481
|
+
id: bestVer.id,
|
|
482
|
+
promptId: bestVer.promptId,
|
|
483
|
+
versionNumber: bestVer.versionNumber,
|
|
484
|
+
body: bestVer.body ?? '',
|
|
485
|
+
outputSchema: bestVer.outputSchema,
|
|
486
|
+
promptLanguage: parsePromptLanguage(bestVer.promptLanguage),
|
|
487
|
+
judgmentRules: bestVer.judgmentRules && typeof bestVer.judgmentRules === 'object'
|
|
488
|
+
? { ruleName: 'default', config: bestVer.judgmentRules }
|
|
489
|
+
: undefined,
|
|
490
|
+
variables: parseVariables(bestVer.variables),
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
if (!bestVersion)
|
|
495
|
+
bestVersion = basePromptVersion;
|
|
496
|
+
// baseline metrics: taken from the source experiment or the main table's best_metrics
|
|
497
|
+
if (!ctx.bestMetrics && ctx.sourceExperimentId) {
|
|
498
|
+
const [src] = await this.db
|
|
499
|
+
.select({ metrics: experiments.metrics })
|
|
500
|
+
.from(experiments)
|
|
501
|
+
.where((0, drizzle_orm_1.eq)(experiments.id, ctx.sourceExperimentId))
|
|
502
|
+
.limit(1);
|
|
503
|
+
const fromSrc = this.toMetricSnapshot(src?.metrics);
|
|
504
|
+
if (fromSrc)
|
|
505
|
+
bestMetrics = fromSrc;
|
|
506
|
+
}
|
|
507
|
+
const runConfig = ctx.runConfig ?? {};
|
|
508
|
+
const optimizationHint = readOptimizationHintFromContext(ctx);
|
|
509
|
+
const childRunConfig = parseChildRunConfigFromOptimization(runConfig);
|
|
510
|
+
const fieldWhitelist = toLoopFieldWhitelist(fwParsed.data, readExpectedField(ctx.baseVersionJudgmentRules));
|
|
511
|
+
return {
|
|
512
|
+
ok: true,
|
|
513
|
+
projectId: ctx.projectId,
|
|
514
|
+
orgId,
|
|
515
|
+
optimizationName: ctx.name,
|
|
516
|
+
promptId: ctx.promptId,
|
|
517
|
+
baseVersionId: ctx.baseVersionId,
|
|
518
|
+
basePromptVersion,
|
|
519
|
+
datasetId: ctx.datasetId,
|
|
520
|
+
datasetSampleCount: ctx.datasetSampleCount,
|
|
521
|
+
startingMode: ctx.startingMode,
|
|
522
|
+
sourceExperimentId: ctx.sourceExperimentId,
|
|
523
|
+
promptLanguage,
|
|
524
|
+
analysisModel,
|
|
525
|
+
analysisLimiterKey: this.limiterKeyStrategy.buildModelKey({ projectId: ctx.projectId, orgId, source: 'local' }, analysisModel.id),
|
|
526
|
+
taskModel,
|
|
527
|
+
goals: goalsParsed.data.map(toLoopGoal),
|
|
528
|
+
fieldWhitelist,
|
|
529
|
+
strategy: ctx.strategy,
|
|
530
|
+
strategyConfig,
|
|
531
|
+
maxRounds: ctx.maxRounds,
|
|
532
|
+
optimizationHint,
|
|
533
|
+
createdBy: ctx.createdBy,
|
|
534
|
+
nextRound,
|
|
535
|
+
bestVersion,
|
|
536
|
+
bestMetrics,
|
|
537
|
+
childRunConfig,
|
|
538
|
+
resumeChildExpId,
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
async markStartedImpl(optimizationId) {
|
|
542
|
+
await this.repo.markStarted(optimizationId);
|
|
543
|
+
}
|
|
544
|
+
async applySynchronousRuntimeLimits(project, model, source) {
|
|
545
|
+
const mergedLimits = await this.runtimeLimitsProvider.mergeLlmLimits({
|
|
546
|
+
project,
|
|
547
|
+
modelId: model.id,
|
|
548
|
+
source,
|
|
549
|
+
});
|
|
550
|
+
return (0, runtime_limits_1.applyRuntimeLimits)(model, mergedLimits);
|
|
551
|
+
}
|
|
552
|
+
async withOptimizationExecutionSlot(project, source, modelId, requestId, run) {
|
|
553
|
+
return this.quotaPolicy.withExecutionSlot({ project, source, modelId, requestId }, run);
|
|
554
|
+
}
|
|
555
|
+
// SPEC 25 §2.1: first version generation for the from_dataset_only start.
|
|
556
|
+
// 1) randomly sample initialSamplingRounds × initialSamplesPerRound items from the dataset
|
|
557
|
+
// 2) call analysisModel to have the LLM induce the first prompt body / variables / outputSchema
|
|
558
|
+
// 3) write one frozen prompt_versions row with a deterministic versionId (replay-idempotent)
|
|
559
|
+
// 4) backfill the versionId into optimizations.base_version_id
|
|
560
|
+
// 5) reuse §12 round_steps record (round_index=0, step='generate_prompt')
|
|
561
|
+
// On failure, throw an Error with a specific reason; runImpl catches it, maps to the finalize reason code, and finalizes the whole optimization as failed.
|
|
562
|
+
async generateFirstVersionImpl(optimizationId, orgId) {
|
|
563
|
+
const ctx = await this.repo.loadWorkflowContext(optimizationId);
|
|
564
|
+
if (!ctx) {
|
|
565
|
+
throw new Error('first_version_generation_failed_v1:context_missing');
|
|
566
|
+
}
|
|
567
|
+
if (ctx.startingMode !== 'from_dataset_only') {
|
|
568
|
+
// Should not reach here; defensive check
|
|
569
|
+
throw new Error('first_version_generation_failed_v1:wrong_starting_mode');
|
|
570
|
+
}
|
|
571
|
+
if (ctx.baseVersionId) {
|
|
572
|
+
// Already generated (replay path) — fallback skip, to avoid calling the LLM again
|
|
573
|
+
this.logger.info({ optimizationId }, 'optimization_first_version_already_generated_skip');
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
if (!ctx.promptId) {
|
|
577
|
+
throw new Error('first_version_generation_failed_v1:prompt_id_missing');
|
|
578
|
+
}
|
|
579
|
+
const dbosWorkflowId = dbos_sdk_1.DBOS.workflowID ?? null;
|
|
580
|
+
const versionId = deterministicUuid(`${optimizationId}:first-version`);
|
|
581
|
+
const generateRunResultId = deterministicUuid(`${optimizationId}:first-generate`);
|
|
582
|
+
await this.upsertStepSafe({
|
|
583
|
+
optimizationId,
|
|
584
|
+
roundIndex: 0,
|
|
585
|
+
step: 'generate_prompt',
|
|
586
|
+
status: 'running',
|
|
587
|
+
startedAt: new Date(),
|
|
588
|
+
dbosWorkflowId,
|
|
589
|
+
});
|
|
590
|
+
try {
|
|
591
|
+
// Validate + parse config
|
|
592
|
+
const fwParsed = shared_1.optimizationFieldWhitelistSchema.safeParse(ctx.fieldWhitelist ?? {});
|
|
593
|
+
if (!fwParsed.success) {
|
|
594
|
+
throw new Error('first_version_generation_failed_v1:field_whitelist_invalid');
|
|
595
|
+
}
|
|
596
|
+
const goalsParsed = zod_1.z.array(shared_1.optimizationGoalSchema).safeParse(ctx.goals ?? []);
|
|
597
|
+
if (!goalsParsed.success || goalsParsed.data.length === 0) {
|
|
598
|
+
throw new Error('first_version_generation_failed_v1:goals_invalid');
|
|
599
|
+
}
|
|
600
|
+
let strategyConfig;
|
|
601
|
+
try {
|
|
602
|
+
strategyConfig = optimization_strategy_1.errorPatternAnalysisConfigSchema.parse(ctx.strategyConfig ?? {});
|
|
603
|
+
}
|
|
604
|
+
catch {
|
|
605
|
+
strategyConfig = optimization_strategy_1.DEFAULT_ERROR_PATTERN_ANALYSIS_CONFIG;
|
|
606
|
+
}
|
|
607
|
+
const analysisModel = await this.loadModelInvocationConfig(ctx.analysisModelId);
|
|
608
|
+
if (!analysisModel) {
|
|
609
|
+
throw new Error('first_version_generation_failed_v1:analysis_model_unavailable');
|
|
610
|
+
}
|
|
611
|
+
const analysisLimiterKey = this.limiterKeyStrategy.buildModelKey({ projectId: ctx.projectId, orgId, source: 'local' }, analysisModel.id);
|
|
612
|
+
const project = { projectId: ctx.projectId, ...(orgId ? { orgId } : {}), source: 'local' };
|
|
613
|
+
const effectiveAnalysisModel = await this.applySynchronousRuntimeLimits(project, analysisModel, 'optimization_generate');
|
|
614
|
+
// Load and randomly sample
|
|
615
|
+
const allSamples = await this.repo.loadDatasetSamples(ctx.datasetId);
|
|
616
|
+
const sampleCount = strategyConfig.initialSamplingRounds * strategyConfig.initialSamplesPerRound;
|
|
617
|
+
if (allSamples.length === 0) {
|
|
618
|
+
throw new Error('first_version_dataset_empty_v1');
|
|
619
|
+
}
|
|
620
|
+
const samples = allSamples.length <= sampleCount
|
|
621
|
+
? allSamples
|
|
622
|
+
: pickRandomSamples(allSamples, sampleCount, `${optimizationId}:first-version`);
|
|
623
|
+
if (allSamples.length < sampleCount) {
|
|
624
|
+
this.logger.warn({
|
|
625
|
+
optimizationId,
|
|
626
|
+
requested: sampleCount,
|
|
627
|
+
available: allSamples.length,
|
|
628
|
+
}, 'optimization_first_version_dataset_undersized');
|
|
629
|
+
}
|
|
630
|
+
const fieldWhitelist = toLoopFieldWhitelist(fwParsed.data, readExpectedField(ctx.baseVersionJudgmentRules));
|
|
631
|
+
// Call LLM to generate the first version
|
|
632
|
+
const promptLanguage = parsePromptLanguage(ctx.promptLanguage);
|
|
633
|
+
const generated = await this.withOptimizationExecutionSlot(project, 'optimization_generate', analysisModel.id, generateRunResultId, () => (0, optimization_strategy_1.generateInitialVersion)({
|
|
634
|
+
optimizationId,
|
|
635
|
+
analysisModel: effectiveAnalysisModel,
|
|
636
|
+
analysisLimiterKey,
|
|
637
|
+
samples,
|
|
638
|
+
goals: goalsParsed.data.map(toLoopGoal),
|
|
639
|
+
fieldWhitelist,
|
|
640
|
+
description: ctx.description,
|
|
641
|
+
optimizationHint: readOptimizationHintFromContext(ctx),
|
|
642
|
+
promptLanguage,
|
|
643
|
+
strategyConfig,
|
|
644
|
+
runResultMeta: {
|
|
645
|
+
projectId: ctx.projectId,
|
|
646
|
+
sourceId: optimizationId,
|
|
647
|
+
promptVersionId: versionId,
|
|
648
|
+
modelId: analysisModel.id,
|
|
649
|
+
dbosWorkflowId,
|
|
650
|
+
bullmqJobId: null,
|
|
651
|
+
attempt: 0,
|
|
652
|
+
},
|
|
653
|
+
generateRunResultId,
|
|
654
|
+
}, {
|
|
655
|
+
limiter: this.limiter,
|
|
656
|
+
logger: this.llmLogger,
|
|
657
|
+
runResultWriter: this.runResultWriter,
|
|
658
|
+
}));
|
|
659
|
+
// Write the frozen prompt_versions row (deterministic id makes replay idempotent)
|
|
660
|
+
await this.promptRepo.createOptimizationFrozenVersion({
|
|
661
|
+
versionId,
|
|
662
|
+
promptId: ctx.promptId,
|
|
663
|
+
parentVersionId: null,
|
|
664
|
+
body: generated.newPromptBody,
|
|
665
|
+
variables: generated.variables,
|
|
666
|
+
outputSchema: generated.outputSchema,
|
|
667
|
+
judgmentRules: deriveJudgmentRulesFromOutputSchema(generated.outputSchema, readExpectedField(ctx.baseVersionJudgmentRules)),
|
|
668
|
+
promptLanguage,
|
|
669
|
+
optimizationId,
|
|
670
|
+
changeReason: 'optimization:first-version',
|
|
671
|
+
createdBy: ctx.createdBy,
|
|
672
|
+
});
|
|
673
|
+
// Backfill base_version_id (with IS NULL guard)
|
|
674
|
+
await this.repo.updateBaseVersionId(optimizationId, versionId);
|
|
675
|
+
await this.upsertStepSafe({
|
|
676
|
+
optimizationId,
|
|
677
|
+
roundIndex: 0,
|
|
678
|
+
step: 'generate_prompt',
|
|
679
|
+
status: 'success',
|
|
680
|
+
finishedAt: new Date(),
|
|
681
|
+
runResultId: generateRunResultId,
|
|
682
|
+
dbosWorkflowId,
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
catch (error) {
|
|
686
|
+
const { errorClass, errorMessage } = normalizeErrorForStep(error);
|
|
687
|
+
await this.upsertStepSafe({
|
|
688
|
+
optimizationId,
|
|
689
|
+
roundIndex: 0,
|
|
690
|
+
step: 'generate_prompt',
|
|
691
|
+
status: 'failed',
|
|
692
|
+
finishedAt: new Date(),
|
|
693
|
+
errorClass,
|
|
694
|
+
errorMessage,
|
|
695
|
+
dbosWorkflowId,
|
|
696
|
+
});
|
|
697
|
+
throw error;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
async preparePromptBaselineImpl(optimizationId) {
|
|
701
|
+
const snapshot = await this.loadConfigImpl(optimizationId);
|
|
702
|
+
if (!snapshot.ok) {
|
|
703
|
+
return { kind: 'failed', errorMessage: snapshot.reason ?? 'snapshot_invalid' };
|
|
704
|
+
}
|
|
705
|
+
if (!isPromptBaselineBootstrapNeeded(snapshot.startingMode)) {
|
|
706
|
+
return { kind: 'skip' };
|
|
707
|
+
}
|
|
708
|
+
if (!snapshot.baseVersionId || !snapshot.basePromptVersion) {
|
|
709
|
+
return { kind: 'failed', errorMessage: 'base_version_missing' };
|
|
710
|
+
}
|
|
711
|
+
if (snapshot.sourceExperimentId) {
|
|
712
|
+
const existing = await this.repo.findExperimentStatus(snapshot.sourceExperimentId);
|
|
713
|
+
if (!existing) {
|
|
714
|
+
return {
|
|
715
|
+
kind: 'failed',
|
|
716
|
+
errorMessage: `baseline_experiment_not_found:${snapshot.sourceExperimentId}`,
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
const status = normalizeBaselineExperimentStatus(existing.status);
|
|
720
|
+
if (status === 'success') {
|
|
721
|
+
return { kind: 'ready', experimentId: existing.id, status };
|
|
722
|
+
}
|
|
723
|
+
if (status === 'failed' || status === 'stopped' || status === 'cancelled') {
|
|
724
|
+
return {
|
|
725
|
+
kind: status,
|
|
726
|
+
experimentId: existing.id,
|
|
727
|
+
status,
|
|
728
|
+
errorMessage: `baseline_experiment_${status}`,
|
|
729
|
+
};
|
|
730
|
+
}
|
|
731
|
+
return { kind: 'wait', experimentId: existing.id, status };
|
|
732
|
+
}
|
|
733
|
+
const experimentId = computeOptimizationBaselineExperimentId(optimizationId);
|
|
734
|
+
await this.repo.freezePromptVersionIfNeeded(snapshot.baseVersionId);
|
|
735
|
+
await this.repo.createPromptBaselineExperimentRow({
|
|
736
|
+
id: experimentId,
|
|
737
|
+
projectId: snapshot.projectId,
|
|
738
|
+
name: await this.buildOptimizationExperimentNameForInsert({
|
|
739
|
+
projectId: snapshot.projectId,
|
|
740
|
+
experimentId,
|
|
741
|
+
optimizationId,
|
|
742
|
+
optimizationName: snapshot.optimizationName,
|
|
743
|
+
round: 'baseline',
|
|
744
|
+
}),
|
|
745
|
+
promptVersionId: snapshot.baseVersionId,
|
|
746
|
+
datasetId: snapshot.datasetId,
|
|
747
|
+
modelId: snapshot.taskModel.id,
|
|
748
|
+
runConfig: snapshot.childRunConfig,
|
|
749
|
+
totalSamples: snapshot.datasetSampleCount,
|
|
750
|
+
createdBy: snapshot.createdBy,
|
|
751
|
+
});
|
|
752
|
+
await this.repo.attachSourceExperimentIfEmpty(optimizationId, experimentId);
|
|
753
|
+
const created = await this.repo.findExperimentStatus(experimentId);
|
|
754
|
+
const status = normalizeBaselineExperimentStatus(created?.status ?? 'running');
|
|
755
|
+
if (status === 'success') {
|
|
756
|
+
return { kind: 'ready', experimentId, status };
|
|
757
|
+
}
|
|
758
|
+
if (status === 'failed' || status === 'stopped' || status === 'cancelled') {
|
|
759
|
+
return {
|
|
760
|
+
kind: status,
|
|
761
|
+
experimentId,
|
|
762
|
+
status,
|
|
763
|
+
errorMessage: `baseline_experiment_${status}`,
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
return { kind: 'launch', experimentId, status };
|
|
767
|
+
}
|
|
768
|
+
async buildOptimizationExperimentNameForInsert(input) {
|
|
769
|
+
const primaryName = buildOptimizationExperimentName(input.optimizationName, input.round);
|
|
770
|
+
const existing = await this.repo.findActiveExperimentByProjectAndName(input.projectId, primaryName);
|
|
771
|
+
if (!existing || existing.id === input.experimentId)
|
|
772
|
+
return primaryName;
|
|
773
|
+
return buildOptimizationExperimentName(input.optimizationName, input.round, {
|
|
774
|
+
collisionSalt: `${input.optimizationId}:${input.round}`,
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
async recordBaselineWorkflowIdImpl(experimentId, workflowId) {
|
|
778
|
+
await this.repo.setExperimentDbosWorkflowId(experimentId, workflowId);
|
|
779
|
+
}
|
|
780
|
+
async markPromptBaselineFailedImpl(experimentId, failureReason) {
|
|
781
|
+
await this.repo.markChildExperimentFailed(experimentId, failureReason);
|
|
782
|
+
}
|
|
783
|
+
async finalizePromptBaselineImpl(optimizationId, experimentId) {
|
|
784
|
+
const snapshot = await this.loadConfigImpl(optimizationId);
|
|
785
|
+
if (!snapshot.ok) {
|
|
786
|
+
return { kind: 'failed', errorMessage: snapshot.reason ?? 'snapshot_invalid' };
|
|
787
|
+
}
|
|
788
|
+
const finalState = await this.waitForExperimentTerminal(experimentId, optimizationId, 0, snapshot.projectId);
|
|
789
|
+
const status = normalizeBaselineExperimentStatus(finalState.status);
|
|
790
|
+
if (status === 'success') {
|
|
791
|
+
return { kind: 'ready' };
|
|
792
|
+
}
|
|
793
|
+
if (status === 'stopped' || status === 'cancelled') {
|
|
794
|
+
return { kind: status, errorMessage: `baseline_experiment_${status}` };
|
|
795
|
+
}
|
|
796
|
+
return { kind: 'failed', errorMessage: 'baseline_experiment_failed' };
|
|
797
|
+
}
|
|
798
|
+
async readStateImpl(optimizationId) {
|
|
799
|
+
const row = await this.repo.findStatusAndControl(optimizationId);
|
|
800
|
+
if (!row)
|
|
801
|
+
return null;
|
|
802
|
+
const ctl = row.controlState;
|
|
803
|
+
const normalized = ctl === 'stop' || ctl === 'cancel' || ctl === 'resume' ? ctl : null;
|
|
804
|
+
return { status: row.status, controlState: normalized };
|
|
805
|
+
}
|
|
806
|
+
async clearResumeImpl(optimizationId) {
|
|
807
|
+
await this.repo.clearResume(optimizationId);
|
|
808
|
+
}
|
|
809
|
+
// SPEC 25 §11.3 cross-round history aggregation step — pure read, wrappable as a DBOS step.
|
|
810
|
+
// beforeRoundIndex locks "only see < N" completed rounds; best_version_id drift makes the history content move with the DB state at the time,
|
|
811
|
+
// but it does not affect this round's idempotency (runResultId is locked by uuidv5; replay does not actually call the LLM again)
|
|
812
|
+
async loadRoundHistoryImpl(optimizationId, beforeRoundIndex) {
|
|
813
|
+
return this.repo.loadRoundHistory(optimizationId, beforeRoundIndex);
|
|
814
|
+
}
|
|
815
|
+
// Convert raw rows returned by the repository into the strategy package's RoundHistoryEntry[].
|
|
816
|
+
// - metrics goes through toMetricSnapshot for normalization
|
|
817
|
+
// - deltaFromPrev is computed by iterating against goals[0]'s primary metric (the first entry is null)
|
|
818
|
+
// - changeSummary / appliedChanges are parsed from generateParsedOutput; for legacy data / parse failures use empty string / []
|
|
819
|
+
// (no throw, so history never blocks the main path)
|
|
820
|
+
buildRoundHistoryEntries(rows, goals) {
|
|
821
|
+
const primaryGoal = goals[0];
|
|
822
|
+
let prevPrimary = null;
|
|
823
|
+
return rows.map((row) => {
|
|
824
|
+
const metrics = this.toMetricSnapshot(row.metrics) ?? { overall: {} };
|
|
825
|
+
const currentPrimary = primaryGoal ? (0, optimization_strategy_1.readMetric)(metrics, primaryGoal) : null;
|
|
826
|
+
const deltaFromPrev = currentPrimary !== null && prevPrimary !== null ? currentPrimary - prevPrimary : null;
|
|
827
|
+
prevPrimary = currentPrimary;
|
|
828
|
+
const parsedGen = (row.generateParsedOutput ?? null);
|
|
829
|
+
const changeSummary = typeof parsedGen?.changeSummary === 'string' ? parsedGen.changeSummary : '';
|
|
830
|
+
const rawApplied = Array.isArray(parsedGen?.appliedChanges) ? parsedGen.appliedChanges : [];
|
|
831
|
+
const appliedChanges = rawApplied
|
|
832
|
+
.filter((c) => Boolean(c) && typeof c.changeId === 'string')
|
|
833
|
+
.map((c) => ({
|
|
834
|
+
changeId: c.changeId,
|
|
835
|
+
patternIds: Array.isArray(c.patternIds)
|
|
836
|
+
? c.patternIds.filter((p) => typeof p === 'string')
|
|
837
|
+
: undefined,
|
|
838
|
+
rationale: typeof c.summary === 'string' ? c.summary : undefined,
|
|
839
|
+
}));
|
|
840
|
+
// Basis for the "toolbox rotation hint" — de-aggregates the LLM's self-reported appliedTips (SPEC 25 §11.3 "toolbox rotation hint")
|
|
841
|
+
const appliedTips = extractAppliedTipsFromGenerateParsedOutput(parsedGen);
|
|
842
|
+
return {
|
|
843
|
+
roundIndex: row.roundIndex,
|
|
844
|
+
metrics,
|
|
845
|
+
deltaFromPrev,
|
|
846
|
+
changeSummary,
|
|
847
|
+
appliedChanges,
|
|
848
|
+
appliedTips,
|
|
849
|
+
isBest: row.isBest,
|
|
850
|
+
generatedFromBaseVersionId: row.parentVersionId ?? '',
|
|
851
|
+
};
|
|
852
|
+
});
|
|
853
|
+
}
|
|
854
|
+
async resolveRoundOptimizationContext(optimizationId, roundNumber, snapshot) {
|
|
855
|
+
const regressionRetry = await this.resolveRegressionRetryContext(optimizationId, roundNumber, snapshot);
|
|
856
|
+
if (regressionRetry)
|
|
857
|
+
return regressionRetry;
|
|
858
|
+
const generationBaseVersion = snapshot.bestVersion ?? snapshot.basePromptVersion;
|
|
859
|
+
if (!generationBaseVersion)
|
|
860
|
+
return { errorMessage: 'base_version_missing' };
|
|
861
|
+
const analysisExperiment = await this.repo.findAnalysisExperimentForPromptVersion({
|
|
862
|
+
optimizationId,
|
|
863
|
+
sourceExperimentId: snapshot.sourceExperimentId,
|
|
864
|
+
promptVersionId: generationBaseVersion.id,
|
|
865
|
+
});
|
|
866
|
+
if (!analysisExperiment) {
|
|
867
|
+
return {
|
|
868
|
+
errorMessage: `analysis_experiment_missing_for_prompt_version:${generationBaseVersion.id}`,
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
const previousExperiment = await this.repo.findPreviousComparableExperiment({
|
|
872
|
+
optimizationId,
|
|
873
|
+
sourceExperimentId: snapshot.sourceExperimentId,
|
|
874
|
+
currentRoundIndex: analysisExperiment.roundIndex,
|
|
875
|
+
});
|
|
876
|
+
const previousVersion = previousExperiment
|
|
877
|
+
? await this.loadPromptVersionRef(previousExperiment.promptVersionId)
|
|
878
|
+
: null;
|
|
879
|
+
return {
|
|
880
|
+
generationBaseVersion,
|
|
881
|
+
analysisVersion: generationBaseVersion,
|
|
882
|
+
analysisExperiment,
|
|
883
|
+
previousExperiment,
|
|
884
|
+
previousVersion,
|
|
885
|
+
regressionRetry: false,
|
|
886
|
+
};
|
|
887
|
+
}
|
|
888
|
+
// SPEC 25 §5 / §11.5: if the just-completed previous round is worse than its parent prompt's corresponding experiment,
|
|
889
|
+
// this round does not continue stacking changes on the bad prompt. Analyze attributes from the bad prompt + bad samples,
|
|
890
|
+
// Generate falls back to the parent prompt for improvement.
|
|
891
|
+
async resolveRegressionRetryContext(optimizationId, roundNumber, snapshot) {
|
|
892
|
+
if (roundNumber <= 1)
|
|
893
|
+
return null;
|
|
894
|
+
const previousRound = await this.repo.findExperimentByRound(optimizationId, roundNumber - 1);
|
|
895
|
+
if (!previousRound || previousRound.status !== 'success' || !previousRound.parentVersionId) {
|
|
896
|
+
return null;
|
|
897
|
+
}
|
|
898
|
+
const regressedMetrics = this.toMetricSnapshot(previousRound.metrics);
|
|
899
|
+
if (!regressedMetrics)
|
|
900
|
+
return null;
|
|
901
|
+
const baseExperiment = await this.repo.findAnalysisExperimentForPromptVersion({
|
|
902
|
+
optimizationId,
|
|
903
|
+
sourceExperimentId: snapshot.sourceExperimentId,
|
|
904
|
+
promptVersionId: previousRound.parentVersionId,
|
|
905
|
+
});
|
|
906
|
+
if (!baseExperiment)
|
|
907
|
+
return null;
|
|
908
|
+
const baseMetrics = this.toMetricSnapshot(baseExperiment.metrics);
|
|
909
|
+
if (!baseMetrics || !(0, optimization_strategy_1.isBetterThan)(baseMetrics, regressedMetrics, snapshot.goals)) {
|
|
910
|
+
return null;
|
|
911
|
+
}
|
|
912
|
+
const baseVersion = await this.loadPromptVersionRef(previousRound.parentVersionId);
|
|
913
|
+
const regressedVersion = await this.loadPromptVersionRef(previousRound.promptVersionId);
|
|
914
|
+
if (!baseVersion || !regressedVersion)
|
|
915
|
+
return null;
|
|
916
|
+
this.logger.info({
|
|
917
|
+
optimizationId,
|
|
918
|
+
roundNumber,
|
|
919
|
+
regressedRound: previousRound.roundIndex,
|
|
920
|
+
regressedVersionId: regressedVersion.id,
|
|
921
|
+
generationBaseVersionId: baseVersion.id,
|
|
922
|
+
}, 'optimization_round_regression_retry_context');
|
|
923
|
+
return {
|
|
924
|
+
generationBaseVersion: baseVersion,
|
|
925
|
+
analysisVersion: regressedVersion,
|
|
926
|
+
analysisExperiment: {
|
|
927
|
+
id: previousRound.id,
|
|
928
|
+
roundIndex: previousRound.roundIndex,
|
|
929
|
+
metrics: previousRound.metrics,
|
|
930
|
+
promptVersionId: previousRound.promptVersionId,
|
|
931
|
+
},
|
|
932
|
+
previousExperiment: baseExperiment,
|
|
933
|
+
previousVersion: baseVersion,
|
|
934
|
+
regressionRetry: true,
|
|
935
|
+
};
|
|
936
|
+
}
|
|
937
|
+
async prepareRoundImpl(optimizationId, roundNumber) {
|
|
938
|
+
const snapshot = await this.loadConfigImpl(optimizationId);
|
|
939
|
+
if (!snapshot.ok) {
|
|
940
|
+
return { kind: 'fatal', errorMessage: snapshot.reason ?? 'snapshot_invalid' };
|
|
941
|
+
}
|
|
942
|
+
// Load dataset samples + this round's optimization context (consumed by the strategy package)
|
|
943
|
+
const samplesRaw = await this.repo.loadDatasetSamples(snapshot.datasetId);
|
|
944
|
+
if (samplesRaw.length === 0) {
|
|
945
|
+
return { kind: 'fatal', errorMessage: 'dataset_empty' };
|
|
946
|
+
}
|
|
947
|
+
const roundContext = await this.resolveRoundOptimizationContext(optimizationId, roundNumber, snapshot);
|
|
948
|
+
if ('errorMessage' in roundContext) {
|
|
949
|
+
return {
|
|
950
|
+
kind: 'fatal',
|
|
951
|
+
errorMessage: roundContext.errorMessage,
|
|
952
|
+
analysisFailure: true,
|
|
953
|
+
};
|
|
954
|
+
}
|
|
955
|
+
const { generationBaseVersion: baseVersionForRound, analysisVersion, analysisExperiment, previousExperiment, previousVersion, } = roundContext;
|
|
956
|
+
// SPEC 25 §11.4.1: step status / reuse metadata inside prepareRoundImpl
|
|
957
|
+
const dbosWorkflowIdForSteps = dbos_sdk_1.DBOS.workflowID ?? null;
|
|
958
|
+
// Parse the expected field name from judgmentRules consistently with the experiment channel, so both channels read the same expected
|
|
959
|
+
const samples = buildSamplesForStrategy(samplesRaw, baseVersionForRound.judgmentRules?.config);
|
|
960
|
+
const rawCurrent = await this.repo.loadRunResultsByExperiment(analysisExperiment.id);
|
|
961
|
+
const rawPrevious = previousExperiment ? await this.repo.loadRunResultsByExperiment(previousExperiment.id) : null;
|
|
962
|
+
const currentRunResults = mapRunResultsForStrategy(rawCurrent);
|
|
963
|
+
const previousRunResults = rawPrevious ? mapRunResultsForStrategy(rawPrevious) : null;
|
|
964
|
+
const analysisMetrics = this.toMetricSnapshot(analysisExperiment.metrics) ?? snapshot.bestMetrics;
|
|
965
|
+
// SPEC 25 §11.3 cross-round history — injected into both LLM calls (analyze / generate) so the LLM avoids repeated ineffective changes across rounds.
|
|
966
|
+
// First round (roundNumber=1) → empty array → the strategy package does not render the history section (backward compatible).
|
|
967
|
+
const roundHistoryRows = await this.loadRoundHistoryStep(optimizationId, roundNumber);
|
|
968
|
+
const roundHistory = this.buildRoundHistoryEntries(roundHistoryRows, snapshot.goals);
|
|
969
|
+
// SPEC 25 §11.3 "toolbox rotation hint" — when !isBest for ≥ 2 consecutive rounds, build a hint and inject it into the generate user prompt.
|
|
970
|
+
// streak < 2 → undefined; the strategy package skips this section.
|
|
971
|
+
const noBestStreak = (0, optimization_strategy_1.computeNoBestStreak)(roundHistory);
|
|
972
|
+
const toolboxSwitchHint = (() => {
|
|
973
|
+
if (noBestStreak < 2)
|
|
974
|
+
return undefined;
|
|
975
|
+
const recentEntries = roundHistory.slice(-2);
|
|
976
|
+
const usedSet = new Set();
|
|
977
|
+
for (const entry of recentEntries) {
|
|
978
|
+
for (const tip of entry.appliedTips) {
|
|
979
|
+
const trimmed = tip.trim();
|
|
980
|
+
if (trimmed.length > 0)
|
|
981
|
+
usedSet.add(trimmed);
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
return {
|
|
985
|
+
recentlyUsedTips: Array.from(usedSet),
|
|
986
|
+
allTipNames: (0, optimization_strategy_1.getOptimizationTipNames)(snapshot.promptLanguage),
|
|
987
|
+
};
|
|
988
|
+
})();
|
|
989
|
+
if (toolboxSwitchHint) {
|
|
990
|
+
this.logger.info({
|
|
991
|
+
optimizationId,
|
|
992
|
+
roundNumber,
|
|
993
|
+
streak: noBestStreak,
|
|
994
|
+
recentlyUsedTips: toolboxSwitchHint.recentlyUsedTips,
|
|
995
|
+
}, 'optimization_toolbox_switch_triggered');
|
|
996
|
+
}
|
|
997
|
+
// Deterministic UUID (uuidv5): on replay, the same id hits INSERT WHERE NOT EXISTS,
|
|
998
|
+
// so duplicate rows are never written (SPEC 25 §11.2)
|
|
999
|
+
const analysisRunResultId = deterministicUuid(`${optimizationId}:${roundNumber}:analysis`);
|
|
1000
|
+
const generateRunResultId = deterministicUuid(`${optimizationId}:${roundNumber}:generate`);
|
|
1001
|
+
const versionId = deterministicUuid(`${optimizationId}:${roundNumber}:version`);
|
|
1002
|
+
const experimentId = deterministicUuid(`${optimizationId}:${roundNumber}:experiment`);
|
|
1003
|
+
// Inside a step, fetch the current workflow id and thread it through logs and run_results.dbos_workflow_id (SPEC 05 §5.6)
|
|
1004
|
+
const dbosWorkflowId = dbos_sdk_1.DBOS.workflowID ?? null;
|
|
1005
|
+
const analysisRunResultMeta = {
|
|
1006
|
+
projectId: snapshot.projectId,
|
|
1007
|
+
sourceId: optimizationId,
|
|
1008
|
+
promptVersionId: analysisVersion.id,
|
|
1009
|
+
modelId: snapshot.analysisModel.id,
|
|
1010
|
+
dbosWorkflowId,
|
|
1011
|
+
bullmqJobId: null,
|
|
1012
|
+
attempt: 0,
|
|
1013
|
+
};
|
|
1014
|
+
const generateRunResultMeta = {
|
|
1015
|
+
projectId: snapshot.projectId,
|
|
1016
|
+
sourceId: optimizationId,
|
|
1017
|
+
promptVersionId: baseVersionForRound.id,
|
|
1018
|
+
modelId: snapshot.analysisModel.id,
|
|
1019
|
+
dbosWorkflowId,
|
|
1020
|
+
bullmqJobId: null,
|
|
1021
|
+
attempt: 0,
|
|
1022
|
+
};
|
|
1023
|
+
// SPEC 25 §11.4.1: reuse the already-success analysis run_result, skipping the LLM call to avoid duplicate token charges
|
|
1024
|
+
let analysisFull;
|
|
1025
|
+
const existingAnalysis = await this.peekOptimizationRunResultStep(optimizationId, roundNumber, 'optimization_analysis');
|
|
1026
|
+
if (existingAnalysis) {
|
|
1027
|
+
analysisFull = reconstructAnalysisFromRunResult(existingAnalysis);
|
|
1028
|
+
this.logger.info({ optimizationId, roundNumber, source: 'optimization_analysis' }, 'optimization_reuse_run_result');
|
|
1029
|
+
await this.upsertStepSafe({
|
|
1030
|
+
optimizationId,
|
|
1031
|
+
roundIndex: roundNumber,
|
|
1032
|
+
step: 'error_analysis',
|
|
1033
|
+
status: 'success',
|
|
1034
|
+
finishedAt: new Date(),
|
|
1035
|
+
runResultId: analysisRunResultId,
|
|
1036
|
+
dbosWorkflowId: dbosWorkflowIdForSteps,
|
|
1037
|
+
});
|
|
1038
|
+
}
|
|
1039
|
+
else {
|
|
1040
|
+
await this.upsertStepSafe({
|
|
1041
|
+
optimizationId,
|
|
1042
|
+
roundIndex: roundNumber,
|
|
1043
|
+
step: 'error_analysis',
|
|
1044
|
+
status: 'running',
|
|
1045
|
+
startedAt: new Date(),
|
|
1046
|
+
dbosWorkflowId: dbosWorkflowIdForSteps,
|
|
1047
|
+
});
|
|
1048
|
+
try {
|
|
1049
|
+
const project = {
|
|
1050
|
+
projectId: snapshot.projectId,
|
|
1051
|
+
...(snapshot.orgId ? { orgId: snapshot.orgId } : {}),
|
|
1052
|
+
source: 'local',
|
|
1053
|
+
};
|
|
1054
|
+
const analysisModel = await this.applySynchronousRuntimeLimits(project, snapshot.analysisModel, 'optimization_analysis');
|
|
1055
|
+
analysisFull = await this.withOptimizationExecutionSlot(project, 'optimization_analysis', snapshot.analysisModel.id, analysisRunResultId, () => (0, optimization_strategy_1.analyzeFailures)({
|
|
1056
|
+
optimizationId,
|
|
1057
|
+
roundNumber,
|
|
1058
|
+
analysisModel,
|
|
1059
|
+
analysisLimiterKey: snapshot.analysisLimiterKey,
|
|
1060
|
+
currentVersion: analysisVersion,
|
|
1061
|
+
previousVersion,
|
|
1062
|
+
samples,
|
|
1063
|
+
currentRunResults,
|
|
1064
|
+
previousRunResults,
|
|
1065
|
+
metrics: analysisMetrics,
|
|
1066
|
+
goals: snapshot.goals,
|
|
1067
|
+
fieldWhitelist: snapshot.fieldWhitelist,
|
|
1068
|
+
strategyConfig: snapshot.strategyConfig,
|
|
1069
|
+
promptLanguage: snapshot.promptLanguage,
|
|
1070
|
+
roundHistory,
|
|
1071
|
+
runResultMeta: analysisRunResultMeta,
|
|
1072
|
+
analysisRunResultId,
|
|
1073
|
+
}, {
|
|
1074
|
+
limiter: this.limiter,
|
|
1075
|
+
logger: this.llmLogger,
|
|
1076
|
+
runResultWriter: this.runResultWriter,
|
|
1077
|
+
}));
|
|
1078
|
+
await this.upsertStepSafe({
|
|
1079
|
+
optimizationId,
|
|
1080
|
+
roundIndex: roundNumber,
|
|
1081
|
+
step: 'error_analysis',
|
|
1082
|
+
status: 'success',
|
|
1083
|
+
finishedAt: new Date(),
|
|
1084
|
+
runResultId: analysisRunResultId,
|
|
1085
|
+
dbosWorkflowId: dbosWorkflowIdForSteps,
|
|
1086
|
+
});
|
|
1087
|
+
}
|
|
1088
|
+
catch (error) {
|
|
1089
|
+
const message = error instanceof limiter_1.RateLimitExceededError ? 'rate_limited' : error.message;
|
|
1090
|
+
const { errorClass, errorMessage } = normalizeErrorForStep(error);
|
|
1091
|
+
await this.upsertStepSafe({
|
|
1092
|
+
optimizationId,
|
|
1093
|
+
roundIndex: roundNumber,
|
|
1094
|
+
step: 'error_analysis',
|
|
1095
|
+
status: 'failed',
|
|
1096
|
+
finishedAt: new Date(),
|
|
1097
|
+
errorClass,
|
|
1098
|
+
errorMessage,
|
|
1099
|
+
dbosWorkflowId: dbosWorkflowIdForSteps,
|
|
1100
|
+
});
|
|
1101
|
+
return {
|
|
1102
|
+
kind: 'fatal',
|
|
1103
|
+
errorMessage: `analysis_failed: ${message}`,
|
|
1104
|
+
analysisFailure: true,
|
|
1105
|
+
};
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
// ---- Generate LLM (SPEC 25 §11.4.1: same reuse mechanism) ----
|
|
1109
|
+
let generated;
|
|
1110
|
+
const existingGenerate = await this.peekOptimizationRunResultStep(optimizationId, roundNumber, 'optimization_generate');
|
|
1111
|
+
if (existingGenerate) {
|
|
1112
|
+
generated = reconstructGenerateFromRunResult(existingGenerate, baseVersionForRound.body);
|
|
1113
|
+
this.logger.info({ optimizationId, roundNumber, source: 'optimization_generate' }, 'optimization_reuse_run_result');
|
|
1114
|
+
await this.upsertStepSafe({
|
|
1115
|
+
optimizationId,
|
|
1116
|
+
roundIndex: roundNumber,
|
|
1117
|
+
step: 'generate_prompt',
|
|
1118
|
+
status: 'success',
|
|
1119
|
+
finishedAt: new Date(),
|
|
1120
|
+
runResultId: generateRunResultId,
|
|
1121
|
+
dbosWorkflowId: dbosWorkflowIdForSteps,
|
|
1122
|
+
});
|
|
1123
|
+
}
|
|
1124
|
+
else {
|
|
1125
|
+
await this.upsertStepSafe({
|
|
1126
|
+
optimizationId,
|
|
1127
|
+
roundIndex: roundNumber,
|
|
1128
|
+
step: 'generate_prompt',
|
|
1129
|
+
status: 'running',
|
|
1130
|
+
startedAt: new Date(),
|
|
1131
|
+
dbosWorkflowId: dbosWorkflowIdForSteps,
|
|
1132
|
+
});
|
|
1133
|
+
try {
|
|
1134
|
+
const project = {
|
|
1135
|
+
projectId: snapshot.projectId,
|
|
1136
|
+
...(snapshot.orgId ? { orgId: snapshot.orgId } : {}),
|
|
1137
|
+
source: 'local',
|
|
1138
|
+
};
|
|
1139
|
+
const analysisModel = await this.applySynchronousRuntimeLimits(project, snapshot.analysisModel, 'optimization_generate');
|
|
1140
|
+
const draft = await this.withOptimizationExecutionSlot(project, 'optimization_generate', snapshot.analysisModel.id, generateRunResultId, () => (0, optimization_strategy_1.generateNextVersion)({
|
|
1141
|
+
optimizationId,
|
|
1142
|
+
roundNumber,
|
|
1143
|
+
analysisModel,
|
|
1144
|
+
analysisLimiterKey: snapshot.analysisLimiterKey,
|
|
1145
|
+
currentVersion: baseVersionForRound,
|
|
1146
|
+
analysis: analysisFull,
|
|
1147
|
+
metrics: analysisMetrics,
|
|
1148
|
+
goals: snapshot.goals,
|
|
1149
|
+
fieldWhitelist: snapshot.fieldWhitelist,
|
|
1150
|
+
optimizationHint: snapshot.optimizationHint,
|
|
1151
|
+
strategyConfig: snapshot.strategyConfig,
|
|
1152
|
+
promptLanguage: snapshot.promptLanguage,
|
|
1153
|
+
roundHistory,
|
|
1154
|
+
toolboxSwitchHint,
|
|
1155
|
+
runResultMeta: generateRunResultMeta,
|
|
1156
|
+
generateRunResultId,
|
|
1157
|
+
}, {
|
|
1158
|
+
limiter: this.limiter,
|
|
1159
|
+
logger: this.llmLogger,
|
|
1160
|
+
runResultWriter: this.runResultWriter,
|
|
1161
|
+
}));
|
|
1162
|
+
generated = {
|
|
1163
|
+
newPromptBody: draft.newPromptBody,
|
|
1164
|
+
changeSummary: draft.changeSummary,
|
|
1165
|
+
newOutputSchema: draft.newOutputSchema,
|
|
1166
|
+
outputSchemaChangeReason: draft.outputSchemaChangeReason,
|
|
1167
|
+
autoPatched: draft.autoPatched,
|
|
1168
|
+
patchedVariables: draft.patchedVariables,
|
|
1169
|
+
};
|
|
1170
|
+
if (draft.autoPatched) {
|
|
1171
|
+
this.logger.warn({
|
|
1172
|
+
optimizationId,
|
|
1173
|
+
roundNumber,
|
|
1174
|
+
patchedVariables: draft.patchedVariables,
|
|
1175
|
+
retries: draft.retries,
|
|
1176
|
+
}, 'optimization_generate_auto_patched_round');
|
|
1177
|
+
}
|
|
1178
|
+
else if (draft.retries > 0) {
|
|
1179
|
+
this.logger.info({ optimizationId, roundNumber, retries: draft.retries }, 'optimization_generate_succeeded_after_retry');
|
|
1180
|
+
}
|
|
1181
|
+
await this.upsertStepSafe({
|
|
1182
|
+
optimizationId,
|
|
1183
|
+
roundIndex: roundNumber,
|
|
1184
|
+
step: 'generate_prompt',
|
|
1185
|
+
status: 'success',
|
|
1186
|
+
finishedAt: new Date(),
|
|
1187
|
+
runResultId: generateRunResultId,
|
|
1188
|
+
dbosWorkflowId: dbosWorkflowIdForSteps,
|
|
1189
|
+
});
|
|
1190
|
+
}
|
|
1191
|
+
catch (error) {
|
|
1192
|
+
const message = error instanceof limiter_1.RateLimitExceededError ? 'rate_limited' : error.message;
|
|
1193
|
+
const { errorClass, errorMessage } = normalizeErrorForStep(error);
|
|
1194
|
+
await this.upsertStepSafe({
|
|
1195
|
+
optimizationId,
|
|
1196
|
+
roundIndex: roundNumber,
|
|
1197
|
+
step: 'generate_prompt',
|
|
1198
|
+
status: 'failed',
|
|
1199
|
+
finishedAt: new Date(),
|
|
1200
|
+
errorClass,
|
|
1201
|
+
errorMessage,
|
|
1202
|
+
dbosWorkflowId: dbosWorkflowIdForSteps,
|
|
1203
|
+
});
|
|
1204
|
+
return {
|
|
1205
|
+
kind: 'fatal',
|
|
1206
|
+
errorMessage: `generate_failed: ${message}`,
|
|
1207
|
+
analysisFailure: true,
|
|
1208
|
+
};
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
// ---- Write the new version (idempotent: deterministic versionId + ON CONFLICT DO NOTHING) ----
|
|
1212
|
+
if (!snapshot.promptId) {
|
|
1213
|
+
return { kind: 'fatal', errorMessage: 'prompt_id_missing' };
|
|
1214
|
+
}
|
|
1215
|
+
const variables = parseVariables(baseVersionForRound.variables);
|
|
1216
|
+
// The newOutputSchema provided by the LLM that passes safeValidateNewOutputSchema takes precedence; otherwise inherit the baseline schema.
|
|
1217
|
+
const outputSchemaForVersion = (generated.newOutputSchema ??
|
|
1218
|
+
baseVersionForRound.outputSchema ??
|
|
1219
|
+
null);
|
|
1220
|
+
const judgmentRulesForVersion = (baseVersionForRound.judgmentRules &&
|
|
1221
|
+
typeof baseVersionForRound.judgmentRules === 'object' &&
|
|
1222
|
+
'config' in baseVersionForRound.judgmentRules
|
|
1223
|
+
? baseVersionForRound.judgmentRules.config
|
|
1224
|
+
: null);
|
|
1225
|
+
const baseChangeReason = generated.outputSchemaChangeReason
|
|
1226
|
+
? `${generated.changeSummary}\n\n[output schema 变更] ${generated.outputSchemaChangeReason}`
|
|
1227
|
+
: generated.changeSummary;
|
|
1228
|
+
// SPEC 25 §11: when autoPatched=true, append the patch tag at the end of changeReason; the frontend round card uses this to render the "system patch" chip
|
|
1229
|
+
const changeReason = generated.autoPatched && generated.patchedVariables && generated.patchedVariables.length > 0
|
|
1230
|
+
? `${baseChangeReason}\n\n[系统自动补丁] 补回占位:${generated.patchedVariables.join(', ')}`
|
|
1231
|
+
: baseChangeReason;
|
|
1232
|
+
await this.promptRepo.createOptimizationFrozenVersion({
|
|
1233
|
+
versionId,
|
|
1234
|
+
promptId: snapshot.promptId,
|
|
1235
|
+
parentVersionId: baseVersionForRound.id,
|
|
1236
|
+
body: generated.newPromptBody,
|
|
1237
|
+
variables,
|
|
1238
|
+
outputSchema: outputSchemaForVersion,
|
|
1239
|
+
judgmentRules: judgmentRulesForVersion,
|
|
1240
|
+
promptLanguage: snapshot.promptLanguage,
|
|
1241
|
+
optimizationId,
|
|
1242
|
+
changeReason,
|
|
1243
|
+
createdBy: snapshot.createdBy,
|
|
1244
|
+
});
|
|
1245
|
+
// ---- Create the child experiment (idempotent: deterministic experimentId + ON CONFLICT DO NOTHING + partial unique) ----
|
|
1246
|
+
await this.repo.createChildExperimentRow({
|
|
1247
|
+
id: experimentId,
|
|
1248
|
+
projectId: snapshot.projectId,
|
|
1249
|
+
name: await this.buildOptimizationExperimentNameForInsert({
|
|
1250
|
+
projectId: snapshot.projectId,
|
|
1251
|
+
experimentId,
|
|
1252
|
+
optimizationId,
|
|
1253
|
+
optimizationName: snapshot.optimizationName,
|
|
1254
|
+
round: roundNumber,
|
|
1255
|
+
}),
|
|
1256
|
+
promptVersionId: versionId,
|
|
1257
|
+
datasetId: snapshot.datasetId,
|
|
1258
|
+
modelId: snapshot.taskModel.id,
|
|
1259
|
+
optimizationId,
|
|
1260
|
+
roundIndex: roundNumber,
|
|
1261
|
+
runConfig: snapshot.childRunConfig,
|
|
1262
|
+
totalSamples: samples.length,
|
|
1263
|
+
createdBy: snapshot.createdBy,
|
|
1264
|
+
});
|
|
1265
|
+
// The experiment step enters running: at this point the third dot of the detail-page stepper starts spinning;
|
|
1266
|
+
// the real terminal state is written by finalizeRoundImpl (after the child experiment completes).
|
|
1267
|
+
await this.upsertStepSafe({
|
|
1268
|
+
optimizationId,
|
|
1269
|
+
roundIndex: roundNumber,
|
|
1270
|
+
step: 'experiment',
|
|
1271
|
+
status: 'running',
|
|
1272
|
+
startedAt: new Date(),
|
|
1273
|
+
experimentId,
|
|
1274
|
+
dbosWorkflowId: dbosWorkflowIdForSteps,
|
|
1275
|
+
});
|
|
1276
|
+
// Starting the child ExperimentWorkflow + waiting + comparing metrics is now split between runImpl (workflow) layer
|
|
1277
|
+
// and finalizeRoundImpl (step): DBOS forbids calling startWorkflow inside a step.
|
|
1278
|
+
return { kind: 'launch', experimentId };
|
|
1279
|
+
}
|
|
1280
|
+
// Wrapped in try-catch: an upsertRoundStep failure must not block the workflow main path (round_steps table is just
|
|
1281
|
+
// a UX enhancement for the detail page; when data is missing, the frontend falls back and can still render). Failures are logged at warn.
|
|
1282
|
+
async upsertStepSafe(input) {
|
|
1283
|
+
try {
|
|
1284
|
+
await this.repo.upsertRoundStep(input);
|
|
1285
|
+
}
|
|
1286
|
+
catch (err) {
|
|
1287
|
+
this.logger.warn({
|
|
1288
|
+
optimizationId: input.optimizationId,
|
|
1289
|
+
roundIndex: input.roundIndex,
|
|
1290
|
+
step: input.step,
|
|
1291
|
+
status: input.status,
|
|
1292
|
+
err: err?.message,
|
|
1293
|
+
}, 'optimization_round_step_upsert_failed');
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
async finalizeRoundImpl(optimizationId, roundNumber, experimentId) {
|
|
1297
|
+
// Reload snapshot inside the step: sensitive fields like apiKey are not stored in the DBOS system tables, so we do not pass snapshot across steps
|
|
1298
|
+
const snapshot = await this.loadConfigImpl(optimizationId);
|
|
1299
|
+
if (!snapshot.ok) {
|
|
1300
|
+
this.logger.warn({ optimizationId, roundNumber, reason: snapshot.reason }, 'optimization_finalize_round_snapshot_invalid');
|
|
1301
|
+
return { kind: 'continue', metrics: undefined, isBest: false };
|
|
1302
|
+
}
|
|
1303
|
+
const versionId = deterministicUuid(`${optimizationId}:${roundNumber}:version`);
|
|
1304
|
+
// ---- Wait for the child experiment to finish (each round has its own poll, and polling is idempotent on replay) ----
|
|
1305
|
+
// SPEC 25 §7 dual path: inside poll, also read the parent control_state; on stop/cancel, propagate to stop the child experiment, then continue polling to terminal
|
|
1306
|
+
const finalState = await this.waitForExperimentTerminal(experimentId, optimizationId, roundNumber, snapshot.projectId);
|
|
1307
|
+
const dbosWfId = dbos_sdk_1.DBOS.workflowID ?? null;
|
|
1308
|
+
if (finalState.status === 'cancelled' || finalState.status === 'stopped') {
|
|
1309
|
+
// The parent workflow observes control_state at the next step boundary; treat this round as continue/skip
|
|
1310
|
+
await this.upsertStepSafe({
|
|
1311
|
+
optimizationId,
|
|
1312
|
+
roundIndex: roundNumber,
|
|
1313
|
+
step: 'experiment',
|
|
1314
|
+
status: 'skipped',
|
|
1315
|
+
finishedAt: new Date(),
|
|
1316
|
+
experimentId,
|
|
1317
|
+
dbosWorkflowId: dbosWfId,
|
|
1318
|
+
});
|
|
1319
|
+
return { kind: 'continue', metrics: undefined, isBest: false };
|
|
1320
|
+
}
|
|
1321
|
+
if (finalState.status === 'failed') {
|
|
1322
|
+
// A single-round failure that is not fatal — continue to the next round (do not block the whole optimization); fatal only on fatal errors in analysis/generate
|
|
1323
|
+
await this.upsertStepSafe({
|
|
1324
|
+
optimizationId,
|
|
1325
|
+
roundIndex: roundNumber,
|
|
1326
|
+
step: 'experiment',
|
|
1327
|
+
status: 'failed',
|
|
1328
|
+
finishedAt: new Date(),
|
|
1329
|
+
errorMessage: 'experiment_failed',
|
|
1330
|
+
experimentId,
|
|
1331
|
+
dbosWorkflowId: dbosWfId,
|
|
1332
|
+
});
|
|
1333
|
+
return { kind: 'continue', metrics: undefined, isBest: false };
|
|
1334
|
+
}
|
|
1335
|
+
const roundMetrics = this.toMetricSnapshot(finalState.metrics) ?? emptyMetrics();
|
|
1336
|
+
const decision = (0, optimization_strategy_1.decideRoundOutcome)({
|
|
1337
|
+
roundMetrics,
|
|
1338
|
+
bestMetrics: snapshot.bestMetrics,
|
|
1339
|
+
goals: snapshot.goals,
|
|
1340
|
+
});
|
|
1341
|
+
if (decision.isBest) {
|
|
1342
|
+
await this.repo.updateBest(optimizationId, versionId, roundMetrics.overall);
|
|
1343
|
+
}
|
|
1344
|
+
await this.repo.updateCurrentRound(optimizationId, roundNumber);
|
|
1345
|
+
await this.upsertStepSafe({
|
|
1346
|
+
optimizationId,
|
|
1347
|
+
roundIndex: roundNumber,
|
|
1348
|
+
step: 'experiment',
|
|
1349
|
+
status: 'success',
|
|
1350
|
+
finishedAt: new Date(),
|
|
1351
|
+
experimentId,
|
|
1352
|
+
dbosWorkflowId: dbosWfId,
|
|
1353
|
+
});
|
|
1354
|
+
if (decision.goalsMet) {
|
|
1355
|
+
return { kind: 'goals_met', metrics: roundMetrics, isBest: decision.isBest };
|
|
1356
|
+
}
|
|
1357
|
+
return { kind: 'continue', metrics: roundMetrics, isBest: decision.isBest };
|
|
1358
|
+
}
|
|
1359
|
+
async markChildLaunchFailedImpl(experimentId, message, optimizationId, roundNumber) {
|
|
1360
|
+
await this.repo.markChildExperimentFailed(experimentId, `launch_failed: ${message}`);
|
|
1361
|
+
await this.upsertStepSafe({
|
|
1362
|
+
optimizationId,
|
|
1363
|
+
roundIndex: roundNumber,
|
|
1364
|
+
step: 'experiment',
|
|
1365
|
+
status: 'failed',
|
|
1366
|
+
finishedAt: new Date(),
|
|
1367
|
+
errorClass: 'LaunchFailed',
|
|
1368
|
+
errorMessage: `launch_failed: ${message.slice(0, 1000)}`,
|
|
1369
|
+
experimentId,
|
|
1370
|
+
dbosWorkflowId: dbos_sdk_1.DBOS.workflowID ?? null,
|
|
1371
|
+
});
|
|
1372
|
+
}
|
|
1373
|
+
async finalizeImpl(optimizationId, kind, options) {
|
|
1374
|
+
const updated = await this.repo.finalize(optimizationId, kind, {
|
|
1375
|
+
summary: options.reason ? { kind, reason: options.reason, finalizedAt: new Date().toISOString() } : undefined,
|
|
1376
|
+
analysisFailureReason: options.analysisFailureReason ?? null,
|
|
1377
|
+
});
|
|
1378
|
+
if (!updated) {
|
|
1379
|
+
// service has already done preemptive terminal-state write (stop/cancel directly writes status); the workflow's own finalize is intercepted by the guard.
|
|
1380
|
+
// Reaching here means the workflow has finished its own cleanup; no need to overwrite status again.
|
|
1381
|
+
this.logger.debug({ optimizationId, kind, reason: options.reason }, 'optimization_finalize_no_op_already_terminal');
|
|
1382
|
+
return;
|
|
1383
|
+
}
|
|
1384
|
+
this.logger.info({ optimizationId, kind, reason: options.reason }, 'optimization_finalized');
|
|
1385
|
+
}
|
|
1386
|
+
// SPEC 25 §11.4.1: check whether the LLM result for this round is already success in DB; on hit, skip the LLM call
|
|
1387
|
+
// Strictly filter status='success'; non-success rows (rate_limited/timeout/error) are not reused — normal re-invocation
|
|
1388
|
+
async peekOptimizationRunResultImpl(optimizationId, roundNumber, source) {
|
|
1389
|
+
const row = await this.repo.findExistingOptimizationRunResult(optimizationId, roundNumber, source, {
|
|
1390
|
+
statusFilter: 'success',
|
|
1391
|
+
});
|
|
1392
|
+
if (!row)
|
|
1393
|
+
return null;
|
|
1394
|
+
return { parsedOutput: row.parsedOutput, rawResponse: row.rawResponse };
|
|
1395
|
+
}
|
|
1396
|
+
// SPEC 25 §7 dual-path linkage: on parent stop/cancel, call the child experiment's controlExperiment; parent resume also goes through this step
|
|
1397
|
+
// Child experiment is already terminal (success/failed/cancelled/stopped) or the row does not exist → swallow Conflict/NotFound
|
|
1398
|
+
// Other errors throw so DBOS step retries (poll is the backstop guaranteeing eventual consistency)
|
|
1399
|
+
async controlChildExperimentImpl(projectId, experimentId, action) {
|
|
1400
|
+
try {
|
|
1401
|
+
await this.experimentService.controlExperiment(projectId, experimentId, action, exports.SYSTEM_ACTOR_OPTIMIZATION, 'system');
|
|
1402
|
+
return { ok: true };
|
|
1403
|
+
}
|
|
1404
|
+
catch (error) {
|
|
1405
|
+
if (error instanceof common_1.ConflictException || error instanceof common_1.NotFoundException) {
|
|
1406
|
+
this.logger.warn({ projectId, experimentId, action, err: error.message }, 'optimization_child_control_skipped');
|
|
1407
|
+
return { ok: true, reason: 'already_terminal_or_invalid' };
|
|
1408
|
+
}
|
|
1409
|
+
throw error;
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
// SPEC 25 §7 resume granularity: on resume, when entering the interrupted round, check the child experiment's current status to decide the action
|
|
1413
|
+
// Returning null means the experiments row does not exist (should be very rare; usually because it was hard-deleted externally)
|
|
1414
|
+
async queryChildExperimentStatusImpl(experimentId) {
|
|
1415
|
+
const rows = await this.db
|
|
1416
|
+
.select({ status: experiments.status, controlState: experiments.controlState })
|
|
1417
|
+
.from(experiments)
|
|
1418
|
+
.where((0, drizzle_orm_1.eq)(experiments.id, experimentId))
|
|
1419
|
+
.limit(1);
|
|
1420
|
+
const row = rows[0];
|
|
1421
|
+
if (!row)
|
|
1422
|
+
return null;
|
|
1423
|
+
return { status: row.status, controlState: row.controlState };
|
|
1424
|
+
}
|
|
1425
|
+
// ---------- helpers ----------
|
|
1426
|
+
async loadModelInvocationConfig(modelId) {
|
|
1427
|
+
const [row] = await this.db.select().from(models).where((0, drizzle_orm_1.eq)(models.id, modelId)).limit(1);
|
|
1428
|
+
if (!row || !row.isActive || row.deletedAt)
|
|
1429
|
+
return null;
|
|
1430
|
+
return {
|
|
1431
|
+
id: row.id,
|
|
1432
|
+
providerType: row.providerType,
|
|
1433
|
+
providerModelId: row.providerModelId,
|
|
1434
|
+
endpoint: row.endpoint,
|
|
1435
|
+
apiKey: this.crypto.decryptApiKey(row.apiKeyEncrypted),
|
|
1436
|
+
capabilities: toModelInvocationCapabilities(row.capabilities),
|
|
1437
|
+
rpmLimit: row.rpmLimit,
|
|
1438
|
+
tpmLimit: row.tpmLimit,
|
|
1439
|
+
concurrencyLimit: row.concurrencyLimit,
|
|
1440
|
+
autoConcurrency: row.autoConcurrency,
|
|
1441
|
+
inputTokenPricePerMillion: row.inputTokenPricePerMillion,
|
|
1442
|
+
outputTokenPricePerMillion: row.outputTokenPricePerMillion,
|
|
1443
|
+
extraBody: toExtraBody(row.extraBody),
|
|
1444
|
+
};
|
|
1445
|
+
}
|
|
1446
|
+
async loadPromptVersionRef(versionId) {
|
|
1447
|
+
const [row] = await this.db
|
|
1448
|
+
.select({
|
|
1449
|
+
id: db_1.schema.promptVersions.id,
|
|
1450
|
+
promptId: db_1.schema.promptVersions.promptId,
|
|
1451
|
+
versionNumber: db_1.schema.promptVersions.versionNumber,
|
|
1452
|
+
body: db_1.schema.promptVersions.body,
|
|
1453
|
+
outputSchema: db_1.schema.promptVersions.outputSchema,
|
|
1454
|
+
promptLanguage: db_1.schema.promptVersions.promptLanguage,
|
|
1455
|
+
judgmentRules: db_1.schema.promptVersions.judgmentRules,
|
|
1456
|
+
variables: db_1.schema.promptVersions.variables,
|
|
1457
|
+
})
|
|
1458
|
+
.from(db_1.schema.promptVersions)
|
|
1459
|
+
.where((0, drizzle_orm_1.eq)(db_1.schema.promptVersions.id, versionId))
|
|
1460
|
+
.limit(1);
|
|
1461
|
+
if (!row)
|
|
1462
|
+
return null;
|
|
1463
|
+
return {
|
|
1464
|
+
id: row.id,
|
|
1465
|
+
promptId: row.promptId,
|
|
1466
|
+
versionNumber: row.versionNumber,
|
|
1467
|
+
body: row.body ?? '',
|
|
1468
|
+
outputSchema: row.outputSchema,
|
|
1469
|
+
promptLanguage: parsePromptLanguage(row.promptLanguage),
|
|
1470
|
+
judgmentRules: row.judgmentRules && typeof row.judgmentRules === 'object'
|
|
1471
|
+
? { ruleName: 'default', config: row.judgmentRules }
|
|
1472
|
+
: undefined,
|
|
1473
|
+
variables: parseVariables(row.variables),
|
|
1474
|
+
};
|
|
1475
|
+
}
|
|
1476
|
+
async waitForExperimentTerminal(experimentId, optimizationId, roundNumber, projectId) {
|
|
1477
|
+
const start = Date.now();
|
|
1478
|
+
let pollIndex = 0;
|
|
1479
|
+
let parentControlLinked = false; // Prevent the same stop/cancel signal from repeatedly calling service (after controlState is set, service throws Conflict; while swallowed, the log noise is large)
|
|
1480
|
+
while (Date.now() - start < POLL_TIMEOUT_SEC * 1000) {
|
|
1481
|
+
const [exp] = await this.db
|
|
1482
|
+
.select({ status: experiments.status, metrics: experiments.metrics })
|
|
1483
|
+
.from(experiments)
|
|
1484
|
+
.where((0, drizzle_orm_1.eq)(experiments.id, experimentId))
|
|
1485
|
+
.limit(1);
|
|
1486
|
+
if (!exp) {
|
|
1487
|
+
return { status: 'failed', metrics: null };
|
|
1488
|
+
}
|
|
1489
|
+
if (exp.status === 'success' ||
|
|
1490
|
+
exp.status === 'failed' ||
|
|
1491
|
+
exp.status === 'stopped' ||
|
|
1492
|
+
exp.status === 'cancelled') {
|
|
1493
|
+
return { status: exp.status, metrics: exp.metrics };
|
|
1494
|
+
}
|
|
1495
|
+
// SPEC 25 §7 dual-path linkage: inside poll, read parent status + control_state.
|
|
1496
|
+
// - When the parent controlState is stop/cancel, propagate to the child experiment via controlExperiment (redundant backstop:
|
|
1497
|
+
// service.tryLinkChildExperimentControl is called first; this covers the corner case where the service call failed).
|
|
1498
|
+
// - When the parent status is no longer running (service preemptive terminal write) → end poll immediately and treat the child experiment as stopped
|
|
1499
|
+
// (so finalizeRoundImpl goes through the continue branch; once the main loop top reads the parent status, it exits directly).
|
|
1500
|
+
const parent = await this.repo.findStatusAndControl(optimizationId);
|
|
1501
|
+
const parentControl = parent?.controlState ?? null;
|
|
1502
|
+
if (!parentControlLinked && (parentControl === 'stop' || parentControl === 'cancel')) {
|
|
1503
|
+
const action = parentControl;
|
|
1504
|
+
await this.controlChildExperimentStep(projectId, experimentId, action);
|
|
1505
|
+
parentControlLinked = true;
|
|
1506
|
+
this.logger.info({ optimizationId, roundNumber, experimentId, action, parentStatus: parent?.status }, 'optimization_child_control_linked_from_poll');
|
|
1507
|
+
}
|
|
1508
|
+
if (parent && parent.status !== 'running') {
|
|
1509
|
+
this.logger.info({
|
|
1510
|
+
optimizationId,
|
|
1511
|
+
roundNumber,
|
|
1512
|
+
experimentId,
|
|
1513
|
+
parentStatus: parent.status,
|
|
1514
|
+
expStatus: exp.status,
|
|
1515
|
+
}, 'optimization_round_wait_parent_terminal_exit');
|
|
1516
|
+
return { status: 'stopped', metrics: exp.metrics };
|
|
1517
|
+
}
|
|
1518
|
+
const sleepSec = POLL_SLEEP_SCHEDULE_SEC[Math.min(pollIndex, POLL_SLEEP_SCHEDULE_SEC.length - 1)] ?? 10;
|
|
1519
|
+
pollIndex += 1;
|
|
1520
|
+
await dbos_sdk_1.DBOS.sleepSeconds(sleepSec);
|
|
1521
|
+
this.logger.debug({ optimizationId, roundNumber, experimentId, pollIndex, status: exp.status, parentControlLinked }, 'optimization_round_wait_tick');
|
|
1522
|
+
}
|
|
1523
|
+
return { status: 'failed', metrics: null };
|
|
1524
|
+
}
|
|
1525
|
+
toMetricSnapshot(value) {
|
|
1526
|
+
if (!value || typeof value !== 'object')
|
|
1527
|
+
return null;
|
|
1528
|
+
const obj = value;
|
|
1529
|
+
const overall = {};
|
|
1530
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
1531
|
+
if (typeof v === 'number' && Number.isFinite(v))
|
|
1532
|
+
overall[k] = v;
|
|
1533
|
+
else if (k === 'per_class' && v && typeof v === 'object') {
|
|
1534
|
+
// handled below
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
const perClassRaw = obj['per_class'];
|
|
1538
|
+
let perClass;
|
|
1539
|
+
if (perClassRaw && typeof perClassRaw === 'object') {
|
|
1540
|
+
perClass = {};
|
|
1541
|
+
for (const [cls, metrics] of Object.entries(perClassRaw)) {
|
|
1542
|
+
if (!metrics || typeof metrics !== 'object')
|
|
1543
|
+
continue;
|
|
1544
|
+
const inner = {};
|
|
1545
|
+
for (const [mk, mv] of Object.entries(metrics)) {
|
|
1546
|
+
if (typeof mv === 'number' && Number.isFinite(mv))
|
|
1547
|
+
inner[mk] = mv;
|
|
1548
|
+
}
|
|
1549
|
+
perClass[cls] = inner;
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
return { overall, perClass };
|
|
1553
|
+
}
|
|
1554
|
+
extractAnalysisText(parsedOutput, rawResponse) {
|
|
1555
|
+
if (parsedOutput && typeof parsedOutput === 'object') {
|
|
1556
|
+
const obj = parsedOutput;
|
|
1557
|
+
if (typeof obj['errorAnalysisText'] === 'string')
|
|
1558
|
+
return obj['errorAnalysisText'];
|
|
1559
|
+
if (typeof obj['summary'] === 'string')
|
|
1560
|
+
return obj['summary'];
|
|
1561
|
+
}
|
|
1562
|
+
if (typeof rawResponse === 'string' && rawResponse.length > 0)
|
|
1563
|
+
return rawResponse;
|
|
1564
|
+
return '';
|
|
1565
|
+
}
|
|
1566
|
+
extractGeneratedDraft(parsedOutput, rawResponse, fallbackBody) {
|
|
1567
|
+
if (parsedOutput && typeof parsedOutput === 'object') {
|
|
1568
|
+
const obj = parsedOutput;
|
|
1569
|
+
const body = typeof obj['newPromptBody'] === 'string' ? obj['newPromptBody'] : null;
|
|
1570
|
+
const summary = typeof obj['changeSummary'] === 'string' ? obj['changeSummary'] : '';
|
|
1571
|
+
if (body)
|
|
1572
|
+
return { newPromptBody: body, changeSummary: summary };
|
|
1573
|
+
}
|
|
1574
|
+
if (typeof rawResponse === 'string' && rawResponse.length > 0) {
|
|
1575
|
+
return { newPromptBody: rawResponse, changeSummary: '' };
|
|
1576
|
+
}
|
|
1577
|
+
return { newPromptBody: fallbackBody, changeSummary: 'restored_from_failed_replay' };
|
|
1578
|
+
}
|
|
1579
|
+
};
|
|
1580
|
+
exports.OptimizationWorkflowRegistrar = OptimizationWorkflowRegistrar;
|
|
1581
|
+
exports.OptimizationWorkflowRegistrar = OptimizationWorkflowRegistrar = __decorate([
|
|
1582
|
+
(0, common_1.Injectable)(),
|
|
1583
|
+
__param(0, (0, common_1.Inject)(database_constants_1.DATABASE_CLIENT)),
|
|
1584
|
+
__param(6, (0, common_1.Inject)(redis_constants_1.REDIS_LIMITER)),
|
|
1585
|
+
__metadata("design:paramtypes", [Object, optimization_repository_1.OptimizationRepository,
|
|
1586
|
+
prompt_repository_1.PromptRepository,
|
|
1587
|
+
experiment_workflow_1.ExperimentWorkflowRegistrar,
|
|
1588
|
+
experiment_service_1.ExperimentService,
|
|
1589
|
+
crypto_service_1.CryptoService, Object, run_result_writer_1.DrizzleRunResultWriter,
|
|
1590
|
+
limiter_key_strategy_1.LimiterKeyStrategy,
|
|
1591
|
+
runtime_limits_provider_1.RuntimeLimitsProvider,
|
|
1592
|
+
quota_policy_hook_1.QuotaPolicyHook])
|
|
1593
|
+
], OptimizationWorkflowRegistrar);
|
|
1594
|
+
// ---------- module-level helpers ----------
|
|
1595
|
+
function mapRunResultsForStrategy(rows) {
|
|
1596
|
+
return rows.map((r) => ({
|
|
1597
|
+
id: r.id,
|
|
1598
|
+
sampleId: r.sampleId ?? '',
|
|
1599
|
+
parsedOutput: r.parsedOutput,
|
|
1600
|
+
decisionOutput: r.decisionOutput,
|
|
1601
|
+
isCorrect: r.isCorrect,
|
|
1602
|
+
errorMessage: r.errorMessage,
|
|
1603
|
+
rawResponse: r.rawResponse,
|
|
1604
|
+
}));
|
|
1605
|
+
}
|
|
1606
|
+
// SPEC 25 §11.4.1: reconstruct the strategy package analyzeFailures return shape from run_results.parsed_output,
|
|
1607
|
+
// so that prepareRoundImpl can skip the actual call when the LLM already has a success result.
|
|
1608
|
+
// Note: on the reuse path, batches/confusionPairs/regressionGroups are left empty — downstream generateNextVersion only reads
|
|
1609
|
+
// errorAnalysisText, not these fields; the detail page also reads run_results.parsed_output directly and does not depend on workflow memory.
|
|
1610
|
+
function reconstructAnalysisFromRunResult(row) {
|
|
1611
|
+
const parsed = isObject(row.parsedOutput) ? row.parsedOutput : null;
|
|
1612
|
+
const summaryText = readSummaryText(parsed, row.rawResponse);
|
|
1613
|
+
const errorPatterns = parsed && Array.isArray(parsed['errorPatterns'])
|
|
1614
|
+
? parsed['errorPatterns']
|
|
1615
|
+
: [];
|
|
1616
|
+
const suggestedChanges = parsed && Array.isArray(parsed['suggestedChanges'])
|
|
1617
|
+
? parsed['suggestedChanges']
|
|
1618
|
+
: [];
|
|
1619
|
+
const conflicts = parsed && Array.isArray(parsed['conflicts'])
|
|
1620
|
+
? parsed['conflicts']
|
|
1621
|
+
: [];
|
|
1622
|
+
const summary = {
|
|
1623
|
+
summary: summaryText,
|
|
1624
|
+
errorPatterns,
|
|
1625
|
+
suggestedChanges,
|
|
1626
|
+
conflicts,
|
|
1627
|
+
evidenceBundleVersion: 1,
|
|
1628
|
+
truncated: false,
|
|
1629
|
+
rawContent: row.rawResponse ?? '',
|
|
1630
|
+
};
|
|
1631
|
+
const fallbackBundle = {
|
|
1632
|
+
evidenceBundleVersion: 1,
|
|
1633
|
+
summary: summaryText,
|
|
1634
|
+
errorPatterns,
|
|
1635
|
+
suggestedChanges,
|
|
1636
|
+
conflicts,
|
|
1637
|
+
sourceStats: {
|
|
1638
|
+
batchCount: 0,
|
|
1639
|
+
totalConfusionFailures: 0,
|
|
1640
|
+
totalRegressionSamples: 0,
|
|
1641
|
+
truncated: false,
|
|
1642
|
+
},
|
|
1643
|
+
};
|
|
1644
|
+
const evidenceBundle = parsed && isObject(parsed['evidenceBundle'])
|
|
1645
|
+
? { ...fallbackBundle, ...parsed['evidenceBundle'] }
|
|
1646
|
+
: fallbackBundle;
|
|
1647
|
+
return {
|
|
1648
|
+
errorAnalysisText: summaryText,
|
|
1649
|
+
summary,
|
|
1650
|
+
evidenceBundle,
|
|
1651
|
+
batches: [],
|
|
1652
|
+
confusionPairs: [],
|
|
1653
|
+
regressionGroups: [],
|
|
1654
|
+
truncated: evidenceBundle.sourceStats?.truncated ?? false,
|
|
1655
|
+
totalConfusionFailures: evidenceBundle.sourceStats?.totalConfusionFailures ?? 0,
|
|
1656
|
+
totalRegressionSamples: evidenceBundle.sourceStats?.totalRegressionSamples ?? 0,
|
|
1657
|
+
};
|
|
1658
|
+
}
|
|
1659
|
+
// SPEC 25 §11.4.1: reconstruct generateNextVersion return fields from run_results.parsed_output.
|
|
1660
|
+
// A success row's parsed_output is guaranteed to have newPromptBody; rawResponse is a fallback for extreme races.
|
|
1661
|
+
// newOutputSchema / outputSchemaChangeReason are optional: present when the LLM provides one and it passes validation.
|
|
1662
|
+
// autoPatched / patchedVariables are written into parsedOutput by the generate retry+patch path; the workflow also passes them through on reuse.
|
|
1663
|
+
function reconstructGenerateFromRunResult(row, fallbackBody) {
|
|
1664
|
+
if (isObject(row.parsedOutput)) {
|
|
1665
|
+
const obj = row.parsedOutput;
|
|
1666
|
+
const body = typeof obj['newPromptBody'] === 'string' ? obj['newPromptBody'] : null;
|
|
1667
|
+
const summary = typeof obj['changeSummary'] === 'string' ? obj['changeSummary'] : '';
|
|
1668
|
+
if (body) {
|
|
1669
|
+
const result = { newPromptBody: body, changeSummary: summary };
|
|
1670
|
+
if (isObject(obj['newOutputSchema'])) {
|
|
1671
|
+
result.newOutputSchema = obj['newOutputSchema'];
|
|
1672
|
+
}
|
|
1673
|
+
if (typeof obj['outputSchemaChangeReason'] === 'string' &&
|
|
1674
|
+
obj['outputSchemaChangeReason'].length > 0) {
|
|
1675
|
+
result.outputSchemaChangeReason = obj['outputSchemaChangeReason'];
|
|
1676
|
+
}
|
|
1677
|
+
if (typeof obj['autoPatched'] === 'boolean') {
|
|
1678
|
+
result.autoPatched = obj['autoPatched'];
|
|
1679
|
+
}
|
|
1680
|
+
if (Array.isArray(obj['patchedVariables'])) {
|
|
1681
|
+
result.patchedVariables = obj['patchedVariables'].filter((v) => typeof v === 'string');
|
|
1682
|
+
}
|
|
1683
|
+
return result;
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
if (typeof row.rawResponse === 'string' && row.rawResponse.length > 0) {
|
|
1687
|
+
return { newPromptBody: row.rawResponse, changeSummary: '' };
|
|
1688
|
+
}
|
|
1689
|
+
return { newPromptBody: fallbackBody, changeSummary: 'restored_from_reuse' };
|
|
1690
|
+
}
|
|
1691
|
+
// Extract LLM-self-reported appliedTips from generate run_results.parsed_output; filter out invalid items.
|
|
1692
|
+
// Used by buildRoundHistoryEntries to back out the "toolbox rotation hint" basis (SPEC 25 §11.3). Legacy data / parse failure → [].
|
|
1693
|
+
function extractAppliedTipsFromGenerateParsedOutput(parsedGen) {
|
|
1694
|
+
if (!parsedGen || typeof parsedGen !== 'object')
|
|
1695
|
+
return [];
|
|
1696
|
+
const tips = parsedGen.appliedTips;
|
|
1697
|
+
if (!Array.isArray(tips))
|
|
1698
|
+
return [];
|
|
1699
|
+
return tips.filter((t) => typeof t === 'string' && t.length > 0);
|
|
1700
|
+
}
|
|
1701
|
+
function isObject(value) {
|
|
1702
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
1703
|
+
}
|
|
1704
|
+
function readSummaryText(parsed, rawResponse) {
|
|
1705
|
+
if (parsed) {
|
|
1706
|
+
if (typeof parsed['summary'] === 'string')
|
|
1707
|
+
return parsed['summary'];
|
|
1708
|
+
if (typeof parsed['errorAnalysisText'] === 'string')
|
|
1709
|
+
return parsed['errorAnalysisText'];
|
|
1710
|
+
}
|
|
1711
|
+
if (typeof rawResponse === 'string' && rawResponse.length > 0)
|
|
1712
|
+
return rawResponse;
|
|
1713
|
+
return '';
|
|
1714
|
+
}
|
|
1715
|
+
function normalizeOptionalText(value) {
|
|
1716
|
+
const trimmed = value?.trim();
|
|
1717
|
+
return trimmed && trimmed.length > 0 ? trimmed : null;
|
|
1718
|
+
}
|
|
1719
|
+
function readOptimizationHintFromContext(ctx) {
|
|
1720
|
+
const direct = normalizeOptionalText(ctx.optimizationHint);
|
|
1721
|
+
if (direct)
|
|
1722
|
+
return direct;
|
|
1723
|
+
const runConfig = ctx.runConfig && typeof ctx.runConfig === 'object' ? ctx.runConfig : {};
|
|
1724
|
+
const legacy = typeof runConfig['optimizationHint'] === 'string' ? runConfig['optimizationHint'] : undefined;
|
|
1725
|
+
return normalizeOptionalText(legacy) ?? undefined;
|
|
1726
|
+
}
|
|
1727
|
+
// Normalize LLM call / parsing exceptions into round_steps.error_class + error_message.
|
|
1728
|
+
// errorMessage length truncated to 1000 chars to prevent a giant stack from being stuffed into the DB.
|
|
1729
|
+
function normalizeErrorForStep(err) {
|
|
1730
|
+
const errObj = err;
|
|
1731
|
+
const errorClass = typeof errObj?.name === 'string' && errObj.name.length > 0 ? errObj.name : 'Error';
|
|
1732
|
+
const rawMsg = typeof errObj?.message === 'string' ? errObj.message : String(err ?? 'unknown');
|
|
1733
|
+
return { errorClass, errorMessage: rawMsg.slice(0, 1000) };
|
|
1734
|
+
}
|
|
1735
|
+
// SPEC 25 §11 child experiment runConfig inheritance: optimizations.run_config is parsed against experimentRunConfigSchema;
|
|
1736
|
+
// unknown fields (stopAfterNoImprovementRounds, etc.) are preserved by catchall; later, service
|
|
1737
|
+
// parseRunConfig filters again with the same schema, ensuring child experiment runConfig only exposes the experimentRunConfigSchema field set.
|
|
1738
|
+
function parseChildRunConfigFromOptimization(value) {
|
|
1739
|
+
const parsed = shared_1.experimentRunConfigSchema.safeParse(value ?? {});
|
|
1740
|
+
return parsed.success ? parsed.data : {};
|
|
1741
|
+
}
|
|
1742
|
+
function invalidSnapshot(reason) {
|
|
1743
|
+
return {
|
|
1744
|
+
ok: false,
|
|
1745
|
+
reason,
|
|
1746
|
+
projectId: '',
|
|
1747
|
+
optimizationName: '',
|
|
1748
|
+
promptId: null,
|
|
1749
|
+
baseVersionId: null,
|
|
1750
|
+
basePromptVersion: null,
|
|
1751
|
+
datasetId: '',
|
|
1752
|
+
datasetSampleCount: 0,
|
|
1753
|
+
startingMode: '',
|
|
1754
|
+
sourceExperimentId: null,
|
|
1755
|
+
promptLanguage: shared_1.DEFAULT_PROMPT_LANGUAGE,
|
|
1756
|
+
analysisModel: emptyModel(),
|
|
1757
|
+
analysisLimiterKey: '',
|
|
1758
|
+
taskModel: emptyModel(),
|
|
1759
|
+
goals: [],
|
|
1760
|
+
fieldWhitelist: { promptVariables: [] },
|
|
1761
|
+
strategy: '',
|
|
1762
|
+
strategyConfig: optimization_strategy_1.DEFAULT_ERROR_PATTERN_ANALYSIS_CONFIG,
|
|
1763
|
+
maxRounds: 0,
|
|
1764
|
+
optimizationHint: undefined,
|
|
1765
|
+
createdBy: '',
|
|
1766
|
+
nextRound: 1,
|
|
1767
|
+
bestVersion: null,
|
|
1768
|
+
bestMetrics: emptyMetrics(),
|
|
1769
|
+
childRunConfig: {},
|
|
1770
|
+
resumeChildExpId: null,
|
|
1771
|
+
};
|
|
1772
|
+
}
|
|
1773
|
+
function emptyMetrics() {
|
|
1774
|
+
return { overall: {} };
|
|
1775
|
+
}
|
|
1776
|
+
function emptyModel() {
|
|
1777
|
+
return {
|
|
1778
|
+
id: '',
|
|
1779
|
+
providerType: '',
|
|
1780
|
+
providerModelId: '',
|
|
1781
|
+
endpoint: '',
|
|
1782
|
+
apiKey: '',
|
|
1783
|
+
rpmLimit: 0,
|
|
1784
|
+
tpmLimit: 0,
|
|
1785
|
+
concurrencyLimit: 0,
|
|
1786
|
+
autoConcurrency: false,
|
|
1787
|
+
inputTokenPricePerMillion: 0,
|
|
1788
|
+
outputTokenPricePerMillion: 0,
|
|
1789
|
+
extraBody: {},
|
|
1790
|
+
};
|
|
1791
|
+
}
|
|
1792
|
+
function parsePromptLanguage(value) {
|
|
1793
|
+
const parse = shared_1.promptLanguageSchema.safeParse(value);
|
|
1794
|
+
return parse.success ? parse.data : shared_1.DEFAULT_PROMPT_LANGUAGE;
|
|
1795
|
+
}
|
|
1796
|
+
function toModelInvocationCapabilities(raw) {
|
|
1797
|
+
if (raw && typeof raw === 'object' && !Array.isArray(raw)) {
|
|
1798
|
+
const image = raw.image;
|
|
1799
|
+
if (typeof image === 'string' && ['none', 'url', 'base64', 'both'].includes(image)) {
|
|
1800
|
+
return { image: image };
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
return { image: 'none' };
|
|
1804
|
+
}
|
|
1805
|
+
function toExtraBody(raw) {
|
|
1806
|
+
if (raw && typeof raw === 'object' && !Array.isArray(raw)) {
|
|
1807
|
+
return { ...raw };
|
|
1808
|
+
}
|
|
1809
|
+
return {};
|
|
1810
|
+
}
|
|
1811
|
+
function toLoopGoal(dto) {
|
|
1812
|
+
const op = dto.comparator === 'gte' ? '>=' : dto.comparator === 'gt' ? '>' : '<=';
|
|
1813
|
+
const scope = dto.scope === 'overall' || !dto.scope
|
|
1814
|
+
? { kind: 'overall' }
|
|
1815
|
+
: { kind: 'class', label: dto.scope };
|
|
1816
|
+
return {
|
|
1817
|
+
metric: dto.metric,
|
|
1818
|
+
op,
|
|
1819
|
+
value: dto.target,
|
|
1820
|
+
scope,
|
|
1821
|
+
};
|
|
1822
|
+
}
|
|
1823
|
+
function toLoopFieldWhitelist(dto, expectedField) {
|
|
1824
|
+
if (!dto)
|
|
1825
|
+
return { promptVariables: [] };
|
|
1826
|
+
// DTO uses inputFields / metaFields; the strategy package uses promptVariables / analysisOnlyFields.
|
|
1827
|
+
// Semantic mapping: inputFields = variables that may appear in the prompt template; metaFields = metadata fields shown only to the analysis LLM.
|
|
1828
|
+
// SPEC 25 §9 expresses these two concepts as one; the DTO is the current minimal set; modifiableSections has no DTO field yet and is left empty.
|
|
1829
|
+
//
|
|
1830
|
+
// Safety constraint: the expected_field referenced by judgment rules (default expected_output) is the ground truth;
|
|
1831
|
+
// the business prompt MUST NOT inject it as a variable (it would leak the answer). If the UI / DTO puts it into inputFields (the frontend by default
|
|
1832
|
+
// dumps every dataset field), we strip it here and demote to analysisOnlyFields — eliminating leakage and
|
|
1833
|
+
// avoiding the over-reactive "defensive" response where the generate LLM sees this field name and strips every {{var}}.
|
|
1834
|
+
const inputFields = dto.inputFields.filter((f) => f !== expectedField);
|
|
1835
|
+
const analysisOnly = [...dto.metaFields, ...(dto.inputFields.includes(expectedField) ? [expectedField] : [])];
|
|
1836
|
+
return {
|
|
1837
|
+
promptVariables: inputFields,
|
|
1838
|
+
analysisOnlyFields: analysisOnly.length > 0 ? analysisOnly : undefined,
|
|
1839
|
+
};
|
|
1840
|
+
}
|
|
1841
|
+
function parseVariables(raw) {
|
|
1842
|
+
if (!Array.isArray(raw))
|
|
1843
|
+
return [];
|
|
1844
|
+
const list = [];
|
|
1845
|
+
for (const item of raw) {
|
|
1846
|
+
const parse = shared_1.promptVariableSchema.safeParse(item);
|
|
1847
|
+
if (parse.success)
|
|
1848
|
+
list.push(parse.data);
|
|
1849
|
+
}
|
|
1850
|
+
return list;
|
|
1851
|
+
}
|
|
1852
|
+
function deterministicUuid(seed) {
|
|
1853
|
+
const hash = (0, node_crypto_1.createHash)('sha1').update(`${OPTIMIZATION_NS}:${seed}`).digest();
|
|
1854
|
+
const bytes = Buffer.from(hash.subarray(0, 16));
|
|
1855
|
+
bytes[6] = ((bytes[6] ?? 0) & 0x0f) | 0x50; // version 5
|
|
1856
|
+
bytes[8] = ((bytes[8] ?? 0) & 0x3f) | 0x80; // RFC 4122 variant
|
|
1857
|
+
const hex = bytes.toString('hex');
|
|
1858
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
|
|
1859
|
+
}
|
|
1860
|
+
function isPromptBaselineBootstrapNeeded(startingMode) {
|
|
1861
|
+
// SPEC 25 §2.1: from_dataset_only, after generateFirstVersionStep completes, follows the same baseline experiment flow as
|
|
1862
|
+
// from_prompt_version.
|
|
1863
|
+
return startingMode === 'from_prompt_version' || startingMode === 'from_dataset_only';
|
|
1864
|
+
}
|
|
1865
|
+
// SPEC 25 §2.1 maps a first-version-generation Error into a finalize reason code.
|
|
1866
|
+
// FirstVersionParseError → first_version_parse_failed_v1
|
|
1867
|
+
// The message already carries the `first_version_*_v1` prefix → take the prefix directly
|
|
1868
|
+
// Others (network errors / LLM rate-limit exhaustion / panic) → first_version_generation_failed_v1
|
|
1869
|
+
function mapFirstVersionErrorReason(error) {
|
|
1870
|
+
if (error instanceof optimization_strategy_1.FirstVersionParseError)
|
|
1871
|
+
return 'first_version_parse_failed_v1';
|
|
1872
|
+
if (error instanceof Error) {
|
|
1873
|
+
const msg = error.message;
|
|
1874
|
+
if (msg.startsWith('first_version_dataset_empty_v1'))
|
|
1875
|
+
return 'first_version_dataset_empty_v1';
|
|
1876
|
+
if (msg.startsWith('first_version_parse_failed_v1'))
|
|
1877
|
+
return 'first_version_parse_failed_v1';
|
|
1878
|
+
if (msg.startsWith('first_version_generation_failed_v1')) {
|
|
1879
|
+
// Carries a sub-reason (e.g. :context_missing) → keep the prefix portion so the frontend mapping is more fine-grained
|
|
1880
|
+
return msg.split(':')[0] ?? 'first_version_generation_failed_v1';
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
return 'first_version_generation_failed_v1';
|
|
1884
|
+
}
|
|
1885
|
+
// SPEC 25 §2.1: sampling for first-version generation — on replay, seed is pinned to `${optimizationId}:first-version`,
|
|
1886
|
+
// guaranteeing the same batch of samples is drawn across replays; the LLM run_result is also written only once via a deterministic id.
|
|
1887
|
+
function pickRandomSamples(items, n, seed) {
|
|
1888
|
+
if (items.length <= n)
|
|
1889
|
+
return items.slice();
|
|
1890
|
+
// seedable PRNG (xorshift32) — not crypto-strength, only requires replay consistency; state=0 self-locks, hence || 1
|
|
1891
|
+
let state = 0;
|
|
1892
|
+
for (const ch of seed)
|
|
1893
|
+
state = (state * 31 + ch.charCodeAt(0)) >>> 0;
|
|
1894
|
+
if (state === 0)
|
|
1895
|
+
state = 1;
|
|
1896
|
+
const rng = () => {
|
|
1897
|
+
state ^= state << 13;
|
|
1898
|
+
state ^= state >>> 17;
|
|
1899
|
+
state ^= state << 5;
|
|
1900
|
+
return ((state >>> 0) % 1_000_000) / 1_000_000;
|
|
1901
|
+
};
|
|
1902
|
+
const out = items.slice();
|
|
1903
|
+
for (let i = 0; i < n; i++) {
|
|
1904
|
+
const j = i + Math.floor(rng() * (out.length - i));
|
|
1905
|
+
const tmp = out[i];
|
|
1906
|
+
out[i] = out[j];
|
|
1907
|
+
out[j] = tmp;
|
|
1908
|
+
}
|
|
1909
|
+
return out.slice(0, n);
|
|
1910
|
+
}
|
|
1911
|
+
function computeOptimizationBaselineExperimentId(optimizationId) {
|
|
1912
|
+
return deterministicUuid(`${optimizationId}:baseline:experiment`);
|
|
1913
|
+
}
|
|
1914
|
+
function buildOptimizationExperimentName(optimizationName, round, options = {}) {
|
|
1915
|
+
const maxLength = options.maxLength ?? OPTIMIZATION_EXPERIMENT_NAME_MAX_LENGTH;
|
|
1916
|
+
const trimmedName = optimizationName.trim() || OPTIMIZATION_EXPERIMENT_NAME_FALLBACK;
|
|
1917
|
+
const roundLabel = round === 'baseline' ? 'baseline' : `R${round}`;
|
|
1918
|
+
const collisionSuffix = options.collisionSalt
|
|
1919
|
+
? `${OPTIMIZATION_EXPERIMENT_NAME_SEPARATOR}${shortHash(options.collisionSalt, 6)}`
|
|
1920
|
+
: '';
|
|
1921
|
+
const fixedSuffix = `${OPTIMIZATION_EXPERIMENT_NAME_SEPARATOR}${roundLabel}${collisionSuffix}`;
|
|
1922
|
+
const prefixMaxLength = Math.max(1, maxLength - fixedSuffix.length);
|
|
1923
|
+
const prefix = trimmedName.length <= prefixMaxLength ? trimmedName : trimmedName.slice(0, prefixMaxLength).trimEnd();
|
|
1924
|
+
return `${prefix || OPTIMIZATION_EXPERIMENT_NAME_FALLBACK}${fixedSuffix}`;
|
|
1925
|
+
}
|
|
1926
|
+
function shortHash(value, length) {
|
|
1927
|
+
return (0, node_crypto_1.createHash)('sha1').update(value).digest('hex').slice(0, length);
|
|
1928
|
+
}
|
|
1929
|
+
function readJudgmentDecisionField(outputSchema) {
|
|
1930
|
+
if (!outputSchema || typeof outputSchema !== 'object' || Array.isArray(outputSchema))
|
|
1931
|
+
return null;
|
|
1932
|
+
const fields = outputSchema['fields'];
|
|
1933
|
+
if (!Array.isArray(fields))
|
|
1934
|
+
return null;
|
|
1935
|
+
for (const field of fields) {
|
|
1936
|
+
if (!field || typeof field !== 'object' || Array.isArray(field))
|
|
1937
|
+
continue;
|
|
1938
|
+
const record = field;
|
|
1939
|
+
if (record['isJudgment'] !== true && record['is_decision'] !== true && record['judgment'] !== true)
|
|
1940
|
+
continue;
|
|
1941
|
+
const key = record['key'] ?? record['name'];
|
|
1942
|
+
if (typeof key === 'string' && key.trim().length > 0)
|
|
1943
|
+
return key.trim();
|
|
1944
|
+
}
|
|
1945
|
+
return null;
|
|
1946
|
+
}
|
|
1947
|
+
function deriveJudgmentRulesFromOutputSchema(outputSchema, expectedField = 'expected_output') {
|
|
1948
|
+
return {
|
|
1949
|
+
mode: 'exact_match',
|
|
1950
|
+
expected_field: expectedField,
|
|
1951
|
+
decision_field: readJudgmentDecisionField(outputSchema) ?? 'label',
|
|
1952
|
+
};
|
|
1953
|
+
}
|
|
1954
|
+
function normalizeBaselineExperimentStatus(status) {
|
|
1955
|
+
if (status === 'success' || status === 'failed' || status === 'stopped' || status === 'cancelled') {
|
|
1956
|
+
return status;
|
|
1957
|
+
}
|
|
1958
|
+
return 'running';
|
|
1959
|
+
}
|
|
1960
|
+
// Behavior matches experiment.workflow.ts's same-name helper: reads the expected field name from judgmentRules JSONB,
|
|
1961
|
+
// default 'expected_output'. The two channels keep their implementations separate to avoid cross-module coupling (SPEC 23/24's judgmentRules contract)
|
|
1962
|
+
function readExpectedField(rules) {
|
|
1963
|
+
if (rules && typeof rules === 'object') {
|
|
1964
|
+
const record = rules;
|
|
1965
|
+
const f = record['expected_field'] ?? record['expectedField'];
|
|
1966
|
+
if (typeof f === 'string' && f.length > 0)
|
|
1967
|
+
return f;
|
|
1968
|
+
const rawRules = record['rules'];
|
|
1969
|
+
if (Array.isArray(rawRules)) {
|
|
1970
|
+
for (const rule of rawRules) {
|
|
1971
|
+
if (!rule || typeof rule !== 'object' || Array.isArray(rule))
|
|
1972
|
+
continue;
|
|
1973
|
+
const nested = rule['expected_field'] ??
|
|
1974
|
+
rule['expectedField'] ??
|
|
1975
|
+
rule['value'];
|
|
1976
|
+
if (typeof nested === 'string' && nested.length > 0)
|
|
1977
|
+
return nested;
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1980
|
+
}
|
|
1981
|
+
return 'expected_output';
|
|
1982
|
+
}
|
|
1983
|
+
// Project the dataset's raw samples into SampleRecord consumed by the strategy package; expected is pulled from data[expectedField];
|
|
1984
|
+
// when absent, leave undefined; let the downstream confusion-pairs' asLabel decide whether to filter
|
|
1985
|
+
function buildSamplesForStrategy(samplesRaw, judgmentRulesConfig) {
|
|
1986
|
+
const expectedField = readExpectedField(judgmentRulesConfig);
|
|
1987
|
+
return samplesRaw.map((s) => {
|
|
1988
|
+
const rawExpected = s.data[expectedField];
|
|
1989
|
+
return {
|
|
1990
|
+
id: s.id,
|
|
1991
|
+
input: s.data,
|
|
1992
|
+
expected: rawExpected == null ? undefined : rawExpected,
|
|
1993
|
+
};
|
|
1994
|
+
});
|
|
1995
|
+
}
|
|
1996
|
+
// Expose the stable id computation for e2e / mcp callers
|
|
1997
|
+
function computeOptimizationVersionId(optimizationId, roundNumber) {
|
|
1998
|
+
return deterministicUuid(`${optimizationId}:${roundNumber}:version`);
|
|
1999
|
+
}
|
|
2000
|
+
function computeOptimizationExperimentId(optimizationId, roundNumber) {
|
|
2001
|
+
return deterministicUuid(`${optimizationId}:${roundNumber}:experiment`);
|
|
2002
|
+
}
|
|
2003
|
+
// These two variables come from the schema but are not directly imported; keep the corresponding types for later step usage
|
|
2004
|
+
void runResults;
|
|
2005
|
+
void drizzle_orm_1.asc;
|
|
2006
|
+
void drizzle_orm_1.and;
|
|
2007
|
+
//# sourceMappingURL=optimization.workflow.js.map
|