@polymorphism-tech/morph-spec 8.33.0 → 8.35.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 +22 -2
- package/README.md +2 -2
- package/bin/morph-spec.js +43 -5
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +5 -4
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +2 -2
- package/framework/MORPH.md +20 -22
- package/framework/agents.json +198 -44
- package/framework/ai-pin.json +22 -0
- package/framework/commands/morph-media.md +45 -0
- package/framework/docs/CLI.md +238 -10
- package/framework/evals/proof-rubrics/README.md +76 -0
- package/framework/evals/proof-rubrics/eval-de-modelo.md +64 -0
- package/framework/evals/proof-rubrics/fiacao.md +54 -0
- package/framework/evals/proof-rubrics/guard-cego.md +52 -0
- package/framework/evals/proof-rubrics/media-claims.md +109 -0
- package/framework/evals/proof-rubrics/mutation-proof.md +77 -0
- package/framework/evals/proof-rubrics/observabilidade-genai.md +119 -0
- package/framework/evals/proof-rubrics/verde-nao-e-coberto.md +48 -0
- package/framework/evals/rubrics/architecture.md +30 -0
- package/framework/evals/rubrics/code-quality.md +26 -0
- package/framework/evals/rubrics/test-coverage.md +20 -0
- package/framework/hooks/README.md +11 -1
- package/framework/hooks/claude-code/core/block-destructive-worktree-removal.js +5 -8
- package/framework/hooks/claude-code/core/block-raw-worktree-add.js +79 -0
- package/framework/hooks/claude-code/core/chain.js +27 -7
- package/framework/hooks/claude-code/core/protect-spec-files.js +41 -4
- package/framework/hooks/claude-code/core/worktree-context.js +83 -14
- package/framework/hooks/claude-code/core/worktree-create.js +206 -0
- package/framework/hooks/claude-code/core/worktree-env-inject.js +87 -0
- package/framework/hooks/claude-code/core/worktree-remove.js +81 -0
- package/framework/hooks/shared/command-tokens.js +23 -0
- package/framework/hooks/shared/e2e-check.js +0 -0
- package/framework/hooks/shared/git-paths.js +25 -0
- package/framework/hooks/shared/hook-response.js +17 -0
- package/framework/hooks/shared/morph-cli.js +134 -0
- package/framework/rules/ai-agents-standards.md +14 -2
- package/framework/rules/infrastructure-standards.md +3 -1
- package/framework/schemas/ai-pin.schema.json +97 -0
- package/framework/schemas/config.schema.json +107 -0
- package/framework/schemas/feature.schema.json +23 -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 +28 -7
- 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 +20 -2
- package/framework/skills/core/morph-media/SKILL.md +215 -0
- package/framework/skills/core/morph-plan/SKILL.md +38 -4
- package/framework/skills/core/morph-proposal/SKILL.md +1 -1
- package/framework/skills/core/morph-review/SKILL.md +166 -9
- 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 +1 -1
- package/framework/standards/PROVENANCE.json +81 -3
- package/framework/standards/STANDARDS.json +1504 -830
- package/framework/standards/ai-agents/agent-archetypes.md +160 -0
- package/framework/standards/ai-agents/agent-session.md +256 -160
- package/framework/standards/ai-agents/agent-spec.md +196 -0
- package/framework/standards/ai-agents/batching.md +217 -193
- package/framework/standards/ai-agents/context-providers.md +261 -188
- package/framework/standards/ai-agents/conversational-agent-with-phases.md +239 -0
- package/framework/standards/ai-agents/cost-and-budget.md +256 -0
- package/framework/standards/ai-agents/durable-workflows-hitl.md +98 -5
- package/framework/standards/ai-agents/evals-with-cache.md +289 -0
- package/framework/standards/ai-agents/guardrails.md +222 -0
- package/framework/standards/ai-agents/llm-runtime-defaults.md +226 -0
- package/framework/standards/ai-agents/mcp-server.md +351 -250
- package/framework/standards/ai-agents/mcp-tools.md +250 -197
- package/framework/standards/ai-agents/media-pipeline.md +352 -0
- package/framework/standards/ai-agents/media-video.md +267 -0
- package/framework/standards/ai-agents/middleware-patterns.md +241 -207
- package/framework/standards/ai-agents/modalities-image-gen.md +422 -173
- package/framework/standards/ai-agents/modalities-speech-to-text.md +177 -171
- package/framework/standards/ai-agents/modalities-text-to-speech.md +189 -181
- package/framework/standards/ai-agents/modalities-vision.md +205 -201
- package/framework/standards/ai-agents/multi-agent-patterns.md +211 -155
- package/framework/standards/ai-agents/observability-patterns.md +227 -17
- package/framework/standards/ai-agents/production.md +260 -231
- package/framework/standards/ai-agents/prompt-sources.md +389 -0
- package/framework/standards/ai-agents/providers/model-registry.md +293 -158
- package/framework/standards/ai-agents/rag-custom-pgvector.md +69 -4
- package/framework/standards/ai-agents/service-tier-flex.md +143 -135
- package/framework/standards/ai-agents/setup.md +166 -50
- package/framework/standards/ai-agents/structured-output.md +280 -249
- package/framework/standards/ai-agents/sweet-spot.md +129 -109
- package/framework/standards/ai-agents/testing-ai.md +236 -0
- package/framework/standards/ai-agents/vector-data-extensions.md +50 -20
- package/framework/standards/ai-agents/workflows.md +27 -7
- package/framework/standards/frontend/design-system/ai-image-generation.md +12 -0
- package/framework/standards/frontend/design-system/ai-video-generation.md +13 -0
- package/framework/standards/infrastructure/docker/local-compose-isolation.md +90 -0
- package/framework/templates/REGISTRY.json +14 -1
- package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +95 -95
- package/framework/templates/code/sql/ai-agents/agent-prompts-migration.sql.template +58 -58
- package/framework/templates/dotnet/ai-kit/Directory.Build.props +25 -0
- package/framework/templates/dotnet/ai-kit/Directory.Packages.props +26 -0
- package/framework/templates/dotnet/ai-kit/Morph.AiKit.sln +56 -0
- package/framework/templates/dotnet/ai-kit/NuGet.config +12 -0
- package/framework/templates/dotnet/ai-kit/README.md +128 -0
- package/framework/templates/dotnet/ai-kit/global.json +9 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentApi.cs +65 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactory.cs +289 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactoryOptions.cs +91 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpec.cs +91 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpecOptions.cs +181 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/AiKitAssemblyMarker.cs +14 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/ConversationStore.cs +174 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/IConversationStore.cs +54 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/InMemoryConversationStore.cs +75 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/CountingChatClient.cs +71 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalAssertion.cs +246 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheMissException.cs +57 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheOptions.cs +78 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheStamp.cs +225 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalFixture.cs +463 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalMode.cs +32 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalQualityEvaluators.cs +49 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalScenario.cs +214 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalSummary.cs +168 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/OfflineChatClient.cs +142 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/ShortPathResponseCache.cs +349 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaGenerator.cs +42 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaProvider.cs +77 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaArtifact.cs +28 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaDisclosure.cs +107 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaGenerator.cs +253 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaIdempotencyKey.cs +103 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJob.cs +259 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobId.cs +21 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobState.cs +47 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaPricing.cs +102 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaProvenance.cs +44 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaRequest.cs +192 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaResult.cs +94 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaReview.cs +99 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaSeams.cs +97 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaStore.cs +133 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MorphMedia.cs +93 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/GoogleImageProvider.cs +112 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/OpenAIImageProvider.cs +288 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/VeoVideoProvider.cs +158 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/IUsageCostSink.cs +29 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostMiddleware.cs +208 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostRecord.cs +95 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Morph.AiKit.csproj +38 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphOpenTelemetryExtensions.cs +119 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphTelemetryOptions.cs +82 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/IProviderClientFactory.cs +33 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelAlias.cs +267 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelRegistry.cs +474 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/model-registry.v2.json +87 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaIngest.cs +169 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaPlan.cs +49 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentFactoryTests.cs +495 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentSpecTests.cs +260 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ConversationStoreTests.cs +405 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/DeltaIngestTests.cs +379 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalAssertionTests.cs +197 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheMissTests.cs +139 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheStampTests.cs +157 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFailuresTests.cs +178 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFixtureTests.cs +153 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalRefreshTests.cs +194 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalWorkspace.cs +86 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/KitEvals.cs +213 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/OfflineChatClientTests.cs +95 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClient.cs +225 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClientTests.cs +215 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeClock.cs +33 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeMediaProvider.cs +89 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeProviderClientFactory.cs +55 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeUsageCostSink.cs +60 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/InMemoryMediaStore.cs +131 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAccessibilityTests.cs +133 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAliasIsNotAnAgentTests.cs +127 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaDisclosureTests.cs +216 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaGeneratorTests.cs +264 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaIdempotencyKeyTests.cs +148 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaJobStateTests.cs +184 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaReviewTests.cs +89 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaStoreTests.cs +98 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ModelRegistryTests.cs +451 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Morph.AiKit.Tests.csproj +76 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/SkeletonSmokeTests.cs +33 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/TelemetryTests.cs +261 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/UsageCostMiddlewareTests.cs +374 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/.morph-eval-cache.json +12 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/orcamento.acme.a-resposta-estruturada/1/CF70A7CE27BBD66A/contents.data +13 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/orcamento.acme.a-resposta-estruturada/1/CF70A7CE27BBD66A/entry.json +7 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.acme.a-saudacao/1/19F631228C6E00BA/contents.data +13 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.acme.a-saudacao/1/19F631228C6E00BA/entry.json +7 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.acme.b-escalonamento/1/A24A7C9BDD8239D8/contents.data +13 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.acme.b-escalonamento/1/A24A7C9BDD8239D8/entry.json +7 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.globex.a-saudacao/1/E38CD37DF003EDB7/contents.data +13 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.globex.a-saudacao/1/E38CD37DF003EDB7/entry.json +7 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-datasets/orcamento/acme/a-resposta-estruturada.json +24 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-datasets/triagem/acme/a-saudacao.json +23 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-datasets/triagem/acme/b-escalonamento.json +24 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-datasets/triagem/globex/a-saudacao.json +23 -0
- package/framework/templates/infrastructure/docker/docker-compose.template.yml +24 -14
- package/package.json +3 -3
- package/src/commands/e2e/e2e.js +91 -40
- package/src/commands/project/doctor.js +143 -6
- package/src/commands/project/env.js +95 -0
- package/src/commands/project/update.js +469 -153
- package/src/commands/project/worktree.js +274 -53
- package/src/commands/state/advance.js +24 -2
- package/src/commands/state/approve.js +33 -1
- package/src/commands/state/create.js +72 -11
- package/src/commands/state/delete.js +8 -0
- package/src/commands/state/finish.js +360 -53
- package/src/commands/state/gate-check.js +190 -6
- package/src/commands/state/score.js +36 -1
- package/src/commands/validation/verify.js +134 -25
- package/src/core/state/state-manager.js +9 -1
- package/src/dashboard/app/pages/HookInspector.jsx +430 -418
- package/src/dashboard/scanner/hooks-scanner.js +5 -1
- package/src/lib/ai/ai-checks.js +182 -0
- package/src/lib/ai/ai-project-scan.js +347 -0
- package/src/lib/ai/code-audit.js +181 -0
- package/src/lib/ai/comment-stripper.js +184 -0
- package/src/lib/ai/pin-drift.js +138 -0
- package/src/lib/ai/registry-audit.js +285 -0
- package/src/lib/e2e/compose-env.js +55 -14
- package/src/lib/e2e/stack-teardown.js +115 -0
- package/src/lib/evaluator-report.js +269 -15
- package/src/lib/generators/architecture-scanner.js +2 -1
- package/src/lib/git/default-branch.js +92 -4
- package/src/lib/graph/graphifyignore.js +2 -1
- package/src/lib/installers/effects.js +193 -0
- package/src/lib/installers/templates-reconcile.js +3 -3
- package/src/lib/risk-detector.js +284 -0
- package/src/lib/standards/verified-against.js +276 -0
- package/src/lib/tasks/eval-node.js +485 -0
- package/src/lib/tasks/linux-runner.js +297 -0
- package/src/lib/tasks/test-nodes.js +486 -0
- package/src/lib/tasks/test-runner.js +431 -37
- package/src/lib/validators/verify-runner.js +590 -28
- package/src/lib/worktree/dirs.js +32 -0
- package/src/lib/worktree/docker-audit.js +287 -0
- package/src/lib/worktree/env-allocator.js +69 -11
- package/src/lib/worktree/worktree-audit.js +70 -3
- package/src/scripts/setup-infra.js +24 -11
- package/src/utils/agents-installer.js +18 -8
- package/src/utils/claude-md-injector.js +10 -5
- package/src/utils/file-copier.js +28 -16
- package/src/utils/hooks-installer.js +75 -29
- package/src/utils/skills-installer.js +194 -187
- package/src/utils/version-checker.js +68 -3
- package/framework/standards/ai-agents/prompts-in-db.md +0 -214
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<!--
|
|
3
|
+
<clear /> e o ponto deste arquivo: sem ele o restore herda os feeds da maquina
|
|
4
|
+
(ou do runner) e "compila aqui, quebra no CI" volta a ser possivel. O kit
|
|
5
|
+
restaura de nuget.org e de mais nada.
|
|
6
|
+
-->
|
|
7
|
+
<configuration>
|
|
8
|
+
<packageSources>
|
|
9
|
+
<clear />
|
|
10
|
+
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
|
|
11
|
+
</packageSources>
|
|
12
|
+
</configuration>
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# Morph.AiKit
|
|
2
|
+
|
|
3
|
+
Solução .NET **compilável** que prova os standards `ai-agents/**` contra
|
|
4
|
+
`framework/ai-pin.json`. Não é fragmento de referência: a unidade de consumo é o
|
|
5
|
+
diretório inteiro, e é por isso que ela está registrada no `REGISTRY.json` como
|
|
6
|
+
`kind: "project"` (uma entrada, `path: "dotnet/ai-kit/"`).
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
ai-kit/
|
|
10
|
+
├── global.json # GERADO do pin — banda de SDK + runner de teste
|
|
11
|
+
├── Directory.Packages.props # GERADO do pin — Central Package Management
|
|
12
|
+
├── Directory.Build.props # TFM, Nullable, TreatWarningsAsErrors
|
|
13
|
+
├── NuGet.config # <clear/> + nuget.org: restore não herda feed de máquina
|
|
14
|
+
├── Morph.AiKit.sln
|
|
15
|
+
├── src/Morph.AiKit/ # a biblioteca
|
|
16
|
+
└── tests/Morph.AiKit.Tests/ # xUnit v3, sem rede e sem chave de API
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Build e teste
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
cd framework/templates/dotnet/ai-kit
|
|
23
|
+
dotnet restore Morph.AiKit.sln
|
|
24
|
+
dotnet build Morph.AiKit.sln -c Release --no-restore
|
|
25
|
+
dotnet test --solution Morph.AiKit.sln -c Release --no-build
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
> **Nenhuma flag da era VSTest no `dotnet test` — `--nologo` mata a sessão.**
|
|
29
|
+
> O `global.json` coloca este kit no **modo MTP** do `dotnet test` (SDK .NET 10).
|
|
30
|
+
> Nesse modo o CLI não conhece `--nologo` e o repassa **literalmente** para o
|
|
31
|
+
> aplicativo de teste, que responde `error: unknown option: --nologo`: a sessão
|
|
32
|
+
> morre com o código de saída **5** e o relatório diz *"Zero testes executados"*
|
|
33
|
+
> — com os 115 testes passando por baixo. Medido em 2026‑09‑08. O `dotnet build
|
|
34
|
+
> --nologo` é inofensivo, e é isso que torna o erro fácil de cometer. Pela mesma
|
|
35
|
+
> razão o caminho da solução vai em `--solution`, e não como argumento
|
|
36
|
+
> posicional: é a grafia documentada do modo MTP (passo 5 do guia de migração).
|
|
37
|
+
> `--logger`, `--collect` e `--blame` caem na mesma armadilha.
|
|
38
|
+
|
|
39
|
+
> **O modo MTP muda também a SAÍDA, não só a invocação.** O sumário não é uma
|
|
40
|
+
> linha: é um **bloco de 6 linhas**, o veredito **não começa** a linha
|
|
41
|
+
> (`Resumo da execução de teste: Aprovado!` / `Test run summary: Passed!`) e a
|
|
42
|
+
> contagem vem em linha própria, indentada, com **`total:` minúsculo** — nada
|
|
43
|
+
> disso é o sumário do VSTest. Medido nos dois locales em 2026‑09‑08.
|
|
44
|
+
> Consequência: **um parser escrito para o sumário do VSTest não conta os testes
|
|
45
|
+
> deste projeto** — um parser que procure uma linha *começando* em
|
|
46
|
+
> `Aprovado!`/`Passed!` **e** contendo `Total de testes:`/`Total:` falha nos dois
|
|
47
|
+
> eixos, e nos dois locales. O conserto do parser pertence à feature
|
|
48
|
+
> `verify-por-stack`; aqui isto é **registro de fronteira**, não conserto.
|
|
49
|
+
|
|
50
|
+
> **Descoberta parcial não pode passar verde.** O `.csproj` de teste declara
|
|
51
|
+
> `TestingPlatformCommandLineArguments` com o piso `--minimum-expected-tests
|
|
52
|
+
> 115`: qualquer execução que rode menos de 115 casos sai com o código **9**.
|
|
53
|
+
> Sem ele, uma execução que descobre **1 de 115** imprime *"Aprovado!"* e sai
|
|
54
|
+
> **0** — medido. O piso mora no `.csproj`, e não no passo do workflow, para
|
|
55
|
+
> valer em toda invocação (CI, máquina do dev e o nó `tests` do `morph-spec
|
|
56
|
+
> verify`); por isso nenhum chamador repassa a flag. Ao adicionar testes, suba o
|
|
57
|
+
> piso — `test/framework/ai-kit-ci-packaging.test.js` reprova um piso que ficou
|
|
58
|
+
> para trás da contagem de `[Fact]`/`[Theory]`.
|
|
59
|
+
|
|
60
|
+
> **`dotnet test` precisa rodar COM O CWD DENTRO deste diretório.** O opt-in do
|
|
61
|
+
> Microsoft.Testing.Platform (que `xunit.v3` 4.x usa) vive em `global.json`, e o
|
|
62
|
+
> SDK procura o `global.json` a partir do diretório corrente subindo — não a
|
|
63
|
+
> partir do `.sln` passado no argumento. Invocado da raiz do repositório,
|
|
64
|
+
> `dotnet test framework/templates/dotnet/ai-kit/Morph.AiKit.sln` falha com
|
|
65
|
+
> *"Testing with VSTest target is no longer supported"*. O `dotnet build` **não**
|
|
66
|
+
> tem esse problema. No CI, use `working-directory:`.
|
|
67
|
+
|
|
68
|
+
## Versões de pacote
|
|
69
|
+
|
|
70
|
+
**Nenhum `.csproj` declara `Version`.** Central Package Management está ligado, e
|
|
71
|
+
todas as versões vivem em `Directory.Packages.props`, gerado por
|
|
72
|
+
`scripts/generate-ai-pin-props.js` a partir de `framework/ai-pin.json`. Para
|
|
73
|
+
mudar uma versão, mude o pin e rode `npm run generate`; `npm run generate:check`
|
|
74
|
+
(dentro de `npm run verify`) reprova qualquer divergência. Um `PackageReference`
|
|
75
|
+
sem `PackageVersion` correspondente falha o restore com **NU1010** — é isso que
|
|
76
|
+
torna a divergência estruturalmente impossível, em vez de apenas detectável.
|
|
77
|
+
|
|
78
|
+
`bin/` e `obj/` ficam fora do git e fora do tarball npm pelo `.gitignore` deste
|
|
79
|
+
diretório. Uma regra no `.npmignore` da raiz **não** funcionaria: `package.json`
|
|
80
|
+
→ `files` lista `framework/`, e o npm não deixa um ignore da raiz excluir nada
|
|
81
|
+
sob um diretório de `files`.
|
|
82
|
+
|
|
83
|
+
## Dois níveis de garantia: compilação × contrato de versão
|
|
84
|
+
|
|
85
|
+
O pin tem **oito** pacotes; o kit **não** os prova todos da mesma forma, e a
|
|
86
|
+
diferença é material — não é detalhe de empacotamento.
|
|
87
|
+
|
|
88
|
+
| Pacote do pin | Garantia | O que reprova a PR |
|
|
89
|
+
|---|---|---|
|
|
90
|
+
| `Microsoft.Agents.AI` | **cobertura de compilação** | um rename de API: o kit tem `using` reais e o build quebra |
|
|
91
|
+
| `Microsoft.Extensions.AI` | **cobertura de compilação** | idem |
|
|
92
|
+
| `Microsoft.Extensions.AI.Evaluation` | **cobertura de compilação** | `Evals/EvalAssertion.cs` implementa `IEvaluator` |
|
|
93
|
+
| `Microsoft.Extensions.AI.Evaluation.Quality` | **cobertura de compilação** | `Evals/EvalQualityEvaluators.cs` constrói os avaliadores estáveis |
|
|
94
|
+
| `Microsoft.Extensions.AI.Evaluation.Reporting` | **cobertura de compilação** | `Evals/EvalFixture.cs` monta a `ReportingConfiguration` |
|
|
95
|
+
| `OpenAI` | **contrato de versão** | só o restore: `NU1102` se a versão sumir do nuget.org |
|
|
96
|
+
| `Google.GenAI` | **contrato de versão** | idem |
|
|
97
|
+
| `ModelContextProtocol` | **contrato de versão** | idem |
|
|
98
|
+
| `OpenAI` | **cobertura de compilação** | um rename: `Media/Providers/OpenAIImageProvider.cs` tem `using OpenAI.Images;` e usa `ImageClient`, `ImageEditOptions`, `GeneratedImageQuality` |
|
|
99
|
+
| `Google.GenAI` | **cobertura de compilação** | um rename: os dois adaptadores de `Media/Providers/` têm `using Google.GenAI.Types;` e usam `GenerateVideosSource`, `GenerateVideosOperation`, `Blob` |
|
|
100
|
+
| `ModelContextProtocol` | **contrato de versão** | só o restore: `NU1102` se a versão sumir do nuget.org |
|
|
101
|
+
|
|
102
|
+
Sem eufemismo: para **um** deles, **um rename de API não reprova a PR**. O job
|
|
103
|
+
`ai-kit` prova apenas que a versão pinada continua existindo no nuget.org — não
|
|
104
|
+
que a API dela continua a mesma. Não existe um único `using ModelContextProtocol`
|
|
105
|
+
em `src/` nem em `tests/`.
|
|
106
|
+
|
|
107
|
+
**A linha mudou, e vale registrar por quê.** Até a chegada de
|
|
108
|
+
`src/Morph.AiKit/Media/`, três dos cinco pacotes eram contrato de versão: o pin
|
|
109
|
+
não inclui `Microsoft.Extensions.AI.OpenAI`, e sem essa ponte o kit não tocava
|
|
110
|
+
tipo nenhum de `OpenAI` nem de `Google.GenAI`. O subsistema de mídia fechou a
|
|
111
|
+
lacuna **sem** acrescentar pacote: o adaptador `IImageGenerator` sobre
|
|
112
|
+
`ImageClient` está escrito à mão em `Media/Providers/OpenAIImageProvider.cs`, e o
|
|
113
|
+
caminho de vídeo do Veo usa `Google.GenAI` direto — que é o único caminho com SDK
|
|
114
|
+
.NET oficial, e por isso não havia ponte a esperar. Quem apontou a mudança foi o
|
|
115
|
+
próprio canário: `test/framework/ai-kit-package-coverage.test.js` ficou vermelho
|
|
116
|
+
nomeando os dois pacotes como entradas obsoletas de `RESTORE_ONLY`.
|
|
117
|
+
|
|
118
|
+
O `PackageReference` de `ModelContextProtocol` **fica** no `.csproj` de propósito:
|
|
119
|
+
sem alguém referenciando o pacote, o restore nunca o resolve, e aí some até a
|
|
120
|
+
régua fraca (o `NU1102`) que existe hoje. Fechar essa última lacuna é expor uma
|
|
121
|
+
superfície MCP de verdade no kit — escopo novo, decisão do dono do pin.
|
|
122
|
+
|
|
123
|
+
Essa classificação não é prosa solta: `test/framework/ai-kit-package-coverage.test.js`
|
|
124
|
+
exige que **todo** `PackageReference` do `Morph.AiKit.csproj` esteja ou
|
|
125
|
+
exercitado por um `using` real em `src/`, ou listado como restore-only na
|
|
126
|
+
constante do próprio teste. Um pacote novo que não caia em nenhum dos dois casos
|
|
127
|
+
deixa o teste vermelho nomeando o pacote — a prosa acima não pode envelhecer em
|
|
128
|
+
silêncio.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
namespace Morph.AiKit.Agents;
|
|
2
|
+
|
|
3
|
+
/// <summary>
|
|
4
|
+
/// Protocolo pelo qual um modelo e alcancado. E o campo <c>api:</c> por alias que a
|
|
5
|
+
/// issue #92 exige: a MESMA familia de modelos e atendida por dois protocolos
|
|
6
|
+
/// diferentes, e qual deles vale e decisao de configuracao — nunca folclore espalhado
|
|
7
|
+
/// por condicional no meio do codigo.
|
|
8
|
+
/// </summary>
|
|
9
|
+
/// <remarks>
|
|
10
|
+
/// <para>
|
|
11
|
+
/// <see cref="Responses"/> e <b>0 de proposito</b>: <c>default(AgentApi)</c> e a Responses
|
|
12
|
+
/// API porque essa e a decisao da casa (Responses por padrao, Chat Completions como
|
|
13
|
+
/// opt-in explicito por alias). Um alias que omite <c>api:</c> no
|
|
14
|
+
/// <c>model-registry.v2.json</c> cai em Responses; usar Chat Completions exige escrever
|
|
15
|
+
/// <c>"api": "chat"</c> — ou seja, exige alguem assumir a escolha por escrito.
|
|
16
|
+
/// </para>
|
|
17
|
+
/// <para>
|
|
18
|
+
/// O enum descreve a escolha ENTRE OS DOIS PROTOCOLOS DA OpenAI. Provider que expoe um
|
|
19
|
+
/// unico protocolo (Google, Ollama) ignora o campo, e por isso declara — ou omite,
|
|
20
|
+
/// caindo em — <see cref="Responses"/>: escrever <c>chat</c> ali sugeriria uma escolha
|
|
21
|
+
/// que naquele provider nao existe.
|
|
22
|
+
/// </para>
|
|
23
|
+
/// </remarks>
|
|
24
|
+
public enum AgentApi
|
|
25
|
+
{
|
|
26
|
+
/// <summary>Responses API — o padrao da casa. Valor 0, logo <c>default(AgentApi)</c>.</summary>
|
|
27
|
+
Responses = 0,
|
|
28
|
+
|
|
29
|
+
/// <summary>
|
|
30
|
+
/// Chat Completions — opt-in explicito por alias. Carrega limites que a Responses API
|
|
31
|
+
/// nao tem; ver <c>Morph.AiKit.Providers.ModelAliasConstraints</c>.
|
|
32
|
+
/// </summary>
|
|
33
|
+
Chat = 1,
|
|
34
|
+
|
|
35
|
+
/// <summary>
|
|
36
|
+
/// Images — geracao/edicao de imagem. <b>Nao e um protocolo de conversa</b>: um alias
|
|
37
|
+
/// com este <c>api</c> e recusado pela <c>AgentFactory</c>, com erro nomeado.
|
|
38
|
+
/// </summary>
|
|
39
|
+
/// <remarks>
|
|
40
|
+
/// Um registry so, um vocabulario so de alias: quem procura "qual modelo este projeto
|
|
41
|
+
/// usa" encontra o de imagem no mesmo lugar que o de chat. A alternativa era um
|
|
42
|
+
/// <c>media-registry.json</c> separado — duas verdades sobre o mesmo fato.
|
|
43
|
+
/// </remarks>
|
|
44
|
+
Images = 2,
|
|
45
|
+
|
|
46
|
+
/// <summary>
|
|
47
|
+
/// Videos — geracao de video. Mesma regra de <see cref="Images"/>: alias de midia nao
|
|
48
|
+
/// vira agente.
|
|
49
|
+
/// </summary>
|
|
50
|
+
Videos = 3,
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/// <summary>Predicados sobre <see cref="AgentApi"/>.</summary>
|
|
54
|
+
public static class AgentApiExtensions
|
|
55
|
+
{
|
|
56
|
+
/// <summary>
|
|
57
|
+
/// Se o protocolo e de MIDIA (imagem ou video) e portanto nao fala conversa.
|
|
58
|
+
/// </summary>
|
|
59
|
+
/// <remarks>
|
|
60
|
+
/// Escrito uma vez, aqui, porque duas copias deste predicado sao como a
|
|
61
|
+
/// <c>AgentFactory</c> e o <c>ModelRegistry</c> passariam a discordar sobre o mesmo
|
|
62
|
+
/// alias.
|
|
63
|
+
/// </remarks>
|
|
64
|
+
public static bool IsMedia(this AgentApi api) => api is AgentApi.Images or AgentApi.Videos;
|
|
65
|
+
}
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
using System.Runtime.CompilerServices;
|
|
2
|
+
using Microsoft.Agents.AI;
|
|
3
|
+
using Microsoft.Extensions.AI;
|
|
4
|
+
using Morph.AiKit.Providers;
|
|
5
|
+
|
|
6
|
+
namespace Morph.AiKit.Agents;
|
|
7
|
+
|
|
8
|
+
/// <summary>
|
|
9
|
+
/// Transforma uma <see cref="AgentSpec"/> (o agente como dado) num
|
|
10
|
+
/// <see cref="AIAgent"/> pronto para rodar. E o unico lugar do kit que conhece a ORDEM do
|
|
11
|
+
/// pipeline.
|
|
12
|
+
/// </summary>
|
|
13
|
+
/// <remarks>
|
|
14
|
+
/// <para><b>A ordem, de fora para dentro, e o contrato desta classe:</b></para>
|
|
15
|
+
/// <code>
|
|
16
|
+
/// OpenTelemetryAgent span invoke_agent — cobre a invocacao inteira (T12)
|
|
17
|
+
/// +- ChatClientAgent UseProvidedChatClientAsIs = true: o pipeline abaixo e nosso
|
|
18
|
+
/// +- timeout TimeoutSeconds vira CancellationToken
|
|
19
|
+
/// +- usage-cost ve UMA resposta com o uso ja somado das voltas (T11)
|
|
20
|
+
/// +- FunctionInvoking MaxToolIterations / AllowConcurrentToolInvocation
|
|
21
|
+
/// +- telemetria de chat span chat, ABAIXO do FICC (T12)
|
|
22
|
+
/// +- cliente do provider memoizado pelo ModelRegistry
|
|
23
|
+
/// </code>
|
|
24
|
+
/// <para>
|
|
25
|
+
/// Cada posicao foi <b>medida</b> contra o pin, nao herdada de folclore. Acima do FICC o
|
|
26
|
+
/// middleware de custo recebe o uso agregado (10/4 + 30/6 chegam como 40/10); abaixo dele
|
|
27
|
+
/// receberia uma resposta por volta, e ainda leria um <c>UsageDetails</c> que o FICC muta
|
|
28
|
+
/// para acumular. A telemetria de chat fica abaixo do FICC porque o MAF 1.20.0 escreve, na
|
|
29
|
+
/// documentacao do proprio slot que ele reserva, que so assim o span de chat fecha antes
|
|
30
|
+
/// das tools e o <c>invoke_agent</c> vira raiz.
|
|
31
|
+
/// </para>
|
|
32
|
+
/// <para>
|
|
33
|
+
/// <b>Por que <c>UseProvidedChatClientAsIs = true</c>:</b> com o default, o
|
|
34
|
+
/// <c>ChatClientAgent</c> monta ele mesmo o FICC — e nao ha onde ajustar
|
|
35
|
+
/// <c>MaximumIterationsPerRequest</c> nem onde encaixar o custo entre o FICC e o cliente.
|
|
36
|
+
/// O preco medido dessa escolha e que o slot de telemetria que o MAF reservaria deixa de
|
|
37
|
+
/// existir: sem a T12 ligada, um agente desta fabrica emite <c>invoke_agent</c> e
|
|
38
|
+
/// <b>nenhum</b> span de chat. E por isso que a telemetria de chat e um encaixe nomeado, e
|
|
39
|
+
/// nao um detalhe opcional.
|
|
40
|
+
/// </para>
|
|
41
|
+
/// </remarks>
|
|
42
|
+
public sealed class AgentFactory
|
|
43
|
+
{
|
|
44
|
+
private readonly ModelRegistry _registry;
|
|
45
|
+
private readonly AgentFactoryOptions _options;
|
|
46
|
+
|
|
47
|
+
/// <summary>Cria a fabrica sobre um registry ja carregado.</summary>
|
|
48
|
+
/// <param name="registry">Fonte unica de alias, protocolo e tarifa.</param>
|
|
49
|
+
/// <param name="options">Infraestrutura e encaixes. Nulo = nenhum encaixe preenchido.</param>
|
|
50
|
+
public AgentFactory(ModelRegistry registry, AgentFactoryOptions? options = null)
|
|
51
|
+
{
|
|
52
|
+
ArgumentNullException.ThrowIfNull(registry);
|
|
53
|
+
|
|
54
|
+
_registry = registry;
|
|
55
|
+
_options = options ?? new AgentFactoryOptions();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/// <summary>
|
|
59
|
+
/// Materializa a spec. Alias desconhecido, limite de alias violado ou troca de
|
|
60
|
+
/// protocolo sem seletor sao recusas <b>na construcao</b> — nunca um agente meio certo.
|
|
61
|
+
/// </summary>
|
|
62
|
+
/// <remarks>
|
|
63
|
+
/// Recusar aqui e falha de composicao, nao excecao como fluxo de controle: acontece uma
|
|
64
|
+
/// vez, na montagem, e seguir adiante seria seguir sem regua. Quem quiser inspecionar
|
|
65
|
+
/// antes chama <c>registry.Resolve(alias).CheckRequest(...)</c>, que e Result.
|
|
66
|
+
/// </remarks>
|
|
67
|
+
public AIAgent Create(AgentSpec spec)
|
|
68
|
+
{
|
|
69
|
+
ArgumentNullException.ThrowIfNull(spec);
|
|
70
|
+
|
|
71
|
+
var alias = _registry.Resolve(spec.Alias);
|
|
72
|
+
var api = spec.ResolveApi(alias.Api);
|
|
73
|
+
|
|
74
|
+
// Alias de MIDIA nao vira agente, e a recusa e aqui, na construcao. Sem ela, um
|
|
75
|
+
// alias de imagem passado por engano viraria um agente de chat apontando para um
|
|
76
|
+
// modelo que nao fala chat: a falha apareceria tarde, no primeiro turno, com uma
|
|
77
|
+
// mensagem do provider que nao diz o que aconteceu. E a companheira obrigatoria de
|
|
78
|
+
// AgentApi.Images/Videos existirem — enum novo sem recusa e uma armadilha nova.
|
|
79
|
+
if (alias.Api.IsMedia() || api.IsMedia())
|
|
80
|
+
{
|
|
81
|
+
throw AgentFactoryException.MediaAliasIsNotAnAgent(spec.Alias, alias.Api.IsMedia() ? alias.Api : api);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
var check = alias.CheckRequest(
|
|
85
|
+
api,
|
|
86
|
+
spec.Tools.Count > 0,
|
|
87
|
+
spec.Options.ReasoningEffort,
|
|
88
|
+
spec.Options.Temperature is not null);
|
|
89
|
+
|
|
90
|
+
if (!check.IsSatisfied)
|
|
91
|
+
{
|
|
92
|
+
throw AgentFactoryException.ConstraintViolated(spec.Alias, check.Violations);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
var agentOptions = new ChatClientAgentOptions
|
|
96
|
+
{
|
|
97
|
+
Id = Guid.CreateVersion7().ToString("N"),
|
|
98
|
+
Name = spec.Name,
|
|
99
|
+
Description = spec.Description,
|
|
100
|
+
ChatOptions = BuildChatOptions(spec, alias),
|
|
101
|
+
UseProvidedChatClientAsIs = true,
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
AIAgent agent = new ChatClientAgent(
|
|
105
|
+
BuildChatPipeline(ResolveChatClient(alias, api), spec, alias),
|
|
106
|
+
agentOptions,
|
|
107
|
+
_options.LoggerFactory,
|
|
108
|
+
_options.ServiceProvider);
|
|
109
|
+
|
|
110
|
+
return _options.ConfigureAgentTelemetry is { } configureAgent
|
|
111
|
+
? configureAgent(agent.AsBuilder(), spec, alias).Build(_options.ServiceProvider)
|
|
112
|
+
: agent;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/// <summary>
|
|
116
|
+
/// As opcoes de requisicao do agente. <c>ModelId</c> vem do alias — a spec nunca cita
|
|
117
|
+
/// modelo. <c>Tools</c> e <c>ResponseFormat</c> entram antes de
|
|
118
|
+
/// <see cref="AgentSpecOptions.ApplyTo"/>, que por contrato nao sobrescreve o que ja
|
|
119
|
+
/// foi definido.
|
|
120
|
+
/// </summary>
|
|
121
|
+
private static ChatOptions BuildChatOptions(AgentSpec spec, ModelAlias alias)
|
|
122
|
+
{
|
|
123
|
+
var options = new ChatOptions
|
|
124
|
+
{
|
|
125
|
+
Instructions = spec.Instructions,
|
|
126
|
+
ModelId = alias.Model,
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
if (spec.Tools.Count > 0)
|
|
130
|
+
{
|
|
131
|
+
options.Tools = [.. spec.Tools];
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (spec.OutputSchema is { } schema)
|
|
135
|
+
{
|
|
136
|
+
options.ResponseFormat = ChatResponseFormat.ForJsonSchema(schema, spec.Name, spec.Description);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return spec.Options.ApplyTo(options);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
private IChatClient ResolveChatClient(ModelAlias alias, AgentApi api)
|
|
143
|
+
{
|
|
144
|
+
if (_options.ChatClientSelector is { } selector)
|
|
145
|
+
{
|
|
146
|
+
return selector(alias, api) ?? throw AgentFactoryException.NoChatClient(alias.Name, api);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return api == alias.Api
|
|
150
|
+
? _registry.GetChatClient(alias.Name)
|
|
151
|
+
: throw AgentFactoryException.ProtocolWithoutSelector(alias.Name, alias.Api, api);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private IChatClient BuildChatPipeline(IChatClient leaf, AgentSpec spec, ModelAlias alias)
|
|
155
|
+
{
|
|
156
|
+
var builder = leaf.AsBuilder();
|
|
157
|
+
|
|
158
|
+
if (spec.Options.TimeoutSeconds is { } seconds)
|
|
159
|
+
{
|
|
160
|
+
builder = builder.Use(inner => new TimeoutChatClient(inner, TimeSpan.FromSeconds(seconds)));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (_options.ConfigureUsageCost is { } configureUsageCost)
|
|
164
|
+
{
|
|
165
|
+
builder = configureUsageCost(builder, spec, alias);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
builder = builder.UseFunctionInvocation(_options.LoggerFactory, functionInvoking =>
|
|
169
|
+
{
|
|
170
|
+
if (spec.Options.MaxToolIterations is { } maxIterations)
|
|
171
|
+
{
|
|
172
|
+
functionInvoking.MaximumIterationsPerRequest = maxIterations;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (spec.Options.AllowConcurrentToolInvocation is { } allowConcurrent)
|
|
176
|
+
{
|
|
177
|
+
functionInvoking.AllowConcurrentInvocation = allowConcurrent;
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
if (_options.ConfigureChatTelemetry is { } configureChatTelemetry)
|
|
182
|
+
{
|
|
183
|
+
builder = configureChatTelemetry(builder, spec, alias);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return builder.Build(_options.ServiceProvider);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/// <summary>
|
|
190
|
+
/// Realiza <see cref="AgentSpecOptions.TimeoutSeconds"/>. Vive aqui, privado, porque e
|
|
191
|
+
/// a traducao de UMA opcao para o <see cref="CancellationToken"/> que o pipeline ja
|
|
192
|
+
/// carrega — e nao um conceito que alguem deva compor por fora.
|
|
193
|
+
/// </summary>
|
|
194
|
+
private sealed class TimeoutChatClient(IChatClient inner, TimeSpan timeout) : DelegatingChatClient(inner)
|
|
195
|
+
{
|
|
196
|
+
public override async Task<ChatResponse> GetResponseAsync(
|
|
197
|
+
IEnumerable<ChatMessage> messages,
|
|
198
|
+
ChatOptions? options = null,
|
|
199
|
+
CancellationToken cancellationToken = default)
|
|
200
|
+
{
|
|
201
|
+
using var timed = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
|
202
|
+
timed.CancelAfter(timeout);
|
|
203
|
+
|
|
204
|
+
return await base.GetResponseAsync(messages, options, timed.Token).ConfigureAwait(false);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
public override async IAsyncEnumerable<ChatResponseUpdate> GetStreamingResponseAsync(
|
|
208
|
+
IEnumerable<ChatMessage> messages,
|
|
209
|
+
ChatOptions? options = null,
|
|
210
|
+
[EnumeratorCancellation] CancellationToken cancellationToken = default)
|
|
211
|
+
{
|
|
212
|
+
using var timed = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
|
213
|
+
timed.CancelAfter(timeout);
|
|
214
|
+
|
|
215
|
+
await foreach (var update in base
|
|
216
|
+
.GetStreamingResponseAsync(messages, options, timed.Token)
|
|
217
|
+
.ConfigureAwait(false))
|
|
218
|
+
{
|
|
219
|
+
yield return update;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/// <summary>Motivo de uma recusa da <see cref="AgentFactory"/>.</summary>
|
|
226
|
+
public enum AgentFactoryError
|
|
227
|
+
{
|
|
228
|
+
/// <summary>A requisicao viola um limite declarado no alias, com procedencia.</summary>
|
|
229
|
+
ConstraintViolated = 0,
|
|
230
|
+
|
|
231
|
+
/// <summary>A spec pede um protocolo diferente do alias e nao ha seletor de cliente.</summary>
|
|
232
|
+
ProtocolWithoutSelector = 1,
|
|
233
|
+
|
|
234
|
+
/// <summary>O seletor de cliente devolveu nulo.</summary>
|
|
235
|
+
NoChatClient = 2,
|
|
236
|
+
|
|
237
|
+
/// <summary>O alias e de midia (<c>api: images</c> ou <c>api: videos</c>) e nao fala conversa.</summary>
|
|
238
|
+
MediaAliasIsNotAnAgent = 3,
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/// <summary>
|
|
242
|
+
/// Recusa de composicao da <see cref="AgentFactory"/>. <c>sealed</c> e discriminada por
|
|
243
|
+
/// <see cref="Reason"/>: quem trata precisa do motivo, nao de herdar do tipo.
|
|
244
|
+
/// </summary>
|
|
245
|
+
public sealed class AgentFactoryException : InvalidOperationException
|
|
246
|
+
{
|
|
247
|
+
private AgentFactoryException(AgentFactoryError reason, string message)
|
|
248
|
+
: base(message)
|
|
249
|
+
=> Reason = reason;
|
|
250
|
+
|
|
251
|
+
/// <summary>Por que a fabrica recusou.</summary>
|
|
252
|
+
public AgentFactoryError Reason { get; }
|
|
253
|
+
|
|
254
|
+
/// <summary>A requisicao viola limites do alias. A mensagem traz cada violacao com sua procedencia.</summary>
|
|
255
|
+
public static AgentFactoryException ConstraintViolated(string alias, IReadOnlyList<string> violations)
|
|
256
|
+
=> new(
|
|
257
|
+
AgentFactoryError.ConstraintViolated,
|
|
258
|
+
$"alias '{alias}': a spec viola limite(s) declarado(s) do alias e o agente NAO foi criado. " +
|
|
259
|
+
string.Join(" | ", violations));
|
|
260
|
+
|
|
261
|
+
/// <summary>Divergencia de protocolo sem seletor que saiba construir o outro cliente.</summary>
|
|
262
|
+
public static AgentFactoryException ProtocolWithoutSelector(string alias, AgentApi aliasApi, AgentApi specApi)
|
|
263
|
+
=> new(
|
|
264
|
+
AgentFactoryError.ProtocolWithoutSelector,
|
|
265
|
+
$"alias '{alias}': a spec pede o protocolo '{specApi}' e o alias foi construido para " +
|
|
266
|
+
$"'{aliasApi}'. O ModelRegistry memoiza UM cliente por alias, entao devolver o cliente " +
|
|
267
|
+
"memoizado seria entregar um agente que diz falar um protocolo e fala outro. Declare um " +
|
|
268
|
+
"alias com o api pedido no model-registry.v2.json, ou preencha " +
|
|
269
|
+
$"{nameof(AgentFactoryOptions)}.{nameof(AgentFactoryOptions.ChatClientSelector)}.");
|
|
270
|
+
|
|
271
|
+
/// <summary>
|
|
272
|
+
/// O alias e de midia. Recusa nomeada, na construcao — nunca um agente que aponta para
|
|
273
|
+
/// um modelo que nao fala chat.
|
|
274
|
+
/// </summary>
|
|
275
|
+
public static AgentFactoryException MediaAliasIsNotAnAgent(string alias, AgentApi api)
|
|
276
|
+
=> new(
|
|
277
|
+
AgentFactoryError.MediaAliasIsNotAnAgent,
|
|
278
|
+
$"alias '{alias}': api '{api}' e um protocolo de MIDIA, nao de conversa, e um agente NAO foi " +
|
|
279
|
+
"criado. Geracao de midia nao passa por AIAgent: o ponto de entrada e " +
|
|
280
|
+
$"{nameof(Morph.AiKit.Media.IMediaGenerator)}.{nameof(Morph.AiKit.Media.IMediaGenerator.EnqueueAsync)}, " +
|
|
281
|
+
"que devolve um id de job e nunca bytes. Ver ai-agents/media-pipeline.md.");
|
|
282
|
+
|
|
283
|
+
/// <summary>O seletor devolveu nulo — nao ha cliente para o par alias/protocolo.</summary>
|
|
284
|
+
public static AgentFactoryException NoChatClient(string alias, AgentApi api)
|
|
285
|
+
=> new(
|
|
286
|
+
AgentFactoryError.NoChatClient,
|
|
287
|
+
$"alias '{alias}': {nameof(AgentFactoryOptions.ChatClientSelector)} devolveu nulo para o " +
|
|
288
|
+
$"protocolo '{api}'. Um cliente ausente e recusa explicita, nunca um agente sem cliente.");
|
|
289
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
using Microsoft.Agents.AI;
|
|
2
|
+
using Microsoft.Extensions.AI;
|
|
3
|
+
using Microsoft.Extensions.Logging;
|
|
4
|
+
using Morph.AiKit.Providers;
|
|
5
|
+
|
|
6
|
+
namespace Morph.AiKit.Agents;
|
|
7
|
+
|
|
8
|
+
/// <summary>
|
|
9
|
+
/// O que a <see cref="AgentFactory"/> precisa saber que NAO esta na
|
|
10
|
+
/// <see cref="AgentSpec"/>: infraestrutura do processo (log, DI) e os tres encaixes do
|
|
11
|
+
/// pipeline que outras tasks preenchem.
|
|
12
|
+
/// </summary>
|
|
13
|
+
/// <remarks>
|
|
14
|
+
/// <para>
|
|
15
|
+
/// <b>Por que encaixes e nao dependencias diretas:</b> a ORDEM do pipeline e do kit — ela
|
|
16
|
+
/// esta escrita, medida e testada dentro da <see cref="AgentFactory"/>. O CONTEUDO de cada
|
|
17
|
+
/// posicao e de quem tem o tipo: o middleware de custo mora na T11 e a telemetria na T12.
|
|
18
|
+
/// Se a fabrica referenciasse os dois diretamente, ou ela nasceria depois deles, ou eles
|
|
19
|
+
/// nasceriam dentro dela. Com encaixe nomeado, a fabrica continua dona do lugar e cada
|
|
20
|
+
/// task continua dona do que ocupa o lugar.
|
|
21
|
+
/// </para>
|
|
22
|
+
/// <para>
|
|
23
|
+
/// Encaixe nulo = posicao vazia. Um agente sem custo e sem telemetria e um agente valido,
|
|
24
|
+
/// nao um agente pela metade.
|
|
25
|
+
/// </para>
|
|
26
|
+
/// </remarks>
|
|
27
|
+
public sealed record AgentFactoryOptions
|
|
28
|
+
{
|
|
29
|
+
/// <summary>
|
|
30
|
+
/// Fabrica de log repassada ao <c>FunctionInvokingChatClient</c> e ao
|
|
31
|
+
/// <c>ChatClientAgent</c>. Nula = sem log, que e o default do MAF.
|
|
32
|
+
/// </summary>
|
|
33
|
+
public ILoggerFactory? LoggerFactory { get; init; }
|
|
34
|
+
|
|
35
|
+
/// <summary>
|
|
36
|
+
/// Container usado para resolver servicos ao montar os pipelines. Nulo = os builders
|
|
37
|
+
/// do <c>Microsoft.Extensions.AI</c> usam o provider vazio deles.
|
|
38
|
+
/// </summary>
|
|
39
|
+
public IServiceProvider? ServiceProvider { get; init; }
|
|
40
|
+
|
|
41
|
+
/// <summary>
|
|
42
|
+
/// De onde sai o <see cref="IChatClient"/> quando o protocolo efetivo NAO e o do alias.
|
|
43
|
+
/// </summary>
|
|
44
|
+
/// <remarks>
|
|
45
|
+
/// <para>
|
|
46
|
+
/// O <c>ModelRegistry</c> memoiza <b>um</b> cliente por alias, construido pela
|
|
47
|
+
/// <c>IProviderClientFactory</c> a partir do <c>api:</c> daquele alias. Logo, uma spec
|
|
48
|
+
/// que pede Chat Completions num alias declarado <c>responses</c> nao pode ser servida
|
|
49
|
+
/// pelo cliente memoizado — ele fala o outro protocolo. Sem este seletor a fabrica
|
|
50
|
+
/// <b>recusa</b> a combinacao, nomeando os dois protocolos, em vez de devolver um agente
|
|
51
|
+
/// que diz falar um protocolo e fala outro.
|
|
52
|
+
/// </para>
|
|
53
|
+
/// <para>
|
|
54
|
+
/// Nulo (o normal) = todo agente usa o cliente memoizado do alias, e
|
|
55
|
+
/// <see cref="AgentSpec.Api"/> divergente vira recusa. Preenchido, recebe o alias e o
|
|
56
|
+
/// protocolo <b>efetivo</b> (ja resolvido por <see cref="AgentSpec.ResolveApi"/>) e
|
|
57
|
+
/// devolve o cliente correspondente.
|
|
58
|
+
/// </para>
|
|
59
|
+
/// </remarks>
|
|
60
|
+
public Func<ModelAlias, AgentApi, IChatClient>? ChatClientSelector { get; init; }
|
|
61
|
+
|
|
62
|
+
/// <summary>
|
|
63
|
+
/// Encaixe do middleware de custo (T11), <b>acima</b> do <c>FunctionInvokingChatClient</c>.
|
|
64
|
+
/// Preencha com <c>(b, _, alias) => b.UseUsageCost(alias, sink)</c>.
|
|
65
|
+
/// </summary>
|
|
66
|
+
/// <remarks>
|
|
67
|
+
/// A posicao e medida, nao opinada: acima do FICC o middleware ve <b>uma</b> resposta
|
|
68
|
+
/// com o uso ja somado de todas as voltas de tool (medido: 40/10 num roteiro de duas
|
|
69
|
+
/// voltas de 10/4 e 30/6); abaixo do FICC ele ve uma resposta por volta. Pior, o FICC
|
|
70
|
+
/// <b>acumula no proprio objeto</b> <c>UsageDetails</c> da primeira volta — um sink que
|
|
71
|
+
/// guardasse a referencia abaixo do FICC leria valores que mudaram depois.
|
|
72
|
+
/// </remarks>
|
|
73
|
+
public Func<ChatClientBuilder, AgentSpec, ModelAlias, ChatClientBuilder>? ConfigureUsageCost { get; init; }
|
|
74
|
+
|
|
75
|
+
/// <summary>
|
|
76
|
+
/// Encaixe da telemetria de chat (T12), <b>abaixo</b> do <c>FunctionInvokingChatClient</c>
|
|
77
|
+
/// e imediatamente acima do cliente do provider.
|
|
78
|
+
/// </summary>
|
|
79
|
+
/// <remarks>
|
|
80
|
+
/// A posicao e exigencia escrita do proprio MAF 1.20.0: com a instrumentacao abaixo do
|
|
81
|
+
/// FICC o span de chat fecha <b>antes</b> de as tools rodarem, o que deixa o
|
|
82
|
+
/// <c>invoke_agent</c> como span raiz e permite ao FICC emitir <c>execute_tool</c>.
|
|
83
|
+
/// </remarks>
|
|
84
|
+
public Func<ChatClientBuilder, AgentSpec, ModelAlias, ChatClientBuilder>? ConfigureChatTelemetry { get; init; }
|
|
85
|
+
|
|
86
|
+
/// <summary>
|
|
87
|
+
/// Encaixe da telemetria de agente (T12) — o span mais externo, que cobre a invocacao
|
|
88
|
+
/// inteira. Preencha com <c>(b, _, _) => b.UseMorphTelemetry(telemetryOptions)</c>.
|
|
89
|
+
/// </summary>
|
|
90
|
+
public Func<AIAgentBuilder, AgentSpec, ModelAlias, AIAgentBuilder>? ConfigureAgentTelemetry { get; init; }
|
|
91
|
+
}
|