@polymorphism-tech/morph-spec 8.38.0 → 8.40.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 +19 -8
- 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 +3 -2
- package/framework/MORPH.md +1 -1
- package/framework/agents.json +36 -25
- package/framework/docs/CLI.md +2 -2
- package/framework/evals/rubrics/code-quality.md +4 -4
- package/framework/evals/rubrics/condensed.md +2 -2
- package/framework/mcp/registry.json +33 -2
- 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 +5 -1
- 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 +1 -1
- package/framework/skills/core/morph-implement/SKILL.md +6 -1
- package/framework/skills/core/morph-media/SKILL.md +1 -1
- package/framework/skills/core/morph-plan/SKILL.md +4 -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 +6 -5
- package/framework/standards/STANDARDS.json +3 -3
- 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/integrations/neon-auth/neon-auth.md +63 -50
- package/framework/standards/frontend/design-system/ai-image-generation.md +5 -5
- package/framework/standards/integration/mcp/mcp-tools.md +48 -117
- 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 +3 -5
- 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/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/validators/code-budget/code-budget-validator.js +32 -4
- package/src/scripts/install-plugin.js +1 -1
- package/src/scripts/setup-infra.js +2 -4
- package/src/utils/agents-installer.js +57 -12
- 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
|
@@ -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
|
{
|
|
@@ -3,128 +3,55 @@ using System.Text.Json.Serialization;
|
|
|
3
3
|
|
|
4
4
|
namespace Morph.AiKit.Evals;
|
|
5
5
|
|
|
6
|
-
/// <summary>Estado da arvore de cache nesta execucao, como o sumario o reporta.</summary>
|
|
7
6
|
public static class EvalCacheState
|
|
8
7
|
{
|
|
9
|
-
/// <summary>Todos os cenarios resolveram pela cache. Nenhuma chamada ao provider.</summary>
|
|
10
8
|
public const string Warm = "warm";
|
|
11
9
|
|
|
12
|
-
/// <summary>Ao menos um cenario foi ao provider — a arvore mudou e precisa ser commitada.</summary>
|
|
13
10
|
public const string Cold = "cold";
|
|
14
11
|
}
|
|
15
12
|
|
|
16
|
-
/// <summary>Uma falha de eval, com nome — cenario, assercao e o motivo literal.</summary>
|
|
17
|
-
/// <remarks>
|
|
18
|
-
/// <para>
|
|
19
|
-
/// <b>Por que o sumario carrega isto, e nao so contadores.</b> Medido: degradar a resposta em
|
|
20
|
-
/// cache deixava a suite vermelha (correto) com a mensagem <c>Expected 0, Actual 1</c> —
|
|
21
|
-
/// verdadeira e inutil. Nada a jusante conseguia dizer QUAL cenario caiu nem por que, porque a
|
|
22
|
-
/// informacao morria dentro do fixture.
|
|
23
|
-
/// </para>
|
|
24
|
-
/// <para>
|
|
25
|
-
/// A rubrica <c>eval-de-modelo.md</c> exige, na banda 9-10, "derrubando o cenario e
|
|
26
|
-
/// NOMEANDO-O", e da como exemplo <c>eval-fail: triagem.acme.a-saudacao</c>. Sem este tipo a
|
|
27
|
-
/// propria rubrica seria inexequivel — o que e pior do que nao ter rubrica, porque parece
|
|
28
|
-
/// coberto.
|
|
29
|
-
/// </para>
|
|
30
|
-
/// </remarks>
|
|
31
|
-
/// <param name="Scenario">A chave do cenario (<c>{agente}.{tenant}.{nome}</c>).</param>
|
|
32
|
-
/// <param name="Assertion">O nome da assercao que reprovou.</param>
|
|
33
|
-
/// <param name="Kind">A regua usada, para distinguir duas assercoes homonimas de tipos distintos.</param>
|
|
34
|
-
/// <param name="Reason">O motivo literal produzido pela assercao.</param>
|
|
35
13
|
public sealed record EvalFailureDetail(
|
|
36
14
|
[property: JsonPropertyName("scenario")] string Scenario,
|
|
37
15
|
[property: JsonPropertyName("assertion")] string Assertion,
|
|
38
16
|
[property: JsonPropertyName("kind")] string Kind,
|
|
39
17
|
[property: JsonPropertyName("reason")] string Reason)
|
|
40
18
|
{
|
|
41
|
-
/// <summary>Uma linha legivel, no formato que a rubrica cita.</summary>
|
|
42
19
|
public override string ToString()
|
|
43
20
|
=> $"eval-fail: {Scenario} — {Assertion} ({Kind}): {Reason}";
|
|
44
21
|
}
|
|
45
22
|
|
|
46
|
-
/// <summary>
|
|
47
|
-
/// O sumario que o fixture escreve no fim da suite, e que o CLI le.
|
|
48
|
-
/// </summary>
|
|
49
|
-
/// <remarks>
|
|
50
|
-
/// <para>
|
|
51
|
-
/// <b>Por que ARQUIVO e nao uma linha marcada no stdout.</b> O runner de teste captura a
|
|
52
|
-
/// saida de console por teste, e o que chega ao stdout do processo depende da configuracao
|
|
53
|
-
/// do runner e do logger. Um contrato que quebra quando alguem liga um logger e um contrato
|
|
54
|
-
/// ruim — e a falha seria SILENCIOSA (sumario ausente, contagem desconhecida, no verde pelo
|
|
55
|
-
/// codigo de saida). O arquivo tem outra virtude: quem escolhe o caminho e o CLI, entao dois
|
|
56
|
-
/// nos de eval no mesmo <c>verify</c> nao colidem.
|
|
57
|
-
/// </para>
|
|
58
|
-
/// <para>
|
|
59
|
-
/// <b>Do outro lado, o leitor trata sumario ausente ou ilegivel como DESCONHECIDO, jamais
|
|
60
|
-
/// como zero.</b> Um sumario que nao parseia nao pode fabricar <c>scenarios: 0</c> e
|
|
61
|
-
/// transformar uma baseline em queda inventada.
|
|
62
|
-
/// </para>
|
|
63
|
-
/// </remarks>
|
|
64
23
|
public sealed record EvalRunSummary
|
|
65
24
|
{
|
|
66
|
-
/// <summary>Quantos cenarios foram avaliados.</summary>
|
|
67
25
|
[JsonPropertyName("scenarios")]
|
|
68
26
|
public required int Scenarios { get; init; }
|
|
69
27
|
|
|
70
|
-
/// <summary>Quantos passaram em TODAS as suas assercoes deterministicas.</summary>
|
|
71
28
|
[JsonPropertyName("passed")]
|
|
72
29
|
public required int Passed { get; init; }
|
|
73
30
|
|
|
74
|
-
/// <summary>Quantos falharam em ao menos uma assercao deterministica.</summary>
|
|
75
31
|
[JsonPropertyName("failed")]
|
|
76
32
|
public required int Failed { get; init; }
|
|
77
33
|
|
|
78
|
-
/// <summary><see cref="EvalCacheState.Warm"/> ou <see cref="EvalCacheState.Cold"/>.</summary>
|
|
79
34
|
[JsonPropertyName("cache")]
|
|
80
35
|
public required string Cache { get; init; }
|
|
81
36
|
|
|
82
|
-
/// <summary>Veredito do carimbo, em minusculas (<c>ok</c>, <c>missing</c>, ...).</summary>
|
|
83
37
|
[JsonPropertyName("stamp")]
|
|
84
38
|
public required string Stamp { get; init; }
|
|
85
39
|
|
|
86
|
-
/// <summary>
|
|
87
|
-
/// Quantas vezes a cache falhou e a chamada desceu ao cliente interno. Em modo offline
|
|
88
|
-
/// com cache quente e ZERO — e esse zero e a prova de que nenhuma chamada paga foi feita.
|
|
89
|
-
/// </summary>
|
|
90
40
|
[JsonPropertyName("misses")]
|
|
91
41
|
public required int Misses { get; init; }
|
|
92
42
|
|
|
93
|
-
/// <summary>O <c>ProviderName</c> usado, o mesmo que entrou na chave.</summary>
|
|
94
43
|
[JsonPropertyName("provider")]
|
|
95
44
|
public required string Provider { get; init; }
|
|
96
45
|
|
|
97
|
-
/// <summary>O <c>DefaultModelId</c> usado, o mesmo que entrou na chave.</summary>
|
|
98
46
|
[JsonPropertyName("model")]
|
|
99
47
|
public string? Model { get; init; }
|
|
100
48
|
|
|
101
|
-
/// <summary>
|
|
102
|
-
/// Metricas de JUIZO (avaliadores de qualidade), nome para valor. Reportadas, nunca
|
|
103
|
-
/// decisivas: com a cache quente elas sao deterministicas, mas a variancia volta na
|
|
104
|
-
/// regravacao, e um gate que dependesse disso nasceria destinado a ser desligado.
|
|
105
|
-
/// </summary>
|
|
106
49
|
[JsonPropertyName("quality")]
|
|
107
50
|
public IReadOnlyDictionary<string, double> Quality { get; init; } = new Dictionary<string, double>(StringComparer.Ordinal);
|
|
108
51
|
|
|
109
|
-
/// <summary>
|
|
110
|
-
/// As falhas, uma por assercao reprovada, com nome. Vazia num run verde.
|
|
111
|
-
/// </summary>
|
|
112
|
-
/// <remarks>
|
|
113
|
-
/// Lista, e nao um booleano ou um contador, porque um cenario pode quebrar em mais de uma
|
|
114
|
-
/// assercao e as duas informam coisas diferentes: uma resposta vazia derruba
|
|
115
|
-
/// <c>Contains</c> E <c>JsonHasKey</c>, e saber disso e a diferenca entre "o prompt mudou"
|
|
116
|
-
/// e "o modelo parou de responder".
|
|
117
|
-
/// </remarks>
|
|
118
52
|
[JsonPropertyName("failures")]
|
|
119
53
|
public IReadOnlyList<EvalFailureDetail> Failures { get; init; } = [];
|
|
120
54
|
|
|
121
|
-
/// <summary>
|
|
122
|
-
/// As falhas em texto, uma por linha — a mensagem que um teste ou o CLI mostra.
|
|
123
|
-
/// </summary>
|
|
124
|
-
/// <remarks>
|
|
125
|
-
/// Devolve frase propria quando nao ha falha alguma, em vez de string vazia: uma mensagem
|
|
126
|
-
/// de assercao vazia e indistinguivel de uma mensagem que ninguem preencheu.
|
|
127
|
-
/// </remarks>
|
|
128
55
|
public string DescribeFailures()
|
|
129
56
|
=> Failures.Count == 0
|
|
130
57
|
? "nenhuma falha de eval registrada"
|
|
@@ -132,7 +59,6 @@ public sealed record EvalRunSummary
|
|
|
132
59
|
|
|
133
60
|
private static readonly JsonSerializerOptions Json = new() { WriteIndented = true };
|
|
134
61
|
|
|
135
|
-
/// <summary>Grava o sumario. Cria o diretorio se preciso; sobrescreve se ja existe.</summary>
|
|
136
62
|
public void WriteTo(string path)
|
|
137
63
|
{
|
|
138
64
|
ArgumentException.ThrowIfNullOrWhiteSpace(path);
|
|
@@ -146,7 +72,6 @@ public sealed record EvalRunSummary
|
|
|
146
72
|
File.WriteAllText(path, JsonSerializer.Serialize(this, Json));
|
|
147
73
|
}
|
|
148
74
|
|
|
149
|
-
/// <summary>Le um sumario gravado. Devolve nulo para arquivo ausente ou ilegivel — DESCONHECIDO, jamais zero.</summary>
|
|
150
75
|
public static EvalRunSummary? ReadFrom(string path)
|
|
151
76
|
{
|
|
152
77
|
ArgumentException.ThrowIfNullOrWhiteSpace(path);
|