@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,54 +3,15 @@ using Microsoft.Extensions.AI;
|
|
|
3
3
|
|
|
4
4
|
namespace Morph.AiKit.Conversation;
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
/// O <see cref="ChatHistoryProvider"/> da casa: o historico de conversa vive no acervo
|
|
8
|
-
/// <b>do projeto</b> (<see cref="IConversationStore"/>), nao dentro da
|
|
9
|
-
/// <see cref="AgentSession"/>.
|
|
10
|
-
/// </summary>
|
|
11
|
-
/// <remarks>
|
|
12
|
-
/// <para>
|
|
13
|
-
/// <b>A decisao que este arquivo torna compilavel.</b> O caminho embutido do MAF
|
|
14
|
-
/// (<c>InMemoryChatHistoryProvider</c>) guarda a conversa no estado da sessao, o que resolve
|
|
15
|
-
/// um chat de demonstracao e nao resolve um CRM multi-tenant: a conversa precisa sobreviver
|
|
16
|
-
/// ao restart, ser lida por relatorio, ser filtrada por tenant e ser apagada a pedido do
|
|
17
|
-
/// titular. Aqui a <see cref="AgentSession"/> guarda apenas <b>a chave</b> da conversa; o
|
|
18
|
-
/// conteudo mora onde o projeto puder consulta-lo — tipicamente uma coluna <c>jsonb</c>.
|
|
19
|
-
/// </para>
|
|
20
|
-
/// <para>
|
|
21
|
-
/// <b>A invariante que o teste cobra:</b> o provider e <b>uma instancia compartilhada por
|
|
22
|
-
/// todas as sessoes</b>. Por isso este tipo <b>nao tem campo de estado por conversa</b> — o
|
|
23
|
-
/// unico estado por sessao vive em <see cref="ProviderSessionState{TState}"/>, dentro da
|
|
24
|
-
/// propria <see cref="AgentSession"/>. Um campo de instancia guardando "a conversa atual"
|
|
25
|
-
/// e como o historico de um usuario aparece na tela de outro, e a falha nao aparece em teste
|
|
26
|
-
/// de uma sessao so: aparece em producao, sob concorrencia, como vazamento de dado alheio.
|
|
27
|
-
/// </para>
|
|
28
|
-
/// <para>
|
|
29
|
-
/// <b>De onde vem a chave da conversa</b>, nesta ordem:
|
|
30
|
-
/// <list type="number">
|
|
31
|
-
/// <item>o que o projeto amarrou com <see cref="BindConversation"/> — o caso do CRM, em que
|
|
32
|
-
/// a conversa ja existe como registro antes de o agente entrar;</item>
|
|
33
|
-
/// <item>o <c>ConversationId</c> da sessao, quando o proprio provider de modelo gerencia a
|
|
34
|
-
/// conversa (<see cref="ChatClientAgentSession"/>);</item>
|
|
35
|
-
/// <item>um id novo, <see cref="Guid.CreateVersion7"/> — ordenavel no tempo, ao contrario de
|
|
36
|
-
/// <c>Guid.NewGuid</c>, que fragmenta indice de banco.</item>
|
|
37
|
-
/// </list>
|
|
38
|
-
/// </para>
|
|
39
|
-
/// </remarks>
|
|
6
|
+
// Uma instancia atende todas as sessoes: estado por conversa so em ProviderSessionState, nunca em campo.
|
|
40
7
|
public sealed class ConversationStore : ChatHistoryProvider
|
|
41
8
|
{
|
|
42
|
-
|
|
43
|
-
/// Chave sob a qual a identidade da conversa e guardada no estado da sessao. Fixa e
|
|
44
|
-
/// publica porque ela viaja na serializacao da sessao: mudar esta string quebra a
|
|
45
|
-
/// retomada de toda conversa ja persistida.
|
|
46
|
-
/// </summary>
|
|
9
|
+
// Viaja na serializacao da sessao: mudar a string quebra a retomada das conversas ja persistidas.
|
|
47
10
|
public const string ConversationIdStateKey = "morph.conversation.id";
|
|
48
11
|
|
|
49
12
|
private readonly IConversationStore _store;
|
|
50
13
|
private readonly ProviderSessionState<string> _conversationId;
|
|
51
14
|
|
|
52
|
-
/// <summary>Monta o provider sobre um acervo.</summary>
|
|
53
|
-
/// <param name="store">Onde o historico e lido e gravado. Nunca nulo.</param>
|
|
54
15
|
public ConversationStore(IConversationStore store)
|
|
55
16
|
{
|
|
56
17
|
ArgumentNullException.ThrowIfNull(store);
|
|
@@ -59,23 +20,8 @@ public sealed class ConversationStore : ChatHistoryProvider
|
|
|
59
20
|
_conversationId = new ProviderSessionState<string>(NewConversationId, ConversationIdStateKey);
|
|
60
21
|
}
|
|
61
22
|
|
|
62
|
-
/// <inheritdoc />
|
|
63
|
-
/// <remarks>
|
|
64
|
-
/// Declara a chave que este provider ocupa no estado da sessao — e o que permite ao MAF
|
|
65
|
-
/// serializar e retomar uma sessao sem adivinhar o que e de quem.
|
|
66
|
-
/// </remarks>
|
|
67
23
|
public override IReadOnlyList<string> StateKeys => [ConversationIdStateKey];
|
|
68
24
|
|
|
69
|
-
/// <summary>
|
|
70
|
-
/// Amarra a sessao a uma conversa que <b>ja existe</b> no acervo do projeto. E o caminho
|
|
71
|
-
/// do CRM: a conversa nasce como registro de negocio, e o agente entra nela.
|
|
72
|
-
/// </summary>
|
|
73
|
-
/// <remarks>
|
|
74
|
-
/// Chamar isto depois de a sessao ja ter carregado historico troca a conversa sob os pes
|
|
75
|
-
/// da sessao. Amarre logo apos <c>CreateSessionAsync</c>.
|
|
76
|
-
/// </remarks>
|
|
77
|
-
/// <param name="session">A sessao.</param>
|
|
78
|
-
/// <param name="conversationId">A chave da conversa no acervo.</param>
|
|
79
25
|
public void BindConversation(AgentSession session, string conversationId)
|
|
80
26
|
{
|
|
81
27
|
ArgumentNullException.ThrowIfNull(session);
|
|
@@ -84,10 +30,6 @@ public sealed class ConversationStore : ChatHistoryProvider
|
|
|
84
30
|
_conversationId.SaveState(session, conversationId);
|
|
85
31
|
}
|
|
86
32
|
|
|
87
|
-
/// <summary>
|
|
88
|
-
/// A chave da conversa desta sessao, criando-a se ainda nao houver. Le e escreve
|
|
89
|
-
/// <b>na sessao</b>, jamais num campo deste objeto.
|
|
90
|
-
/// </summary>
|
|
91
33
|
public string GetConversationId(AgentSession session)
|
|
92
34
|
{
|
|
93
35
|
ArgumentNullException.ThrowIfNull(session);
|
|
@@ -95,11 +37,7 @@ public sealed class ConversationStore : ChatHistoryProvider
|
|
|
95
37
|
return _conversationId.GetOrInitializeState(session);
|
|
96
38
|
}
|
|
97
39
|
|
|
98
|
-
|
|
99
|
-
/// <remarks>
|
|
100
|
-
/// Devolve o historico guardado; as mensagens desta volta sao acrescentadas pelo agente
|
|
101
|
-
/// depois. Repetir aqui as mensagens do pedido duplicaria o turno corrente no prompt.
|
|
102
|
-
/// </remarks>
|
|
40
|
+
// O MAF acrescenta as mensagens desta volta depois; devolve-las aqui duplicaria o turno no prompt.
|
|
103
41
|
protected override async ValueTask<IEnumerable<ChatMessage>> ProvideChatHistoryAsync(
|
|
104
42
|
InvokingContext context,
|
|
105
43
|
CancellationToken cancellationToken = default)
|
|
@@ -111,19 +49,6 @@ public sealed class ConversationStore : ChatHistoryProvider
|
|
|
111
49
|
.ConfigureAwait(false);
|
|
112
50
|
}
|
|
113
51
|
|
|
114
|
-
/// <inheritdoc />
|
|
115
|
-
/// <remarks>
|
|
116
|
-
/// <para>
|
|
117
|
-
/// Grava o pedido e a resposta <b>desta</b> volta, nessa ordem — e so eles. Regravar o
|
|
118
|
-
/// historico inteiro a cada volta faria a conversa crescer em progressao geometrica.
|
|
119
|
-
/// </para>
|
|
120
|
-
/// <para>
|
|
121
|
-
/// <b>Volta que falhou nao e gravada.</b> Com <c>InvokeException</c> preenchida nao ha
|
|
122
|
-
/// resposta; persistir o pedido sozinho deixaria a conversa terminando numa pergunta que
|
|
123
|
-
/// o modelo nunca respondeu, e a proxima volta a mandaria de novo — o usuario veria a si
|
|
124
|
-
/// mesmo repetido.
|
|
125
|
-
/// </para>
|
|
126
|
-
/// </remarks>
|
|
127
52
|
protected override async ValueTask StoreChatHistoryAsync(
|
|
128
53
|
InvokedContext context,
|
|
129
54
|
CancellationToken cancellationToken = default)
|
|
@@ -135,9 +60,6 @@ public sealed class ConversationStore : ChatHistoryProvider
|
|
|
135
60
|
return;
|
|
136
61
|
}
|
|
137
62
|
|
|
138
|
-
// Os dois lados sao anulaveis no contrato do MAF (a resposta some quando a volta
|
|
139
|
-
// falhou, e o pedido pode vir vazio numa continuacao). Tratados como ausencia, nunca
|
|
140
|
-
// desreferenciados.
|
|
141
63
|
List<ChatMessage> round =
|
|
142
64
|
[
|
|
143
65
|
.. context.RequestMessages ?? [],
|
|
@@ -154,13 +76,6 @@ public sealed class ConversationStore : ChatHistoryProvider
|
|
|
154
76
|
.ConfigureAwait(false);
|
|
155
77
|
}
|
|
156
78
|
|
|
157
|
-
/// <summary>
|
|
158
|
-
/// O MAF declara <c>InvokingContext.Session</c> como anulavel, e uma invocacao sem
|
|
159
|
-
/// sessao nao tem onde guardar a identidade da conversa. As duas saidas silenciosas sao
|
|
160
|
-
/// piores que a recusa: inventar um id grava uma linha orfa que ninguem consegue ler de
|
|
161
|
-
/// novo, e usar uma chave fixa junta conversas de tenants diferentes no mesmo balde —
|
|
162
|
-
/// que e o vazamento que este provider existe para impedir.
|
|
163
|
-
/// </summary>
|
|
164
79
|
private static AgentSession RequireSession(AgentSession? session)
|
|
165
80
|
=> session ?? throw new InvalidOperationException(
|
|
166
81
|
"ConversationStore foi invocado sem AgentSession. Sem sessao nao ha identidade de " +
|
package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/IConversationStore.cs
CHANGED
|
@@ -2,51 +2,11 @@ using Microsoft.Extensions.AI;
|
|
|
2
2
|
|
|
3
3
|
namespace Morph.AiKit.Conversation;
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
/// Onde o historico de conversa fica guardado. <b>A costura entre o kit e o banco do
|
|
7
|
-
/// projeto</b> — e o unico ponto do <see cref="ConversationStore"/> que um projeto precisa
|
|
8
|
-
/// implementar para trocar memoria por tabela.
|
|
9
|
-
/// </summary>
|
|
10
|
-
/// <remarks>
|
|
11
|
-
/// <para>
|
|
12
|
-
/// <b>A decisao da casa que este tipo torna compilavel:</b> a conversa vive no armazenamento
|
|
13
|
-
/// <b>do projeto</b>, nao dentro da <c>AgentSession</c>. Num CRM multi-tenant a conversa e
|
|
14
|
-
/// um registro de negocio — ela precisa sobreviver ao restart do processo, ser lida por
|
|
15
|
-
/// relatorio, ser filtrada por tenant e ser apagada a pedido do titular. Nada disso e
|
|
16
|
-
/// possivel quando o historico so existe na memoria de uma sessao.
|
|
17
|
-
/// </para>
|
|
18
|
-
/// <para>
|
|
19
|
-
/// <b>O kit nao conhece banco.</b> Esta interface e deliberadamente pequena e sem tipo de
|
|
20
|
-
/// infraestrutura na assinatura: nenhum <c>DbContext</c>, nenhuma conexao, nenhum
|
|
21
|
-
/// <c>jsonb</c>. A implementacao de producao vive no projeto (handler falando com o
|
|
22
|
-
/// <c>ApplicationDbContext</c> direto, sem camada de service e sem repository generico);
|
|
23
|
-
/// a implementacao de teste e <see cref="InMemoryConversationStore"/>.
|
|
24
|
-
/// </para>
|
|
25
|
-
/// <para>
|
|
26
|
-
/// <b>Contrato de identidade:</b> <c>conversationId</c> e a chave do acervo, opaca para o
|
|
27
|
-
/// kit. Num CRM ela costuma ser o id da linha de conversa — e por isso
|
|
28
|
-
/// <see cref="ConversationStore.BindConversation"/> existe: e como o projeto amarra uma
|
|
29
|
-
/// sessao a uma conversa que ja existe, em vez de deixar o kit inventar um id.
|
|
30
|
-
/// </para>
|
|
31
|
-
/// <para>
|
|
32
|
-
/// <b>Contrato de concorrencia:</b> uma unica instancia atende todas as sessoes ao mesmo
|
|
33
|
-
/// tempo. A implementacao precisa ser segura para uso concorrente e, acima de tudo,
|
|
34
|
-
/// <b>nunca deixar mensagem de uma conversa aparecer em outra</b> — a chave e o unico
|
|
35
|
-
/// isolamento que existe aqui.
|
|
36
|
-
/// </para>
|
|
37
|
-
/// </remarks>
|
|
5
|
+
// Uma instancia atende todas as sessoes ao mesmo tempo: a implementacao precisa ser segura sob concorrencia.
|
|
38
6
|
public interface IConversationStore
|
|
39
7
|
{
|
|
40
|
-
/// <summary>
|
|
41
|
-
/// O historico da conversa, na ordem cronologica. Conversa inexistente devolve lista
|
|
42
|
-
/// <b>vazia</b>, nunca nulo e nunca excecao: uma primeira volta e o caso normal.
|
|
43
|
-
/// </summary>
|
|
44
8
|
ValueTask<IReadOnlyList<ChatMessage>> LoadAsync(string conversationId, CancellationToken cancellationToken);
|
|
45
9
|
|
|
46
|
-
/// <summary>
|
|
47
|
-
/// Acrescenta as mensagens de uma volta ao fim da conversa. Chamado uma vez por volta,
|
|
48
|
-
/// com o pedido e a resposta daquela volta — nunca com o historico inteiro.
|
|
49
|
-
/// </summary>
|
|
50
10
|
ValueTask AppendAsync(
|
|
51
11
|
string conversationId,
|
|
52
12
|
IReadOnlyList<ChatMessage> messages,
|
package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/InMemoryConversationStore.cs
CHANGED
|
@@ -3,33 +3,13 @@ using Microsoft.Extensions.AI;
|
|
|
3
3
|
|
|
4
4
|
namespace Morph.AiKit.Conversation;
|
|
5
5
|
|
|
6
|
-
/// <summary>
|
|
7
|
-
/// <see cref="IConversationStore"/> em memoria. <b>Para teste e para prototipo</b> — nao
|
|
8
|
-
/// para producao: o historico morre com o processo, e um CRM que perde a conversa no deploy
|
|
9
|
-
/// nao tem conversa.
|
|
10
|
-
/// </summary>
|
|
11
|
-
/// <remarks>
|
|
12
|
-
/// <para>
|
|
13
|
-
/// Existe para que a suite do kit prove o <see cref="ConversationStore"/> <b>sem banco</b>,
|
|
14
|
-
/// e para que um projeto tenha um exemplar de referencia do contrato antes de escrever a
|
|
15
|
-
/// versao com <c>jsonb</c>.
|
|
16
|
-
/// </para>
|
|
17
|
-
/// <para>
|
|
18
|
-
/// <b>Seguro sob concorrencia por construcao</b>, e nao por acidente: uma
|
|
19
|
-
/// <see cref="ConcurrentDictionary{TKey, TValue}"/> por conversa, e a lista de cada conversa
|
|
20
|
-
/// sempre mutada sob o lock daquela conversa. Sem isso, o teste de duas sessoes concorrentes
|
|
21
|
-
/// mediria a falta de trava aqui em vez do isolamento do provider.
|
|
22
|
-
/// </para>
|
|
23
|
-
/// </remarks>
|
|
24
6
|
public sealed class InMemoryConversationStore : IConversationStore
|
|
25
7
|
{
|
|
26
8
|
private readonly ConcurrentDictionary<string, List<ChatMessage>> _conversations =
|
|
27
9
|
new(StringComparer.Ordinal);
|
|
28
10
|
|
|
29
|
-
/// <summary>As conversas que existem no acervo, para inspecao em teste.</summary>
|
|
30
11
|
public IReadOnlyCollection<string> ConversationIds => _conversations.Keys.ToArray();
|
|
31
12
|
|
|
32
|
-
/// <inheritdoc />
|
|
33
13
|
public ValueTask<IReadOnlyList<ChatMessage>> LoadAsync(
|
|
34
14
|
string conversationId,
|
|
35
15
|
CancellationToken cancellationToken)
|
|
@@ -48,7 +28,6 @@ public sealed class InMemoryConversationStore : IConversationStore
|
|
|
48
28
|
}
|
|
49
29
|
}
|
|
50
30
|
|
|
51
|
-
/// <inheritdoc />
|
|
52
31
|
public ValueTask AppendAsync(
|
|
53
32
|
string conversationId,
|
|
54
33
|
IReadOnlyList<ChatMessage> messages,
|
|
@@ -2,39 +2,17 @@ using Microsoft.Extensions.AI;
|
|
|
2
2
|
|
|
3
3
|
namespace Morph.AiKit.Evals;
|
|
4
4
|
|
|
5
|
-
/// <summary>
|
|
6
|
-
/// Conta quantas vezes o cliente interno foi realmente invocado, e repassa tudo o mais
|
|
7
|
-
/// intacto.
|
|
8
|
-
/// </summary>
|
|
9
|
-
/// <remarks>
|
|
10
|
-
/// <para>
|
|
11
|
-
/// <b>Por que existe.</b> Nos modos que TEM cliente real (<see cref="EvalMode.Refresh"/> e
|
|
12
|
-
/// <see cref="EvalMode.Online"/>) nao ha <see cref="OfflineChatClient"/> para contar, e sem
|
|
13
|
-
/// contador o sumario nao consegue dizer se a arvore de cache mudou. "A cache esta fria" e
|
|
14
|
-
/// uma afirmacao com consequencia (a arvore precisa ser commitada), e afirmacao com
|
|
15
|
-
/// consequencia se mede.
|
|
16
|
-
/// </para>
|
|
17
|
-
/// <para>
|
|
18
|
-
/// <b><see cref="GetService"/> repassa VERBATIM</b>, e isso e load-bearing: e por ele que o
|
|
19
|
-
/// <c>ChatClientMetadata</c> — com o <c>ProviderName</c> e o <c>DefaultModelId</c> que
|
|
20
|
-
/// entram na CHAVE de cache — chega a camada de cache. Um wrapper que respondesse por conta
|
|
21
|
-
/// propria aqui gravaria a arvore sob uma identidade diferente da que a leitura offline
|
|
22
|
-
/// replica, e todo cenario daria miss.
|
|
23
|
-
/// </para>
|
|
24
|
-
/// </remarks>
|
|
25
5
|
public sealed class CountingChatClient(IChatClient inner) : IChatClient
|
|
26
6
|
{
|
|
27
7
|
private readonly IChatClient _inner = inner ?? throw new ArgumentNullException(nameof(inner));
|
|
28
8
|
private readonly Lock _gate = new();
|
|
29
9
|
private int _calls;
|
|
30
10
|
|
|
31
|
-
/// <summary>Quantas chamadas atravessaram ate o cliente real.</summary>
|
|
32
11
|
public int Calls
|
|
33
12
|
{
|
|
34
13
|
get { lock (_gate) { return _calls; } }
|
|
35
14
|
}
|
|
36
15
|
|
|
37
|
-
/// <inheritdoc />
|
|
38
16
|
public Task<ChatResponse> GetResponseAsync(
|
|
39
17
|
IEnumerable<ChatMessage> messages,
|
|
40
18
|
ChatOptions? options = null,
|
|
@@ -44,7 +22,6 @@ public sealed class CountingChatClient(IChatClient inner) : IChatClient
|
|
|
44
22
|
return _inner.GetResponseAsync(messages, options, cancellationToken);
|
|
45
23
|
}
|
|
46
24
|
|
|
47
|
-
/// <inheritdoc />
|
|
48
25
|
public IAsyncEnumerable<ChatResponseUpdate> GetStreamingResponseAsync(
|
|
49
26
|
IEnumerable<ChatMessage> messages,
|
|
50
27
|
ChatOptions? options = null,
|
|
@@ -54,11 +31,10 @@ public sealed class CountingChatClient(IChatClient inner) : IChatClient
|
|
|
54
31
|
return _inner.GetStreamingResponseAsync(messages, options, cancellationToken);
|
|
55
32
|
}
|
|
56
33
|
|
|
57
|
-
|
|
34
|
+
// Repassa verbatim: o ChatClientMetadata daqui entra na chave de cache.
|
|
58
35
|
public object? GetService(Type serviceType, object? serviceKey = null)
|
|
59
36
|
=> _inner.GetService(serviceType, serviceKey);
|
|
60
37
|
|
|
61
|
-
/// <inheritdoc />
|
|
62
38
|
public void Dispose() => _inner.Dispose();
|
|
63
39
|
|
|
64
40
|
private void Count()
|
|
@@ -6,79 +6,31 @@ using Microsoft.Extensions.AI.Evaluation;
|
|
|
6
6
|
|
|
7
7
|
namespace Morph.AiKit.Evals;
|
|
8
8
|
|
|
9
|
-
/// <summary>O que uma assercao deterministica pergunta a uma resposta.</summary>
|
|
10
|
-
/// <remarks>
|
|
11
|
-
/// A lista e curta de proposito. Toda entrada aqui e uma propriedade que se decide sem
|
|
12
|
-
/// julgamento: ou o texto contem, ou nao contem; ou a ferramenta foi escolhida, ou nao foi.
|
|
13
|
-
/// Uma "assercao" que precisasse de um modelo para decidir seria juizo, e juizo nao reprova
|
|
14
|
-
/// sozinho neste kit.
|
|
15
|
-
/// </remarks>
|
|
16
9
|
public enum EvalAssertionKind
|
|
17
10
|
{
|
|
18
|
-
/// <summary>O texto da resposta contem o valor (ignorando caixa).</summary>
|
|
19
11
|
Contains = 0,
|
|
20
12
|
|
|
21
|
-
/// <summary>O texto da resposta NAO contem o valor (ignorando caixa).</summary>
|
|
22
13
|
NotContains = 1,
|
|
23
14
|
|
|
24
|
-
/// <summary>A resposta e um objeto JSON e tem a chave de topo indicada em <c>Target</c>.</summary>
|
|
25
15
|
JsonHasKey = 2,
|
|
26
16
|
|
|
27
|
-
/// <summary>A resposta nao carrega dado pessoal nas formas que o kit sabe reconhecer.</summary>
|
|
28
17
|
NoPii = 3,
|
|
29
18
|
|
|
30
|
-
/// <summary>A resposta escolheu a ferramenta nomeada em <c>Target</c>.</summary>
|
|
31
19
|
ToolCalled = 4,
|
|
32
20
|
|
|
33
|
-
/// <summary>A ferramenta <c>Target</c> foi chamada com o argumento <c>Value</c> no formato <c>chave=valor</c>.</summary>
|
|
34
21
|
ToolArgument = 5,
|
|
35
22
|
}
|
|
36
23
|
|
|
37
|
-
/// <summary>Veredito de uma assercao: o booleano e a frase que o explica.</summary>
|
|
38
|
-
/// <param name="Passed">Se a propriedade se sustentou.</param>
|
|
39
|
-
/// <param name="Reason">Por que — nomeando o que foi procurado, para nao mandar o leitor adivinhar.</param>
|
|
40
24
|
public sealed record EvalAssertionOutcome(bool Passed, string Reason)
|
|
41
25
|
{
|
|
42
|
-
/// <summary>Nome da assercao que produziu este veredito.</summary>
|
|
43
|
-
/// <remarks>
|
|
44
|
-
/// Carimbado por <see cref="EvalAssertion.Evaluate"/>. Existe porque uma falha que chega a
|
|
45
|
-
/// jusante como contador ("Expected 0, Actual 1") nao diz O QUE quebrou: quem le precisa
|
|
46
|
-
/// abrir o dataset e adivinhar. Um eval que morde anonimamente obriga a investigacao que
|
|
47
|
-
/// ele deveria dispensar.
|
|
48
|
-
/// </remarks>
|
|
49
26
|
public string? Assertion { get; init; }
|
|
50
27
|
|
|
51
|
-
/// <summary>O tipo da assercao que julgou — a REGUA, nao so o nome dela.</summary>
|
|
52
28
|
public EvalAssertionKind? Kind { get; init; }
|
|
53
29
|
}
|
|
54
30
|
|
|
55
|
-
/// <summary>
|
|
56
|
-
/// A assercao deterministica: o unico tipo de avaliador que REPROVA neste kit.
|
|
57
|
-
/// </summary>
|
|
58
|
-
/// <remarks>
|
|
59
|
-
/// <para>
|
|
60
|
-
/// <b>Por que so ela decide.</b> Os avaliadores de qualidade da biblioteca sao juizo de LLM,
|
|
61
|
-
/// e juizo de LLM varia. Um gate que reprovasse por variancia de juiz reintroduziria o
|
|
62
|
-
/// FLAKY que faz suites de eval serem desligadas do CI — e um no de eval desligado nao
|
|
63
|
-
/// verifica nada. Entao a divisao e explicita: assercao deterministica decide; qualidade e
|
|
64
|
-
/// numero reportado.
|
|
65
|
-
/// </para>
|
|
66
|
-
/// <para>
|
|
67
|
-
/// <b>Ela e um <see cref="IEvaluator"/> de verdade</b>, e nao um predicado aplicado por
|
|
68
|
-
/// fora, para que a metrica caia no MESMO relatorio que as de qualidade. Um resultado que
|
|
69
|
-
/// vivesse fora do relatorio precisaria de um segundo lugar para ser lido, e o segundo lugar
|
|
70
|
-
/// e onde as duas verdades comecam.
|
|
71
|
-
/// </para>
|
|
72
|
-
/// </remarks>
|
|
73
31
|
public sealed record EvalAssertion : IEvaluator
|
|
74
32
|
{
|
|
75
|
-
|
|
76
|
-
/// Formas de dado pessoal que o kit reconhece. Deliberadamente poucas e literais: um
|
|
77
|
-
/// detector de PII abrangente e um projeto proprio, e prometer mais do que se mede seria
|
|
78
|
-
/// pior do que nao prometer. Sem <c>\b</c> em nenhum padrao — a fronteira de palavra do
|
|
79
|
-
/// .NET tambem nao trata acento como letra em toda cultura, e um padrao que nao casa e
|
|
80
|
-
/// pior que um padrao que nao existe.
|
|
81
|
-
/// </summary>
|
|
33
|
+
// Sem \b: a fronteira de palavra do .NET nao trata acento como letra em toda cultura.
|
|
82
34
|
private static readonly (string Label, Regex Pattern)[] PiiPatterns =
|
|
83
35
|
[
|
|
84
36
|
("e-mail", new Regex("[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+[.][A-Za-z]{2,}", RegexOptions.CultureInvariant)),
|
|
@@ -86,26 +38,17 @@ public sealed record EvalAssertion : IEvaluator
|
|
|
86
38
|
("telefone", new Regex("[(][0-9]{2}[)] ?[0-9]{4,5}-[0-9]{4}", RegexOptions.CultureInvariant)),
|
|
87
39
|
];
|
|
88
40
|
|
|
89
|
-
|
|
90
|
-
/// Nome da metrica. UNICO dentro de um cenario: a biblioteca indexa metricas por nome, e
|
|
91
|
-
/// duas assercoes homonimas fariam a segunda sobrescrever a primeira em silencio — um
|
|
92
|
-
/// cenario que parece cobrar duas propriedades e cobra uma.
|
|
93
|
-
/// </summary>
|
|
41
|
+
// Unico por cenario: a biblioteca indexa metricas por nome e a homonima sobrescreve em silencio.
|
|
94
42
|
public required string Name { get; init; }
|
|
95
43
|
|
|
96
|
-
/// <summary>O que esta assercao pergunta.</summary>
|
|
97
44
|
public required EvalAssertionKind Kind { get; init; }
|
|
98
45
|
|
|
99
|
-
/// <summary>Chave JSON ou nome de ferramenta, conforme o <see cref="Kind"/>.</summary>
|
|
100
46
|
public string? Target { get; init; }
|
|
101
47
|
|
|
102
|
-
/// <summary>Texto procurado, ou <c>chave=valor</c> em <see cref="EvalAssertionKind.ToolArgument"/>.</summary>
|
|
103
48
|
public string? Value { get; init; }
|
|
104
49
|
|
|
105
|
-
/// <inheritdoc />
|
|
106
50
|
public IReadOnlyCollection<string> EvaluationMetricNames => [Name];
|
|
107
51
|
|
|
108
|
-
/// <summary>Aplica a assercao a uma resposta. Puro: nao le disco, nao le ambiente, nao chama rede.</summary>
|
|
109
52
|
public EvalAssertionOutcome Evaluate(ChatResponse response)
|
|
110
53
|
{
|
|
111
54
|
ArgumentNullException.ThrowIfNull(response);
|
|
@@ -135,12 +78,9 @@ public sealed record EvalAssertion : IEvaluator
|
|
|
135
78
|
_ => new EvalAssertionOutcome(false, $"tipo de assercao desconhecido: {Kind}"),
|
|
136
79
|
};
|
|
137
80
|
|
|
138
|
-
// Carimba QUEM julgou. O veredito sozinho e um booleano; com o nome e a regua ele vira
|
|
139
|
-
// uma frase que nomeia o defeito sem obrigar ninguem a abrir o dataset.
|
|
140
81
|
return outcome with { Assertion = Name, Kind = this.Kind };
|
|
141
82
|
}
|
|
142
83
|
|
|
143
|
-
/// <inheritdoc />
|
|
144
84
|
public ValueTask<EvaluationResult> EvaluateAsync(
|
|
145
85
|
IEnumerable<ChatMessage> messages,
|
|
146
86
|
ChatResponse modelResponse,
|
|
@@ -156,7 +96,6 @@ public sealed record EvalAssertion : IEvaluator
|
|
|
156
96
|
return ValueTask.FromResult(new EvaluationResult(metric));
|
|
157
97
|
}
|
|
158
98
|
|
|
159
|
-
/// <summary>Texto concatenado da resposta — a superficie que as assercoes de texto leem.</summary>
|
|
160
99
|
internal static string TextOf(ChatResponse response)
|
|
161
100
|
{
|
|
162
101
|
var sb = new StringBuilder();
|
|
@@ -1,36 +1,9 @@
|
|
|
1
1
|
namespace Morph.AiKit.Evals;
|
|
2
2
|
|
|
3
|
-
/// <summary>
|
|
4
|
-
/// Lancada quando um cenario de eval NAO estava na cache e o modo corrente e
|
|
5
|
-
/// <see cref="EvalMode.Offline"/> — o unico caminho pelo qual a suite tocaria a rede.
|
|
6
|
-
/// </summary>
|
|
7
|
-
/// <remarks>
|
|
8
|
-
/// <para>
|
|
9
|
-
/// <b>Por que uma excecao, e nao um resultado.</b> A regra do kit e "excecao nunca como
|
|
10
|
-
/// fluxo de controle"; esta e a excecao de CONTRATO que a propria regra admite. O ponto de
|
|
11
|
-
/// deteccao e o <see cref="OfflineChatClient"/>, que implementa <c>IChatClient</c> — uma
|
|
12
|
-
/// interface de terceiro cuja assinatura devolve <c>Task<ChatResponse></c> e nao tem
|
|
13
|
-
/// onde carregar um veredito. Devolver uma resposta vazia ali seria pior do que lancar:
|
|
14
|
-
/// o eval ficaria VERDE sobre um cenario que ninguem avaliou, que e exatamente o defeito
|
|
15
|
-
/// (gate verde sobre verificacao que nao aconteceu) que esta feature existe para fechar.
|
|
16
|
-
/// </para>
|
|
17
|
-
/// <para>
|
|
18
|
-
/// <b>A mensagem e o produto.</b> Em CI, com a cache commitada, um miss significa uma coisa
|
|
19
|
-
/// so: o prompt ou o modelo mudou e ninguem regravou. Entao a mensagem nomeia as quatro
|
|
20
|
-
/// coisas que o leitor precisa para agir sem abrir o codigo — o cenario, o caminho da
|
|
21
|
-
/// cache, o comando de regravacao e o arquivo de credencial esperado. Um "cache miss"
|
|
22
|
-
/// seco mandaria o leitor procurar as quatro.
|
|
23
|
-
/// </para>
|
|
24
|
-
/// </remarks>
|
|
25
3
|
public sealed class EvalCacheMissException : Exception
|
|
26
4
|
{
|
|
27
|
-
/// <summary>Comando literal que regrava a arvore de cache. Nunca implicito, nunca automatico.</summary>
|
|
28
5
|
public const string RefreshCommand = "morph-spec verify {feature} --refresh-cache";
|
|
29
6
|
|
|
30
|
-
/// <summary>Cria a excecao com todos os dados que a mensagem precisa nomear.</summary>
|
|
31
|
-
/// <param name="scenarioName">O cenario que faltou na cache.</param>
|
|
32
|
-
/// <param name="cachePath">Raiz da arvore de cache consultada.</param>
|
|
33
|
-
/// <param name="envFile">Arquivo (gitignored) de onde a credencial viria numa regravacao.</param>
|
|
34
7
|
public EvalCacheMissException(string scenarioName, string cachePath, string envFile)
|
|
35
8
|
: base(BuildMessage(scenarioName, cachePath, envFile))
|
|
36
9
|
{
|
|
@@ -39,13 +12,10 @@ public sealed class EvalCacheMissException : Exception
|
|
|
39
12
|
EnvFile = envFile;
|
|
40
13
|
}
|
|
41
14
|
|
|
42
|
-
/// <summary>O cenario que faltou na cache.</summary>
|
|
43
15
|
public string ScenarioName { get; }
|
|
44
16
|
|
|
45
|
-
/// <summary>Raiz da arvore de cache consultada.</summary>
|
|
46
17
|
public string CachePath { get; }
|
|
47
18
|
|
|
48
|
-
/// <summary>Arquivo de credencial esperado por uma regravacao.</summary>
|
|
49
19
|
public string EnvFile { get; }
|
|
50
20
|
|
|
51
21
|
private static string BuildMessage(string scenarioName, string cachePath, string envFile)
|
|
@@ -1,78 +1,27 @@
|
|
|
1
1
|
namespace Morph.AiKit.Evals;
|
|
2
2
|
|
|
3
|
-
/// <summary>
|
|
4
|
-
/// Tudo o que o <see cref="EvalFixture"/> precisa saber, e nada que ele possa adivinhar.
|
|
5
|
-
/// </summary>
|
|
6
|
-
/// <remarks>
|
|
7
|
-
/// <para>
|
|
8
|
-
/// <b><see cref="TimeToLive"/> nao tem valor default, e isso e o item mais importante deste
|
|
9
|
-
/// tipo.</b> O default da biblioteca e de 14 dias E A EXPIRACAO E DESTRUTIVA NA LEITURA: ao
|
|
10
|
-
/// achar uma entrada vencida, o provedor APAGA o diretorio da chave e devolve nulo. Uma
|
|
11
|
-
/// cache commitada com o default se apaga sozinha ao ser lida, duas semanas depois de
|
|
12
|
-
/// gravada — e o sintoma chega como miss em massa (e arvore suja no <c>git status</c>) num
|
|
13
|
-
/// dia em que ninguem mexeu em nada. Por isso o valor e obrigatorio aqui e o fixture recusa
|
|
14
|
-
/// qualquer coisa abaixo de <see cref="MinimumTimeToLive"/>.
|
|
15
|
-
/// </para>
|
|
16
|
-
/// <para>
|
|
17
|
-
/// <b><see cref="ProviderName"/> e <see cref="DefaultModelId"/> entram na chave de cache</b>
|
|
18
|
-
/// e por isso viajam como configuracao, nao como constante: eles precisam reproduzir o run
|
|
19
|
-
/// que GRAVOU a arvore. O endpoint nao entra na chave (medido na v10.9.0), que e o que
|
|
20
|
-
/// permite ao cliente offline nao ter endpoint algum.
|
|
21
|
-
/// </para>
|
|
22
|
-
/// </remarks>
|
|
23
3
|
public sealed record EvalCacheOptions
|
|
24
4
|
{
|
|
25
|
-
/// <summary>
|
|
26
|
-
/// Piso do tempo de vida das entradas. Um ano nao e um numero bonito: e o menor prazo
|
|
27
|
-
/// que garante que uma cache commitada sobreviva a um ciclo de release sem se apagar
|
|
28
|
-
/// durante uma leitura.
|
|
29
|
-
/// </summary>
|
|
30
5
|
public static readonly TimeSpan MinimumTimeToLive = TimeSpan.FromDays(365);
|
|
31
6
|
|
|
32
|
-
/// <summary>Raiz da arvore de cache. COMMITADA.</summary>
|
|
33
7
|
public required string CacheRoot { get; init; }
|
|
34
8
|
|
|
35
|
-
/// <summary>
|
|
36
|
-
/// Raiz dos resultados de execucao. FORA da arvore commitada — por construcao, e nao por
|
|
37
|
-
/// um <c>.gitignore</c> dentro de um diretorio commitado (regra de ignore ali e regra que
|
|
38
|
-
/// a proxima pessoa remove sem entender). Os resultados carregam latencia medida por
|
|
39
|
-
/// cronometro, uso de tokens e o flag de cache hit: commita-los e diff garantido em toda
|
|
40
|
-
/// rodada, sem sinal nenhum.
|
|
41
|
-
/// </summary>
|
|
42
9
|
public required string ResultsRoot { get; init; }
|
|
43
10
|
|
|
44
|
-
/// <summary>Raiz do dataset versionado, em <c>{agente}/{tenant}/{cenario}.json</c>.</summary>
|
|
45
11
|
public required string DatasetRoot { get; init; }
|
|
46
12
|
|
|
47
|
-
|
|
13
|
+
// Sem default: o da biblioteca (14 dias) apaga a entrada vencida durante a leitura da arvore commitada.
|
|
48
14
|
public required TimeSpan TimeToLive { get; init; }
|
|
49
15
|
|
|
50
|
-
/// <summary>O <c>ProviderName</c> que entra na chave de cache.</summary>
|
|
51
16
|
public required string ProviderName { get; init; }
|
|
52
17
|
|
|
53
|
-
/// <summary>O <c>DefaultModelId</c> que entra na chave de cache. Nulo e um valor.</summary>
|
|
54
18
|
public string? DefaultModelId { get; init; }
|
|
55
19
|
|
|
56
|
-
/// <summary>De onde a resposta pode vir nesta execucao.</summary>
|
|
57
20
|
public required EvalMode Mode { get; init; }
|
|
58
21
|
|
|
59
|
-
/// <summary>
|
|
60
|
-
/// Onde o sumario JSON e escrito. O CAMINHO E DITADO PELO CHAMADOR (o CLI escolhe um
|
|
61
|
-
/// arquivo temporario) para que dois nos de eval no mesmo <c>verify</c> nao colidam.
|
|
62
|
-
/// Nulo quando ninguem quer sumario — rodar sem sumario e legitimo, inventar um caminho
|
|
63
|
-
/// nao e.
|
|
64
|
-
/// </summary>
|
|
65
22
|
public string? SummaryPath { get; init; }
|
|
66
23
|
|
|
67
|
-
/// <summary>
|
|
68
|
-
/// Arquivo (gitignored) de onde a credencial viria numa regravacao. So aparece em
|
|
69
|
-
/// mensagem de erro; o fixture nunca o le.
|
|
70
|
-
/// </summary>
|
|
71
24
|
public string EnvFile { get; init; } = ".env.evals";
|
|
72
25
|
|
|
73
|
-
/// <summary>
|
|
74
|
-
/// O pin corrente, como o CLI o entregou. Nulo desliga a checagem de carimbo — util para
|
|
75
|
-
/// um projeto que constroi o fixture a mao, e explicitamente NAO o caminho do CLI.
|
|
76
|
-
/// </summary>
|
|
77
26
|
public EvalPin? Pin { get; init; }
|
|
78
27
|
}
|