@polymorphism-tech/morph-spec 8.38.0 → 8.41.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +20 -9
- package/README.md +2 -2
- package/bin/morph-spec.js +60 -43
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +4 -3
- package/framework/MORPH.md +10 -9
- package/framework/agents.json +43 -26
- package/framework/docs/CLI.md +7 -5
- package/framework/evals/rubrics/code-quality.md +4 -4
- package/framework/evals/rubrics/condensed.md +2 -2
- package/framework/hooks/README.md +1 -1
- package/framework/hooks/claude-code/core/telemetry-log.js +39 -8
- package/framework/hooks/claude-code/core/worktree-create.js +22 -6
- package/framework/hooks/shared/git-paths.js +38 -5
- package/framework/mcp/registry.json +58 -66
- package/framework/rules/infrastructure-standards.md +0 -1
- package/framework/rules/morph-workflow.md +9 -4
- package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
- package/framework/skills/core/morph-apply/SKILL.md +59 -25
- package/framework/skills/core/morph-brainstorming/SKILL.md +1 -1
- package/framework/skills/core/morph-eval/SKILL.md +1 -1
- package/framework/skills/core/morph-hotfix/SKILL.md +2 -2
- package/framework/skills/core/morph-implement/SKILL.md +48 -24
- package/framework/skills/core/morph-media/SKILL.md +2 -2
- package/framework/skills/core/morph-plan/SKILL.md +10 -3
- package/framework/skills/core/morph-proposal/SKILL.md +1 -1
- package/framework/skills/core/morph-review/SKILL.md +1 -1
- package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
- package/framework/skills/core/morph-standards/SKILL.md +1 -1
- package/framework/skills/core/morph-uiux/SKILL.md +7 -6
- package/framework/standards/PROVENANCE.json +4 -4
- package/framework/standards/STANDARDS.json +9 -9
- package/framework/standards/ai-agents/context-providers.md +16 -26
- package/framework/standards/ai-agents/durable-workflows-hitl.md +15 -21
- package/framework/standards/ai-agents/modalities-image-gen.md +21 -25
- package/framework/standards/ai-agents/modalities-speech-to-text.md +4 -15
- package/framework/standards/ai-agents/modalities-text-to-speech.md +2 -11
- package/framework/standards/ai-agents/modalities-vision.md +1 -18
- package/framework/standards/ai-agents/prompt-sources.md +0 -4
- package/framework/standards/architecture/code-comments.md +52 -16
- package/framework/standards/backend/dotnet/testing.md +4 -2
- package/framework/standards/backend/integrations/neon-auth/neon-auth.md +63 -50
- package/framework/standards/frontend/design-system/ai-image-generation.md +42 -28
- package/framework/standards/frontend/design-system/ai-video-generation.md +24 -9
- package/framework/standards/frontend/nextjs/testing.md +4 -2
- package/framework/standards/integration/mcp/mcp-tools.md +64 -126
- package/framework/templates/code/dotnet/abstractions/result.cs +0 -2
- package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +26 -39
- package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +28 -68
- package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +99 -117
- package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +2 -44
- package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +40 -61
- package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +54 -107
- package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +53 -58
- package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +192 -281
- package/framework/templates/code/dotnet/database/migration.cs +49 -83
- package/framework/templates/code/dotnet/database/vector-entity.cs +2 -46
- package/framework/templates/code/dotnet/extensions/result-extensions.cs +0 -2
- package/framework/templates/code/dotnet/feature/validator.cs.hbs +11 -15
- package/framework/templates/code/dotnet/jobs/agent.cs +3 -51
- package/framework/templates/code/dotnet/jobs/job.cs +2 -97
- package/framework/templates/code/dotnet/test.cs +2 -87
- package/framework/templates/code/typescript/contracts.ts +140 -167
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentApi.cs +1 -48
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactory.cs +3 -79
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactoryOptions.cs +1 -70
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpec.cs +1 -47
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpecOptions.cs +2 -99
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/AiKitAssemblyMarker.cs +0 -6
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/ConversationStore.cs +3 -88
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/IConversationStore.cs +1 -41
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/InMemoryConversationStore.cs +0 -21
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/CountingChatClient.cs +1 -25
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalAssertion.cs +2 -63
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheMissException.cs +0 -30
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheOptions.cs +1 -52
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheStamp.cs +3 -78
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalFixture.cs +4 -109
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalMode.cs +0 -22
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalQualityEvaluators.cs +1 -36
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalScenario.cs +2 -57
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalSummary.cs +0 -75
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/OfflineChatClient.cs +2 -59
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/ShortPathResponseCache.cs +4 -111
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaGenerator.cs +0 -34
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaProvider.cs +1 -57
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaArtifact.cs +0 -19
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaDisclosure.cs +2 -74
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaGenerator.cs +2 -33
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaIdempotencyKey.cs +1 -43
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJob.cs +1 -96
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobId.cs +0 -13
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobState.cs +0 -27
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaPricing.cs +0 -33
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaProvenance.cs +0 -31
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaRequest.cs +3 -75
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaResult.cs +0 -42
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaReview.cs +0 -39
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaSeams.cs +1 -55
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaStore.cs +3 -59
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MorphMedia.cs +0 -46
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/GoogleImageProvider.cs +3 -28
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/OpenAIImageProvider.cs +7 -61
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/VeoVideoProvider.cs +2 -37
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/IUsageCostSink.cs +1 -23
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostMiddleware.cs +2 -82
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostRecord.cs +1 -68
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphOpenTelemetryExtensions.cs +1 -62
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphTelemetryOptions.cs +2 -62
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/IProviderClientFactory.cs +1 -25
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelAlias.cs +0 -124
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelRegistry.cs +1 -73
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaIngest.cs +1 -66
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaPlan.cs +0 -41
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentFactoryTests.cs +2 -112
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentSpecTests.cs +2 -43
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ConversationStoreTests.cs +5 -110
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/DeltaIngestTests.cs +7 -103
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalAssertionTests.cs +1 -42
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheMissTests.cs +1 -30
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheStampTests.cs +1 -19
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFailuresTests.cs +0 -48
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFixtureTests.cs +1 -29
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalRefreshTests.cs +2 -38
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalWorkspace.cs +1 -14
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/KitEvals.cs +4 -69
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/OfflineChatClientTests.cs +1 -10
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClient.cs +1 -57
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClientTests.cs +0 -26
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeClock.cs +1 -19
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeMediaProvider.cs +0 -32
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeProviderClientFactory.cs +0 -14
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeUsageCostSink.cs +0 -24
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/InMemoryMediaStore.cs +0 -32
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAccessibilityTests.cs +0 -37
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAliasIsNotAnAgentTests.cs +0 -24
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaDisclosureTests.cs +0 -78
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaGeneratorTests.cs +0 -53
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaIdempotencyKeyTests.cs +0 -44
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaJobStateTests.cs +0 -44
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaReviewTests.cs +0 -19
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaStoreTests.cs +0 -24
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ModelRegistryTests.cs +6 -48
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/SkeletonSmokeTests.cs +1 -13
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/TelemetryTests.cs +2 -71
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/UsageCostMiddlewareTests.cs +5 -100
- package/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +39 -43
- package/framework/templates/frontend/nextjs/env.mjs.hbs +29 -32
- package/framework/templates/frontend/nextjs/page.tsx.hbs +21 -22
- package/framework/templates/frontend/nextjs/use-feature.ts.hbs +42 -54
- package/framework/templates/integrations/asaas-client.cs +332 -387
- package/framework/templates/integrations/asaas-webhook.cs +290 -351
- package/framework/templates/integrations/neon-auth-config.cs +9 -105
- package/framework/templates/project/validate.js +97 -120
- package/package.json +2 -2
- package/src/commands/dashboard/dashboard.js +80 -53
- package/src/commands/mcp/mcp-setup.js +37 -9
- package/src/commands/project/doctor.js +12 -6
- package/src/commands/project/init.js +6 -3
- package/src/commands/project/install-plugin-cmd.js +1 -1
- package/src/commands/project/update.js +5 -0
- package/src/commands/project/worktree.js +60 -4
- package/src/commands/state/gate-check.js +5 -0
- package/src/commands/tasks/dag.js +27 -4
- package/src/commands/validation/verify.js +22 -38
- package/src/dashboard/app/App.jsx +80 -16
- package/src/dashboard/app/components/ActivityFeed.jsx +172 -0
- package/src/dashboard/app/components/CommandPalette.jsx +155 -0
- package/src/dashboard/app/components/DetailPanel.jsx +47 -11
- package/src/dashboard/app/components/EntityLink.jsx +55 -0
- package/src/dashboard/app/components/ErrorBoundary.jsx +54 -0
- package/src/dashboard/app/components/FlowGraph.jsx +19 -17
- package/src/dashboard/app/components/KnowledgeMarkdown.jsx +53 -0
- package/src/dashboard/app/components/Markdown.jsx +47 -0
- package/src/dashboard/app/components/PhaseRail.jsx +111 -0
- package/src/dashboard/app/components/SearchFilter.jsx +40 -22
- package/src/dashboard/app/components/Sidebar.jsx +146 -37
- package/src/dashboard/app/hooks/useMorphQuery.js +54 -0
- package/src/dashboard/app/hooks/usePref.js +38 -0
- package/src/dashboard/app/lib/format.js +69 -0
- package/src/dashboard/app/lib/phases.js +56 -0
- package/src/dashboard/app/lib/project.js +50 -0
- package/src/dashboard/app/pages/Activity.jsx +24 -0
- package/src/dashboard/app/pages/AgentMap.jsx +371 -370
- package/src/dashboard/app/pages/FeatureDetail.jsx +604 -0
- package/src/dashboard/app/pages/Features.jsx +400 -0
- package/src/dashboard/app/pages/Guide.jsx +146 -0
- package/src/dashboard/app/pages/HookInspector.jsx +357 -322
- package/src/dashboard/app/pages/PhasePipeline.jsx +277 -408
- package/src/dashboard/app/pages/Portfolio.jsx +156 -0
- package/src/dashboard/app/pages/Rubrics.jsx +101 -0
- package/src/dashboard/app/pages/RulesMap.jsx +300 -273
- package/src/dashboard/app/pages/SkillsLibrary.jsx +483 -366
- package/src/dashboard/app/pages/Standards.jsx +407 -332
- package/src/dashboard/app/styles/index.css +187 -47
- package/src/dashboard/index.html +14 -12
- package/src/dashboard/package.json +32 -24
- package/src/dashboard/scanner/activity-scanner.js +351 -0
- package/src/dashboard/scanner/agents-scanner.js +59 -1
- package/src/dashboard/scanner/docs-scanner.js +59 -0
- package/src/dashboard/scanner/feature-status.js +10 -0
- package/src/dashboard/scanner/features-scanner.js +360 -0
- package/src/dashboard/scanner/index.js +14 -7
- package/src/dashboard/scanner/phases-scanner.js +14 -15
- package/src/dashboard/scanner/portfolio.js +153 -0
- package/src/dashboard/scanner/project-api.js +195 -0
- package/src/dashboard/scanner/rules-scanner.js +42 -37
- package/src/dashboard/scanner/skills-scanner.js +105 -43
- package/src/dashboard/scanner/standards-scanner.js +2 -0
- package/src/dashboard/vite-plugin-morph-data.js +134 -17
- package/src/lib/detectors/claude-config-detector.js +2 -7
- package/src/lib/detectors/index.js +5 -1
- package/src/lib/evaluator-report.js +22 -0
- package/src/lib/generators/settings-generator.js +208 -209
- package/src/lib/installers/mcp-installer.js +724 -719
- package/src/lib/installers/memory-installer.js +1 -2
- package/src/lib/projects/registry.js +154 -0
- package/src/lib/risk-detector.js +13 -0
- package/src/lib/tasks/tasks-json-parser.js +21 -0
- package/src/lib/validators/code-budget/code-budget-validator.js +32 -4
- package/src/lib/worktree/env-allocator.js +9 -3
- package/src/scripts/install-plugin.js +1 -1
- package/src/scripts/setup-infra.js +2 -4
- package/src/utils/agents-installer.js +108 -13
- package/src/utils/banner.js +1 -1
- package/src/commands/index.js +0 -13
- package/src/commands/project/checkpoint.js +0 -209
- package/src/core/state/phase-state-machine.js +0 -197
- package/src/dashboard/app/hooks/useDetailPanel.js +0 -15
- package/src/dashboard/app/pages/LiveState.jsx +0 -206
- package/src/dashboard/scanner/squads-scanner.js +0 -13
- package/src/dashboard/scanner/state-scanner.js +0 -85
- package/src/lib/generators/index.js +0 -10
- package/src/lib/hooks/hook-executor.js +0 -167
- package/src/lib/validators/checkpoint-persistence.js +0 -39
- package/src/lib/validators/shared/emit-validator-dispatch.js +0 -14
- package/src/lib/validators/shared/index.js +0 -12
- package/src/lib/validators/shared/issue-counter.js +0 -14
- package/src/lib/validators/shared/result-formatter.js +0 -124
|
@@ -3,84 +3,34 @@ using System.Text.Json.Serialization;
|
|
|
3
3
|
|
|
4
4
|
namespace Morph.AiKit.Evals;
|
|
5
5
|
|
|
6
|
-
/// <summary>O veredito de um carimbo de cache — quatro estados, nunca um booleano.</summary>
|
|
7
|
-
/// <remarks>
|
|
8
|
-
/// <c>Missing</c> e <c>PinMismatch</c> sao SEPARADOS de proposito: as duas situacoes pedem
|
|
9
|
-
/// acoes diferentes. A primeira e "regrave"; a segunda e "regrave <b>porque o pacote
|
|
10
|
-
/// mudou</b>". Colapsa-las num unico "carimbo invalido" devolve ao leitor a pergunta que o
|
|
11
|
-
/// carimbo existia para responder.
|
|
12
|
-
/// </remarks>
|
|
13
6
|
public enum EvalStampVerdict
|
|
14
7
|
{
|
|
15
|
-
/// <summary>Carimbo presente e de acordo com o pin corrente.</summary>
|
|
16
8
|
Ok = 0,
|
|
17
9
|
|
|
18
|
-
/// <summary>Nao ha carimbo: cache anterior a esta feature, ou diretorio criado a mao.</summary>
|
|
19
10
|
Missing = 1,
|
|
20
11
|
|
|
21
|
-
/// <summary>Ha um arquivo, mas ele nao e JSON legivel no formato do carimbo.</summary>
|
|
22
12
|
Unreadable = 2,
|
|
23
13
|
|
|
24
|
-
/// <summary>As versoes gravadas divergem do pin corrente — a chave de cache pode ter mudado.</summary>
|
|
25
14
|
PinMismatch = 3,
|
|
26
15
|
|
|
27
|
-
/// <summary>O <c>pinnedAt</c> do pin corrente esta no futuro. Erro de pin, jamais cache fresca.</summary>
|
|
28
16
|
FuturePin = 4,
|
|
29
17
|
}
|
|
30
18
|
|
|
31
|
-
/// <summary>Resultado de <see cref="EvalCacheStamp.Check"/>: veredito mais a frase que o explica.</summary>
|
|
32
|
-
/// <param name="Verdict">O estado apurado.</param>
|
|
33
|
-
/// <param name="Reason">
|
|
34
|
-
/// Frase pronta para o relatorio. Nos dois vereditos que nomeiam versoes, ela cita AS DUAS
|
|
35
|
-
/// (a gravada e a do pin) — dizer so uma obriga o leitor a ir buscar a outra.
|
|
36
|
-
/// </param>
|
|
37
19
|
public sealed record EvalCacheStampCheck(EvalStampVerdict Verdict, string Reason)
|
|
38
20
|
{
|
|
39
|
-
/// <summary>Atalho de leitura: o veredito unico que permite avaliar cenario.</summary>
|
|
40
21
|
public bool IsOk => Verdict == EvalStampVerdict.Ok;
|
|
41
22
|
}
|
|
42
23
|
|
|
43
|
-
|
|
44
|
-
/// O pin, do jeito que o fixture o recebe: data de apuracao mais pacote-para-versao.
|
|
45
|
-
/// </summary>
|
|
46
|
-
/// <remarks>
|
|
47
|
-
/// Achatado (nome para versao) e nao a forma completa de <c>framework/ai-pin.json</c>
|
|
48
|
-
/// porque quem o produz e o CLI, na variavel <c>MORPH_EVAL_PIN</c>, e o kit nao le o pin do
|
|
49
|
-
/// disco: um segundo parser de <c>ai-pin.json</c> e como as duas verdades comecam. Aqui
|
|
50
|
-
/// entram apenas os pacotes que decidem o FORMATO da cache — a familia
|
|
51
|
-
/// <c>Microsoft.Extensions.AI</c> e as tres de Evaluation. Subir o provider da OpenAI nao
|
|
52
|
-
/// invalida uma arvore de cache, e um carimbo que reagisse a isso reprovaria por nada.
|
|
53
|
-
/// </remarks>
|
|
54
|
-
/// <param name="PinnedAt">Data ISO em que um humano conferiu as versoes.</param>
|
|
55
|
-
/// <param name="Packages">Nome do pacote para versao exata.</param>
|
|
24
|
+
// So os pacotes que decidem o formato da cache; o kit nunca le ai-pin.json do disco.
|
|
56
25
|
public sealed record EvalPin(
|
|
57
26
|
[property: JsonPropertyName("pinnedAt")] string PinnedAt,
|
|
58
27
|
[property: JsonPropertyName("packages")] IReadOnlyDictionary<string, string> Packages);
|
|
59
28
|
|
|
60
|
-
|
|
61
|
-
/// O carimbo que fica na raiz da arvore de cache commitada, e a regra que o julga.
|
|
62
|
-
/// </summary>
|
|
63
|
-
/// <remarks>
|
|
64
|
-
/// <para>
|
|
65
|
-
/// <b>Por que existe.</b> A funcao de hash que produz a chave de cache nao promete ser
|
|
66
|
-
/// estavel entre versoes da biblioteca — a propria biblioteca carrega um numero de versao
|
|
67
|
-
/// de cache interno, para invalidar tudo quando o formato muda. Ou seja: SUBIR O PIN PODE
|
|
68
|
-
/// INVALIDAR A ARVORE INTEIRA. Sem carimbo isso chega como N misses inexplicaveis num dia
|
|
69
|
-
/// em que ninguem mexeu em nada. Com carimbo, chega como uma frase que nomeia as duas
|
|
70
|
-
/// versoes e o comando de regravacao.
|
|
71
|
-
/// </para>
|
|
72
|
-
/// <para>
|
|
73
|
-
/// <b>Ele nao substitui a leitura da cache</b> — a chave continua sendo computada pela
|
|
74
|
-
/// biblioteca. Ele responde antes, e de graca, a pergunta cuja resposta cara seria "rodou a
|
|
75
|
-
/// suite inteira para descobrir que a cache nao servia".
|
|
76
|
-
/// </para>
|
|
77
|
-
/// </remarks>
|
|
29
|
+
// A chave de cache da biblioteca nao e estavel entre versoes: subir o pin pode invalidar a arvore inteira.
|
|
78
30
|
public sealed record EvalCacheStamp
|
|
79
31
|
{
|
|
80
|
-
/// <summary>Nome do arquivo, na raiz de <c>cachePath</c>. Uma unica grafia.</summary>
|
|
81
32
|
public const string FileName = ".morph-eval-cache.json";
|
|
82
33
|
|
|
83
|
-
/// <summary>Quem grava. Um carimbo de outra origem e um carimbo que ninguem mantem.</summary>
|
|
84
34
|
public const string Writer = "morph-eval-fixture";
|
|
85
35
|
|
|
86
36
|
private static readonly JsonSerializerOptions Json = new()
|
|
@@ -89,34 +39,27 @@ public sealed record EvalCacheStamp
|
|
|
89
39
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
|
90
40
|
};
|
|
91
41
|
|
|
92
|
-
/// <summary>O <c>pinnedAt</c> vigente quando a arvore foi gravada.</summary>
|
|
93
42
|
[JsonPropertyName("pinnedAt")]
|
|
94
43
|
public required string PinnedAt { get; init; }
|
|
95
44
|
|
|
96
|
-
/// <summary>As versoes de pacote sob as quais a arvore foi gravada.</summary>
|
|
97
45
|
[JsonPropertyName("packages")]
|
|
98
46
|
public required IReadOnlyDictionary<string, string> Packages { get; init; }
|
|
99
47
|
|
|
100
|
-
/// <summary>O <c>ProviderName</c> que entrou na chave.</summary>
|
|
101
48
|
[JsonPropertyName("provider")]
|
|
102
49
|
public required string Provider { get; init; }
|
|
103
50
|
|
|
104
|
-
/// <summary>O <c>DefaultModelId</c> que entrou na chave. Nulo e um valor, nao um erro.</summary>
|
|
105
51
|
[JsonPropertyName("model")]
|
|
106
52
|
public string? Model { get; init; }
|
|
107
53
|
|
|
108
|
-
/// <summary>Quem gravou o carimbo.</summary>
|
|
109
54
|
[JsonPropertyName("writtenBy")]
|
|
110
55
|
public string WrittenBy { get; init; } = Writer;
|
|
111
56
|
|
|
112
|
-
/// <summary>Caminho do arquivo de carimbo dentro de uma raiz de cache.</summary>
|
|
113
57
|
public static string PathFor(string cacheRoot)
|
|
114
58
|
{
|
|
115
59
|
ArgumentException.ThrowIfNullOrWhiteSpace(cacheRoot);
|
|
116
60
|
return Path.Combine(cacheRoot, FileName);
|
|
117
61
|
}
|
|
118
62
|
|
|
119
|
-
/// <summary>Grava o carimbo. Cria a raiz se ela ainda nao existe.</summary>
|
|
120
63
|
public static void Write(string cacheRoot, EvalCacheStamp stamp)
|
|
121
64
|
{
|
|
122
65
|
ArgumentException.ThrowIfNullOrWhiteSpace(cacheRoot);
|
|
@@ -124,17 +67,10 @@ public sealed record EvalCacheStamp
|
|
|
124
67
|
|
|
125
68
|
Directory.CreateDirectory(cacheRoot);
|
|
126
69
|
|
|
127
|
-
// Quebras de linha normalizadas para LF e uma quebra final: o carimbo e um arquivo
|
|
128
|
-
// COMMITADO. Sem isso ele produziria diff que nao e diff — CRLF numa maquina, LF
|
|
129
|
-
// noutra, e nenhuma quebra no fim em lugar nenhum.
|
|
130
70
|
var json = JsonSerializer.Serialize(stamp, Json).ReplaceLineEndings("\n");
|
|
131
71
|
File.WriteAllText(PathFor(cacheRoot), json + "\n");
|
|
132
72
|
}
|
|
133
73
|
|
|
134
|
-
/// <summary>
|
|
135
|
-
/// Le o carimbo. Devolve <c>null</c> quando o arquivo nao existe OU nao parseia —
|
|
136
|
-
/// <see cref="Check"/> e quem distingue os dois casos, porque so ela tem as palavras.
|
|
137
|
-
/// </summary>
|
|
138
74
|
public static EvalCacheStamp? Read(string cacheRoot)
|
|
139
75
|
{
|
|
140
76
|
ArgumentException.ThrowIfNullOrWhiteSpace(cacheRoot);
|
|
@@ -155,24 +91,13 @@ public sealed record EvalCacheStamp
|
|
|
155
91
|
}
|
|
156
92
|
}
|
|
157
93
|
|
|
158
|
-
/// <summary>
|
|
159
|
-
/// O carimbo desta arvore contra o pin corrente.
|
|
160
|
-
/// </summary>
|
|
161
|
-
/// <param name="cacheRoot">Raiz da arvore de cache.</param>
|
|
162
|
-
/// <param name="pin">O pin corrente, como o CLI o entregou.</param>
|
|
163
|
-
/// <param name="timeProvider">
|
|
164
|
-
/// Relogio injetado — a comparacao de <c>pinnedAt</c> com hoje e uma leitura de relogio,
|
|
165
|
-
/// e um teste que dependesse do relogio da maquina mudaria de veredito na virada do dia.
|
|
166
|
-
/// </param>
|
|
167
94
|
public static EvalCacheStampCheck Check(string cacheRoot, EvalPin pin, TimeProvider timeProvider)
|
|
168
95
|
{
|
|
169
96
|
ArgumentException.ThrowIfNullOrWhiteSpace(cacheRoot);
|
|
170
97
|
ArgumentNullException.ThrowIfNull(pin);
|
|
171
98
|
ArgumentNullException.ThrowIfNull(timeProvider);
|
|
172
99
|
|
|
173
|
-
// O pin vem
|
|
174
|
-
// janela medida contra ele, e comparar um carimbo contra uma regua adulterada
|
|
175
|
-
// produziria um "ok" que nao significa nada.
|
|
100
|
+
// O pin vem antes do carimbo: um pin com data no futuro renovaria qualquer janela medida contra ele.
|
|
176
101
|
var today = timeProvider.GetUtcNow().UtcDateTime.ToString("yyyy-MM-dd", System.Globalization.CultureInfo.InvariantCulture);
|
|
177
102
|
if (string.CompareOrdinal(pin.PinnedAt, today) > 0)
|
|
178
103
|
{
|
|
@@ -5,56 +5,14 @@ using Microsoft.Extensions.AI.Evaluation.Reporting.Storage;
|
|
|
5
5
|
|
|
6
6
|
namespace Morph.AiKit.Evals;
|
|
7
7
|
|
|
8
|
-
/// <summary>O que uma execucao de cenario produziu.</summary>
|
|
9
|
-
/// <param name="Scenario">A chave do cenario na cache (<c>{agente}/{tenant}/{nome}</c>).</param>
|
|
10
|
-
/// <param name="Passed">Se TODAS as assercoes deterministicas se sustentaram.</param>
|
|
11
|
-
/// <param name="Outcomes">Um veredito por assercao, na ordem do dataset.</param>
|
|
12
|
-
/// <param name="Result">O resultado completo da biblioteca, com as metricas de juizo junto.</param>
|
|
13
8
|
public sealed record EvalScenarioResult(
|
|
14
9
|
string Scenario,
|
|
15
10
|
bool Passed,
|
|
16
11
|
IReadOnlyList<EvalAssertionOutcome> Outcomes,
|
|
17
12
|
EvaluationResult Result);
|
|
18
13
|
|
|
19
|
-
/// <summary>
|
|
20
|
-
/// Monta a configuracao de eval com cache em disco de TTL explicito, roda os cenarios do
|
|
21
|
-
/// dataset versionado e escreve o sumario que o CLI le.
|
|
22
|
-
/// </summary>
|
|
23
|
-
/// <remarks>
|
|
24
|
-
/// <para>
|
|
25
|
-
/// <b>A montagem inteira existe para uma frase: com a cache quente, o eval roda sem
|
|
26
|
-
/// credencial e sem rede.</b> O caminho e este — a biblioteca embrulha o cliente interno num
|
|
27
|
-
/// cliente de cache; no HIT o interno nunca e invocado; em modo offline o interno e o
|
|
28
|
-
/// <see cref="OfflineChatClient"/>, que recusa. Logo, ou tudo resolve pela cache, ou a suite
|
|
29
|
-
/// fica vermelha nomeando o cenario que faltou. Nunca <c>skip</c>: gate verde sobre
|
|
30
|
-
/// verificacao que nao aconteceu e o modo de falha que este harness mais paga para evitar.
|
|
31
|
-
/// </para>
|
|
32
|
-
/// <para>
|
|
33
|
-
/// <b>Cache e resultados vivem em raizes DIFERENTES</b>, e isso e uma decisao, nao um
|
|
34
|
-
/// detalhe: os resultados carregam latencia, uso de tokens e o flag de cache hit, todos
|
|
35
|
-
/// nao-deterministicos. Numa raiz so, commitar a cache arrastaria os resultados junto e
|
|
36
|
-
/// produziria diff em toda rodada, sem sinal nenhum. Separar por CONSTRUCAO (duas raizes) e
|
|
37
|
-
/// mais forte que separar por <c>.gitignore</c>: uma regra de ignore dentro de um diretorio
|
|
38
|
-
/// commitado e uma regra que a proxima pessoa remove sem entender.
|
|
39
|
-
/// </para>
|
|
40
|
-
/// <para>
|
|
41
|
-
/// <b>Uma <c>ReportingConfiguration</c> por cenario</b>, e nao uma para a suite, porque as
|
|
42
|
-
/// assercoes deterministicas SAO os avaliadores e elas mudam a cada cenario. A alternativa
|
|
43
|
-
/// seria um avaliador com estado mutavel entre cenarios — barato de escrever e caro de
|
|
44
|
-
/// depurar no dia em que dois cenarios rodarem fora de ordem.
|
|
45
|
-
/// </para>
|
|
46
|
-
/// </remarks>
|
|
47
14
|
public sealed class EvalFixture : IAsyncDisposable
|
|
48
15
|
{
|
|
49
|
-
/// <summary>
|
|
50
|
-
/// O nome do cenario em execucao, por indirecao.
|
|
51
|
-
/// </summary>
|
|
52
|
-
/// <remarks>
|
|
53
|
-
/// O <see cref="OfflineChatClient"/> e construido ANTES do fixture (ele e o cliente
|
|
54
|
-
/// interno dele) e precisa nomear, na mensagem de miss, o cenario que esta rodando
|
|
55
|
-
/// AGORA. Um nome capturado na construcao nomearia sempre o primeiro cenario, e a
|
|
56
|
-
/// mensagem mandaria o leitor investigar o cenario errado.
|
|
57
|
-
/// </remarks>
|
|
58
16
|
private sealed class ScenarioCursor
|
|
59
17
|
{
|
|
60
18
|
public string Name { get; set; } = "(nenhum)";
|
|
@@ -87,33 +45,18 @@ public sealed class EvalFixture : IAsyncDisposable
|
|
|
87
45
|
_resultStore = new DiskBasedResultStore(options.ResultsRoot);
|
|
88
46
|
}
|
|
89
47
|
|
|
90
|
-
/// <summary>Nome da execucao no store de resultados. Estavel, porque os resultados nao se commitam.</summary>
|
|
91
48
|
public const string ExecutionName = "morph-eval";
|
|
92
49
|
|
|
93
|
-
/// <summary>A configuracao efetiva desta execucao.</summary>
|
|
94
50
|
public EvalCacheOptions Options { get; }
|
|
95
51
|
|
|
96
|
-
/// <summary>
|
|
97
|
-
/// O veredito do carimbo, apurado UMA vez na construcao — antes de avaliar qualquer
|
|
98
|
-
/// cenario. Nao faz sentido pagar o relogio de uma suite para descobrir no fim que a
|
|
99
|
-
/// cache nao servia.
|
|
100
|
-
/// </summary>
|
|
101
52
|
public EvalCacheStampCheck StampCheck { get; }
|
|
102
53
|
|
|
103
|
-
/// <summary>
|
|
104
|
-
/// O cliente offline, quando o modo e <see cref="EvalMode.Offline"/>. E por ele que se
|
|
105
|
-
/// mede "zero invocacoes do cliente interno" — a prova de que nenhuma chamada paga foi
|
|
106
|
-
/// feita.
|
|
107
|
-
/// </summary>
|
|
108
54
|
public OfflineChatClient? Offline => _offline;
|
|
109
55
|
|
|
110
|
-
/// <summary>Quantas vezes a cache falhou e a chamada desceu ao cliente interno.</summary>
|
|
111
56
|
public int Misses => _offline?.Invocations ?? _counting?.Calls ?? 0;
|
|
112
57
|
|
|
113
|
-
/// <summary>Os resultados acumulados, na ordem em que os cenarios rodaram.</summary>
|
|
114
58
|
public IReadOnlyList<EvalScenarioResult> Results => _results;
|
|
115
59
|
|
|
116
|
-
/// <summary>O sumario do que rodou ate agora.</summary>
|
|
117
60
|
public EvalRunSummary Summary => new()
|
|
118
61
|
{
|
|
119
62
|
Scenarios = _results.Count,
|
|
@@ -128,15 +71,6 @@ public sealed class EvalFixture : IAsyncDisposable
|
|
|
128
71
|
Failures = CollectFailures(),
|
|
129
72
|
};
|
|
130
73
|
|
|
131
|
-
/// <summary>
|
|
132
|
-
/// Uma entrada por assercao REPROVADA, com o cenario que a carregava.
|
|
133
|
-
/// </summary>
|
|
134
|
-
/// <remarks>
|
|
135
|
-
/// Percorre as assercoes reprovadas, e nao os cenarios reprovados: o cenario diz ONDE, a
|
|
136
|
-
/// assercao diz O QUE. Um sumario que so contasse cenarios obrigaria quem le a reabrir o
|
|
137
|
-
/// dataset para descobrir qual regua quebrou — que e exatamente a investigacao que a
|
|
138
|
-
/// mensagem existe para dispensar.
|
|
139
|
-
/// </remarks>
|
|
140
74
|
private IReadOnlyList<EvalFailureDetail> CollectFailures()
|
|
141
75
|
{
|
|
142
76
|
var failures = new List<EvalFailureDetail>();
|
|
@@ -145,10 +79,6 @@ public sealed class EvalFixture : IAsyncDisposable
|
|
|
145
79
|
{
|
|
146
80
|
var reproved = result.Outcomes.Where(o => !o.Passed).ToList();
|
|
147
81
|
|
|
148
|
-
// O cenario SEM assercao nenhuma reprova por vacuidade (ele nao cobra nada), e
|
|
149
|
-
// nesse caso nao existe outcome reprovado para nomear. Sem esta linha ele seria
|
|
150
|
-
// contado em `Failed` e invisivel em `Failures` — um numero sem frase, que e a
|
|
151
|
-
// metade exata do defeito que este tipo existe para fechar.
|
|
152
82
|
if (reproved.Count == 0)
|
|
153
83
|
{
|
|
154
84
|
failures.Add(new EvalFailureDetail(
|
|
@@ -169,20 +99,6 @@ public sealed class EvalFixture : IAsyncDisposable
|
|
|
169
99
|
return failures;
|
|
170
100
|
}
|
|
171
101
|
|
|
172
|
-
/// <summary>
|
|
173
|
-
/// Constroi o fixture a partir das variaveis que o CLI DITA.
|
|
174
|
-
/// </summary>
|
|
175
|
-
/// <remarks>
|
|
176
|
-
/// Fora do CLI um projeto pode montar <see cref="EvalCacheOptions"/> a mao e chamar
|
|
177
|
-
/// <see cref="Create"/>; as variaveis existem para que a MESMA suite seja executavel pelo
|
|
178
|
-
/// <c>verify</c> sem um segundo caminho de configuracao.
|
|
179
|
-
/// </remarks>
|
|
180
|
-
/// <param name="liveClient">
|
|
181
|
-
/// O cliente real. Obrigatorio em <see cref="EvalMode.Refresh"/> e
|
|
182
|
-
/// <see cref="EvalMode.Online"/>; ignorado em <see cref="EvalMode.Offline"/>.
|
|
183
|
-
/// </param>
|
|
184
|
-
/// <param name="qualityEvaluators">Avaliadores de juizo. Reportados, nunca decisivos.</param>
|
|
185
|
-
/// <param name="timeProvider">Relogio, para a checagem de pin no futuro.</param>
|
|
186
102
|
public static EvalFixture FromEnvironment(
|
|
187
103
|
IChatClient? liveClient = null,
|
|
188
104
|
IEnumerable<IEvaluator>? qualityEvaluators = null,
|
|
@@ -198,8 +114,6 @@ public sealed class EvalFixture : IAsyncDisposable
|
|
|
198
114
|
DatasetRoot = Environment.GetEnvironmentVariable("MORPH_EVAL_DATASET") ?? Path.Combine(beside, "eval-datasets"),
|
|
199
115
|
ResultsRoot = Environment.GetEnvironmentVariable("MORPH_EVAL_RESULTS") ?? Path.Combine(beside, "eval-results"),
|
|
200
116
|
Mode = ParseMode(Environment.GetEnvironmentVariable("MORPH_EVAL_MODE")),
|
|
201
|
-
// Explicito e longo: o default da biblioteca (14 dias) apagaria a arvore
|
|
202
|
-
// commitada durante uma leitura. Ver EvalCacheOptions.
|
|
203
117
|
TimeToLive = TimeSpan.FromDays(3650),
|
|
204
118
|
ProviderName = Environment.GetEnvironmentVariable("MORPH_EVAL_PROVIDER") ?? "morph-eval",
|
|
205
119
|
DefaultModelId = Environment.GetEnvironmentVariable("MORPH_EVAL_MODEL"),
|
|
@@ -211,9 +125,6 @@ public sealed class EvalFixture : IAsyncDisposable
|
|
|
211
125
|
return Create(options, liveClient, qualityEvaluators, timeProvider);
|
|
212
126
|
}
|
|
213
127
|
|
|
214
|
-
/// <summary>Constroi o fixture a partir de opcoes explicitas.</summary>
|
|
215
|
-
/// <exception cref="ArgumentOutOfRangeException">TTL abaixo do piso — ver <see cref="EvalCacheOptions.MinimumTimeToLive"/>.</exception>
|
|
216
|
-
/// <exception cref="InvalidOperationException">Carimbo ausente ou divergente em modo de leitura; ou modo de escrita sem cliente real.</exception>
|
|
217
128
|
public static EvalFixture Create(
|
|
218
129
|
EvalCacheOptions options,
|
|
219
130
|
IChatClient? liveClient = null,
|
|
@@ -237,8 +148,7 @@ public sealed class EvalFixture : IAsyncDisposable
|
|
|
237
148
|
var clock = timeProvider ?? TimeProvider.System;
|
|
238
149
|
IReadOnlyList<IEvaluator> quality = qualityEvaluators is null ? [] : [.. qualityEvaluators];
|
|
239
150
|
|
|
240
|
-
//
|
|
241
|
-
// (re)escrita — cobrar carimbo ali tornaria impossivel criar a primeira cache.
|
|
151
|
+
// Em Refresh a arvore esta sendo reescrita: cobrar carimbo impediria gravar a primeira cache.
|
|
242
152
|
var stampCheck = options.Mode == EvalMode.Refresh || options.Pin is null
|
|
243
153
|
? new EvalCacheStampCheck(
|
|
244
154
|
EvalStampVerdict.Ok,
|
|
@@ -278,10 +188,6 @@ public sealed class EvalFixture : IAsyncDisposable
|
|
|
278
188
|
return new EvalFixture(options, stampCheck, cursor, inner, offline, counting, quality);
|
|
279
189
|
}
|
|
280
190
|
|
|
281
|
-
/// <summary>
|
|
282
|
-
/// Roda TODOS os cenarios do dataset e escreve o sumario, quando ha caminho para ele.
|
|
283
|
-
/// </summary>
|
|
284
|
-
/// <exception cref="InvalidOperationException">Dataset sem nenhum cenario — uma suite de eval que avalia zero cenarios e sai verde e um guardrail inerte.</exception>
|
|
285
191
|
public async Task<EvalRunSummary> RunAllAsync(CancellationToken cancellationToken = default)
|
|
286
192
|
{
|
|
287
193
|
var scenarios = EvalScenario.LoadAll(Options.DatasetRoot);
|
|
@@ -293,9 +199,7 @@ public sealed class EvalFixture : IAsyncDisposable
|
|
|
293
199
|
+ "que este kit existe para nao produzir.");
|
|
294
200
|
}
|
|
295
201
|
|
|
296
|
-
//
|
|
297
|
-
// deixaria entradas de uma versao antiga convivendo com entradas novas, e a proxima
|
|
298
|
-
// leitura offline nao teria como saber quais sao quais.
|
|
202
|
+
// Zera antes de regravar: entradas de versoes diferentes nao podem conviver na arvore.
|
|
299
203
|
if (Options.Mode == EvalMode.Refresh)
|
|
300
204
|
{
|
|
301
205
|
try
|
|
@@ -305,12 +209,7 @@ public sealed class EvalFixture : IAsyncDisposable
|
|
|
305
209
|
}
|
|
306
210
|
catch (DirectoryNotFoundException)
|
|
307
211
|
{
|
|
308
|
-
//
|
|
309
|
-
// cache e LANCA quando ele ainda nao existe — o caso da PRIMEIRA gravacao.
|
|
310
|
-
// "Nao ha o que zerar" e sucesso, nao falha. Capturado aqui, e nao evitado
|
|
311
|
-
// com um Directory.Exists sobre o layout interno da biblioteca: repetir o
|
|
312
|
-
// nome do subdiretorio dela aqui seria uma segunda verdade sobre onde a
|
|
313
|
-
// cache mora.
|
|
212
|
+
// Raiz ausente na primeira gravacao: nada a zerar.
|
|
314
213
|
}
|
|
315
214
|
}
|
|
316
215
|
|
|
@@ -339,7 +238,6 @@ public sealed class EvalFixture : IAsyncDisposable
|
|
|
339
238
|
return summary;
|
|
340
239
|
}
|
|
341
240
|
|
|
342
|
-
/// <summary>Roda UM cenario: resolve a resposta (cache ou cliente interno) e aplica os avaliadores.</summary>
|
|
343
241
|
public async Task<EvalScenarioResult> RunAsync(EvalScenario scenario, CancellationToken cancellationToken = default)
|
|
344
242
|
{
|
|
345
243
|
ArgumentNullException.ThrowIfNull(scenario);
|
|
@@ -378,11 +276,9 @@ public sealed class EvalFixture : IAsyncDisposable
|
|
|
378
276
|
outcomes.Add(assertion.Evaluate(response));
|
|
379
277
|
}
|
|
380
278
|
|
|
381
|
-
// Um cenario SEM assercao deterministica nao passa: ele nao cobra nada. Deixa-lo
|
|
382
|
-
// verde por vacuidade e o mesmo defeito de um teste vazio — e aqui ele seria pior,
|
|
383
|
-
// porque o cenario apareceria na contagem como se tivesse verificado algo.
|
|
384
279
|
var scenarioResult = new EvalScenarioResult(
|
|
385
280
|
scenario.CacheKey,
|
|
281
|
+
// Sem assercao o cenario reprova: verde por vacuidade contaria como verificado.
|
|
386
282
|
outcomes.Count > 0 && outcomes.All(o => o.Passed),
|
|
387
283
|
outcomes,
|
|
388
284
|
result);
|
|
@@ -391,7 +287,6 @@ public sealed class EvalFixture : IAsyncDisposable
|
|
|
391
287
|
return scenarioResult;
|
|
392
288
|
}
|
|
393
289
|
|
|
394
|
-
/// <inheritdoc />
|
|
395
290
|
public ValueTask DisposeAsync()
|
|
396
291
|
{
|
|
397
292
|
_inner.Dispose();
|
|
@@ -1,32 +1,10 @@
|
|
|
1
1
|
namespace Morph.AiKit.Evals;
|
|
2
2
|
|
|
3
|
-
/// <summary>
|
|
4
|
-
/// De onde a resposta do modelo pode vir nesta execucao de eval.
|
|
5
|
-
/// </summary>
|
|
6
|
-
/// <remarks>
|
|
7
|
-
/// A distincao existe porque as tres situacoes tem custos diferentes em DINHEIRO e em
|
|
8
|
-
/// confianca, e um enum de dois valores ("usa cache" / "nao usa") esconderia a do meio.
|
|
9
|
-
/// </remarks>
|
|
10
3
|
public enum EvalMode
|
|
11
4
|
{
|
|
12
|
-
/// <summary>
|
|
13
|
-
/// So a cache. Um miss e falha nomeada, nunca uma chamada de rede — e nunca um
|
|
14
|
-
/// <c>skip</c>: gate verde sobre verificacao que nao aconteceu e o modo de falha que
|
|
15
|
-
/// este harness mais paga para evitar. E o modo do CI.
|
|
16
|
-
/// </summary>
|
|
17
5
|
Offline = 0,
|
|
18
6
|
|
|
19
|
-
/// <summary>
|
|
20
|
-
/// Reexecuta TODOS os cenarios contra o provider e regrava a arvore de cache. Exige
|
|
21
|
-
/// credencial e nunca acontece implicitamente: uma regravacao automatica e uma chamada
|
|
22
|
-
/// paga que ninguem pediu.
|
|
23
|
-
/// </summary>
|
|
24
7
|
Refresh = 1,
|
|
25
8
|
|
|
26
|
-
/// <summary>
|
|
27
|
-
/// Cache primeiro, provider no miss. E o modo de quem esta escrevendo cenario novo na
|
|
28
|
-
/// propria maquina: paga so pelo que falta, e o resultado sai marcado como
|
|
29
|
-
/// <c>cold</c> com aviso de que a arvore mudou e precisa ser commitada.
|
|
30
|
-
/// </summary>
|
|
31
9
|
Online = 2,
|
|
32
10
|
}
|
|
@@ -3,44 +3,9 @@ using Microsoft.Extensions.AI.Evaluation.Quality;
|
|
|
3
3
|
|
|
4
4
|
namespace Morph.AiKit.Evals;
|
|
5
5
|
|
|
6
|
-
/// <summary>
|
|
7
|
-
/// Os avaliadores de JUIZO que o kit considera utilizaveis, e a fronteira que os separa dos
|
|
8
|
-
/// experimentais.
|
|
9
|
-
/// </summary>
|
|
10
|
-
/// <remarks>
|
|
11
|
-
/// <para>
|
|
12
|
-
/// <b>Eles nunca decidem.</b> O veredito de um cenario sai das <see cref="EvalAssertion"/>
|
|
13
|
-
/// deterministicas; o que sai daqui e METRICA REPORTADA. Um juizo de LLM que reprovasse
|
|
14
|
-
/// sozinho reintroduziria a variancia que faz suites de eval serem desligadas do CI — e um
|
|
15
|
-
/// no de eval desligado nao verifica nada.
|
|
16
|
-
/// </para>
|
|
17
|
-
/// <para>
|
|
18
|
-
/// <b>Por que a lista e curta.</b> Dois dos quatro avaliadores que o plano desta feature
|
|
19
|
-
/// listava como estaveis carregam <c>[Experimental("AIEVAL001")]</c> na versao pinada:
|
|
20
|
-
/// <c>TaskAdherenceEvaluator</c> e <c>ToolCallAccuracyEvaluator</c>. Sob
|
|
21
|
-
/// <c>TreatWarningsAsErrors</c>, usa-los exige suprimir um diagnostico — uma decisao
|
|
22
|
-
/// consciente de quem adota o kit, jamais um efeito colateral de chamar um metodo chamado
|
|
23
|
-
/// "Stable". Medido por reflexao sobre <c>Microsoft.Extensions.AI.Evaluation.Quality</c>
|
|
24
|
-
/// 10.9.0, e pinado pelo teste <c>WhichQualityEvaluatorsAreExperimentalIsMEASURED</c>.
|
|
25
|
-
/// </para>
|
|
26
|
-
/// <para>
|
|
27
|
-
/// <b>E por que este tipo existe.</b> Sem uma unica referencia em <c>src/</c>, o pacote
|
|
28
|
-
/// <c>.Quality</c> teria apenas contrato de VERSAO (o restore, NU1102) e um rename de API
|
|
29
|
-
/// nele nao reprovaria PR nenhuma. Com o <c>using</c> real acima, ele passa a ter cobertura
|
|
30
|
-
/// de compilacao — que e o nivel de garantia que o kit promete para a familia de Evaluation.
|
|
31
|
-
/// </para>
|
|
32
|
-
/// </remarks>
|
|
33
6
|
public static class EvalQualityEvaluators
|
|
34
7
|
{
|
|
35
|
-
|
|
36
|
-
/// Os avaliadores de qualidade NAO experimentais da versao pinada, prontos para
|
|
37
|
-
/// <see cref="EvalFixture.Create"/>.
|
|
38
|
-
/// </summary>
|
|
39
|
-
/// <remarks>
|
|
40
|
-
/// Uma lista nova a cada chamada: os avaliadores sao passados adiante e guardados por uma
|
|
41
|
-
/// <c>ReportingConfiguration</c>, e um array compartilhado seria estado global disfarcado
|
|
42
|
-
/// de conveniencia.
|
|
43
|
-
/// </remarks>
|
|
8
|
+
// TaskAdherence e ToolCallAccuracy ficam fora: sao [Experimental("AIEVAL001")] no Quality 10.9.0.
|
|
44
9
|
public static IReadOnlyList<IEvaluator> Stable() =>
|
|
45
10
|
[
|
|
46
11
|
new RelevanceEvaluator(),
|
|
@@ -4,73 +4,21 @@ using Microsoft.Extensions.AI;
|
|
|
4
4
|
|
|
5
5
|
namespace Morph.AiKit.Evals;
|
|
6
6
|
|
|
7
|
-
/// <summary>
|
|
8
|
-
/// Um cenario de eval: as mensagens que vao ao modelo e as propriedades que a resposta tem
|
|
9
|
-
/// de ter.
|
|
10
|
-
/// </summary>
|
|
11
|
-
/// <remarks>
|
|
12
|
-
/// <para>
|
|
13
|
-
/// <b>O dataset e versionado e vive em disco</b>, organizado como
|
|
14
|
-
/// <c>{agente}/{tenant}/{cenario}.json</c>. Os dois primeiros segmentos existem porque a
|
|
15
|
-
/// pergunta util quase nunca e "o produto regrediu?", e sim "o agente X regrediu para o
|
|
16
|
-
/// tenant Y?" — e um dataset plano nao consegue responder isso sem convencao de nome.
|
|
17
|
-
/// </para>
|
|
18
|
-
/// <para>
|
|
19
|
-
/// <b>Dado real de tenant e proibido aqui.</b> A cache guarda mensagens INTEIRAS e e
|
|
20
|
-
/// commitada: um segredo que entre no dataset entra no git. O fixture so le cenario deste
|
|
21
|
-
/// diretorio versionado — nunca de banco, nunca de variavel de ambiente.
|
|
22
|
-
/// </para>
|
|
23
|
-
/// </remarks>
|
|
24
7
|
public sealed record EvalScenario
|
|
25
8
|
{
|
|
26
|
-
/// <summary>Primeiro segmento do caminho no dataset.</summary>
|
|
27
9
|
public required string Agent { get; init; }
|
|
28
10
|
|
|
29
|
-
/// <summary>Segundo segmento do caminho no dataset.</summary>
|
|
30
11
|
public required string Tenant { get; init; }
|
|
31
12
|
|
|
32
|
-
/// <summary>
|
|
33
|
-
/// Nome do arquivo, sem extensao. Vira o <c>scenarioName</c> da cache, e por isso e o
|
|
34
|
-
/// que aparece na mensagem de miss.
|
|
35
|
-
/// </summary>
|
|
36
13
|
public required string Name { get; init; }
|
|
37
14
|
|
|
38
|
-
/// <summary>As mensagens enviadas ao modelo, na ordem.</summary>
|
|
39
15
|
public required IReadOnlyList<ChatMessage> Messages { get; init; }
|
|
40
16
|
|
|
41
|
-
/// <summary>As assercoes deterministicas que decidem pass/fail deste cenario.</summary>
|
|
42
17
|
public IReadOnlyList<EvalAssertion> Assertions { get; init; } = [];
|
|
43
18
|
|
|
44
|
-
|
|
45
|
-
/// Identidade do cenario dentro da cache: <c>{agente}.{tenant}.{nome}</c>.
|
|
46
|
-
/// </summary>
|
|
47
|
-
/// <remarks>
|
|
48
|
-
/// <para>
|
|
49
|
-
/// Qualificado, e nao so <see cref="Name"/>, porque dois tenants tem cenarios com o
|
|
50
|
-
/// mesmo nome de proposito ("saudacao", "handoff") — e uma chave de cache que os
|
|
51
|
-
/// confundisse devolveria a resposta de um tenant para o outro.
|
|
52
|
-
/// </para>
|
|
53
|
-
/// <para>
|
|
54
|
-
/// <b>O separador e ponto, e nao barra, por uma MEDICAO.</b> A primeira versao usava
|
|
55
|
-
/// barra, na suposicao de que a biblioteca aninharia o nome do cenario em diretorios. Ela
|
|
56
|
-
/// nao aninha: <c>PathValidation.ValidatePathSegment</c> recusa o nome com
|
|
57
|
-
/// <c>ArgumentException: 'scenarioName' contains invalid path characters or directory
|
|
58
|
-
/// traversal sequences</c> — o nome do cenario e UM segmento de caminho, sempre. Medido
|
|
59
|
-
/// na v10.9.0 contra o SDK .NET 10.
|
|
60
|
-
/// </para>
|
|
61
|
-
/// </remarks>
|
|
19
|
+
// Ponto, nao barra: a biblioteca exige que o nome do cenario seja UM segmento de caminho (10.9.0).
|
|
62
20
|
public string CacheKey => $"{Agent}.{Tenant}.{Name}";
|
|
63
21
|
|
|
64
|
-
/// <summary>
|
|
65
|
-
/// Le TODOS os cenarios de um dataset, em ordem estavel (ordinal por caminho).
|
|
66
|
-
/// </summary>
|
|
67
|
-
/// <remarks>
|
|
68
|
-
/// Ordem estavel importa mais do que parece: e ela que faz duas maquinas gravarem a
|
|
69
|
-
/// mesma arvore de cache e produzirem o mesmo diff. Uma enumeracao dependente do
|
|
70
|
-
/// sistema de arquivos e um diff que muda sozinho.
|
|
71
|
-
/// </remarks>
|
|
72
|
-
/// <param name="datasetRoot">Raiz do dataset versionado.</param>
|
|
73
|
-
/// <exception cref="DirectoryNotFoundException">Quando a raiz nao existe — declarar um dataset ausente e erro de config, nao um dataset vazio.</exception>
|
|
74
22
|
public static IReadOnlyList<EvalScenario> LoadAll(string datasetRoot)
|
|
75
23
|
{
|
|
76
24
|
ArgumentException.ThrowIfNullOrWhiteSpace(datasetRoot);
|
|
@@ -85,6 +33,7 @@ public sealed record EvalScenario
|
|
|
85
33
|
|
|
86
34
|
var files = Directory
|
|
87
35
|
.EnumerateFiles(datasetRoot, "*.json", SearchOption.AllDirectories)
|
|
36
|
+
// Ordem ordinal: duas maquinas precisam gravar a mesma arvore de cache.
|
|
88
37
|
.OrderBy(p => p.Replace('\\', '/'), StringComparer.Ordinal)
|
|
89
38
|
.ToList();
|
|
90
39
|
|
|
@@ -97,7 +46,6 @@ public sealed record EvalScenario
|
|
|
97
46
|
return scenarios;
|
|
98
47
|
}
|
|
99
48
|
|
|
100
|
-
/// <summary>Le um cenario, derivando agente e tenant do caminho relativo a raiz.</summary>
|
|
101
49
|
public static EvalScenario Load(string datasetRoot, string file)
|
|
102
50
|
{
|
|
103
51
|
ArgumentException.ThrowIfNullOrWhiteSpace(datasetRoot);
|
|
@@ -126,9 +74,6 @@ public sealed record EvalScenario
|
|
|
126
74
|
for (var i = 0; i < (dto.Assertions?.Count ?? 0); i++)
|
|
127
75
|
{
|
|
128
76
|
var raw = dto.Assertions![i];
|
|
129
|
-
// Nome default derivado do indice, e unicidade COBRADA: a biblioteca indexa
|
|
130
|
-
// metricas por nome, e um nome repetido faria a segunda assercao sobrescrever a
|
|
131
|
-
// primeira — um cenario que parece cobrar duas propriedades cobrando uma.
|
|
132
77
|
var name = string.IsNullOrWhiteSpace(raw.Name) ? $"{raw.Kind}-{i + 1}" : raw.Name!;
|
|
133
78
|
if (!used.Add(name))
|
|
134
79
|
{
|