@polymorphism-tech/morph-spec 8.38.0 → 8.40.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +19 -8
- package/README.md +2 -2
- package/bin/morph-spec.js +60 -43
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +3 -2
- package/framework/MORPH.md +1 -1
- package/framework/agents.json +36 -25
- package/framework/docs/CLI.md +2 -2
- package/framework/evals/rubrics/code-quality.md +4 -4
- package/framework/evals/rubrics/condensed.md +2 -2
- package/framework/mcp/registry.json +33 -2
- package/framework/rules/infrastructure-standards.md +0 -1
- package/framework/rules/morph-workflow.md +9 -4
- package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
- package/framework/skills/core/morph-apply/SKILL.md +5 -1
- package/framework/skills/core/morph-brainstorming/SKILL.md +1 -1
- package/framework/skills/core/morph-eval/SKILL.md +1 -1
- package/framework/skills/core/morph-hotfix/SKILL.md +1 -1
- package/framework/skills/core/morph-implement/SKILL.md +6 -1
- package/framework/skills/core/morph-media/SKILL.md +1 -1
- package/framework/skills/core/morph-plan/SKILL.md +4 -3
- package/framework/skills/core/morph-proposal/SKILL.md +1 -1
- package/framework/skills/core/morph-review/SKILL.md +1 -1
- package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
- package/framework/skills/core/morph-standards/SKILL.md +1 -1
- package/framework/skills/core/morph-uiux/SKILL.md +6 -5
- package/framework/standards/STANDARDS.json +3 -3
- package/framework/standards/ai-agents/context-providers.md +16 -26
- package/framework/standards/ai-agents/durable-workflows-hitl.md +15 -21
- package/framework/standards/ai-agents/modalities-image-gen.md +21 -25
- package/framework/standards/ai-agents/modalities-speech-to-text.md +4 -15
- package/framework/standards/ai-agents/modalities-text-to-speech.md +2 -11
- package/framework/standards/ai-agents/modalities-vision.md +1 -18
- package/framework/standards/ai-agents/prompt-sources.md +0 -4
- package/framework/standards/architecture/code-comments.md +52 -16
- package/framework/standards/backend/integrations/neon-auth/neon-auth.md +63 -50
- package/framework/standards/frontend/design-system/ai-image-generation.md +5 -5
- package/framework/standards/integration/mcp/mcp-tools.md +48 -117
- package/framework/templates/code/dotnet/abstractions/result.cs +0 -2
- package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +26 -39
- package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +28 -68
- package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +99 -117
- package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +2 -44
- package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +40 -61
- package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +54 -107
- package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +53 -58
- package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +192 -281
- package/framework/templates/code/dotnet/database/migration.cs +49 -83
- package/framework/templates/code/dotnet/database/vector-entity.cs +2 -46
- package/framework/templates/code/dotnet/extensions/result-extensions.cs +0 -2
- package/framework/templates/code/dotnet/feature/validator.cs.hbs +11 -15
- package/framework/templates/code/dotnet/jobs/agent.cs +3 -51
- package/framework/templates/code/dotnet/jobs/job.cs +2 -97
- package/framework/templates/code/dotnet/test.cs +2 -87
- package/framework/templates/code/typescript/contracts.ts +140 -167
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentApi.cs +1 -48
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactory.cs +3 -79
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactoryOptions.cs +1 -70
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpec.cs +1 -47
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpecOptions.cs +2 -99
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/AiKitAssemblyMarker.cs +0 -6
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/ConversationStore.cs +3 -88
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/IConversationStore.cs +1 -41
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/InMemoryConversationStore.cs +0 -21
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/CountingChatClient.cs +1 -25
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalAssertion.cs +2 -63
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheMissException.cs +0 -30
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheOptions.cs +1 -52
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheStamp.cs +3 -78
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalFixture.cs +4 -109
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalMode.cs +0 -22
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalQualityEvaluators.cs +1 -36
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalScenario.cs +2 -57
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalSummary.cs +0 -75
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/OfflineChatClient.cs +2 -59
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/ShortPathResponseCache.cs +4 -111
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaGenerator.cs +0 -34
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaProvider.cs +1 -57
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaArtifact.cs +0 -19
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaDisclosure.cs +2 -74
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaGenerator.cs +2 -33
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaIdempotencyKey.cs +1 -43
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJob.cs +1 -96
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobId.cs +0 -13
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobState.cs +0 -27
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaPricing.cs +0 -33
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaProvenance.cs +0 -31
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaRequest.cs +3 -75
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaResult.cs +0 -42
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaReview.cs +0 -39
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaSeams.cs +1 -55
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaStore.cs +3 -59
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MorphMedia.cs +0 -46
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/GoogleImageProvider.cs +3 -28
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/OpenAIImageProvider.cs +7 -61
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/VeoVideoProvider.cs +2 -37
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/IUsageCostSink.cs +1 -23
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostMiddleware.cs +2 -82
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostRecord.cs +1 -68
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphOpenTelemetryExtensions.cs +1 -62
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphTelemetryOptions.cs +2 -62
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/IProviderClientFactory.cs +1 -25
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelAlias.cs +0 -124
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelRegistry.cs +1 -73
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaIngest.cs +1 -66
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaPlan.cs +0 -41
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentFactoryTests.cs +2 -112
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentSpecTests.cs +2 -43
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ConversationStoreTests.cs +5 -110
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/DeltaIngestTests.cs +7 -103
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalAssertionTests.cs +1 -42
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheMissTests.cs +1 -30
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheStampTests.cs +1 -19
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFailuresTests.cs +0 -48
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFixtureTests.cs +1 -29
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalRefreshTests.cs +2 -38
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalWorkspace.cs +1 -14
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/KitEvals.cs +4 -69
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/OfflineChatClientTests.cs +1 -10
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClient.cs +1 -57
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClientTests.cs +0 -26
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeClock.cs +1 -19
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeMediaProvider.cs +0 -32
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeProviderClientFactory.cs +0 -14
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeUsageCostSink.cs +0 -24
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/InMemoryMediaStore.cs +0 -32
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAccessibilityTests.cs +0 -37
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAliasIsNotAnAgentTests.cs +0 -24
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaDisclosureTests.cs +0 -78
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaGeneratorTests.cs +0 -53
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaIdempotencyKeyTests.cs +0 -44
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaJobStateTests.cs +0 -44
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaReviewTests.cs +0 -19
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaStoreTests.cs +0 -24
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ModelRegistryTests.cs +6 -48
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/SkeletonSmokeTests.cs +1 -13
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/TelemetryTests.cs +2 -71
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/UsageCostMiddlewareTests.cs +5 -100
- package/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +39 -43
- package/framework/templates/frontend/nextjs/env.mjs.hbs +29 -32
- package/framework/templates/frontend/nextjs/page.tsx.hbs +21 -22
- package/framework/templates/frontend/nextjs/use-feature.ts.hbs +42 -54
- package/framework/templates/integrations/asaas-client.cs +332 -387
- package/framework/templates/integrations/asaas-webhook.cs +290 -351
- package/framework/templates/integrations/neon-auth-config.cs +9 -105
- package/framework/templates/project/validate.js +97 -120
- package/package.json +2 -2
- package/src/commands/dashboard/dashboard.js +80 -53
- package/src/commands/mcp/mcp-setup.js +37 -9
- package/src/commands/project/doctor.js +3 -5
- package/src/commands/project/init.js +6 -3
- package/src/commands/project/install-plugin-cmd.js +1 -1
- package/src/commands/project/update.js +5 -0
- package/src/dashboard/app/App.jsx +80 -16
- package/src/dashboard/app/components/ActivityFeed.jsx +172 -0
- package/src/dashboard/app/components/CommandPalette.jsx +155 -0
- package/src/dashboard/app/components/DetailPanel.jsx +47 -11
- package/src/dashboard/app/components/EntityLink.jsx +55 -0
- package/src/dashboard/app/components/ErrorBoundary.jsx +54 -0
- package/src/dashboard/app/components/FlowGraph.jsx +19 -17
- package/src/dashboard/app/components/KnowledgeMarkdown.jsx +53 -0
- package/src/dashboard/app/components/Markdown.jsx +47 -0
- package/src/dashboard/app/components/PhaseRail.jsx +111 -0
- package/src/dashboard/app/components/SearchFilter.jsx +40 -22
- package/src/dashboard/app/components/Sidebar.jsx +146 -37
- package/src/dashboard/app/hooks/useMorphQuery.js +54 -0
- package/src/dashboard/app/hooks/usePref.js +38 -0
- package/src/dashboard/app/lib/format.js +69 -0
- package/src/dashboard/app/lib/phases.js +56 -0
- package/src/dashboard/app/lib/project.js +50 -0
- package/src/dashboard/app/pages/Activity.jsx +24 -0
- package/src/dashboard/app/pages/AgentMap.jsx +371 -370
- package/src/dashboard/app/pages/FeatureDetail.jsx +604 -0
- package/src/dashboard/app/pages/Features.jsx +400 -0
- package/src/dashboard/app/pages/Guide.jsx +146 -0
- package/src/dashboard/app/pages/HookInspector.jsx +357 -322
- package/src/dashboard/app/pages/PhasePipeline.jsx +277 -408
- package/src/dashboard/app/pages/Portfolio.jsx +156 -0
- package/src/dashboard/app/pages/Rubrics.jsx +101 -0
- package/src/dashboard/app/pages/RulesMap.jsx +300 -273
- package/src/dashboard/app/pages/SkillsLibrary.jsx +483 -366
- package/src/dashboard/app/pages/Standards.jsx +407 -332
- package/src/dashboard/app/styles/index.css +187 -47
- package/src/dashboard/index.html +14 -12
- package/src/dashboard/package.json +32 -24
- package/src/dashboard/scanner/activity-scanner.js +351 -0
- package/src/dashboard/scanner/agents-scanner.js +59 -1
- package/src/dashboard/scanner/docs-scanner.js +59 -0
- package/src/dashboard/scanner/feature-status.js +10 -0
- package/src/dashboard/scanner/features-scanner.js +360 -0
- package/src/dashboard/scanner/index.js +14 -7
- package/src/dashboard/scanner/phases-scanner.js +14 -15
- package/src/dashboard/scanner/portfolio.js +153 -0
- package/src/dashboard/scanner/project-api.js +195 -0
- package/src/dashboard/scanner/rules-scanner.js +42 -37
- package/src/dashboard/scanner/skills-scanner.js +105 -43
- package/src/dashboard/scanner/standards-scanner.js +2 -0
- package/src/dashboard/vite-plugin-morph-data.js +134 -17
- package/src/lib/detectors/claude-config-detector.js +2 -7
- package/src/lib/detectors/index.js +5 -1
- package/src/lib/evaluator-report.js +22 -0
- package/src/lib/generators/settings-generator.js +208 -209
- package/src/lib/installers/mcp-installer.js +724 -719
- package/src/lib/installers/memory-installer.js +1 -2
- package/src/lib/projects/registry.js +154 -0
- package/src/lib/validators/code-budget/code-budget-validator.js +32 -4
- package/src/scripts/install-plugin.js +1 -1
- package/src/scripts/setup-infra.js +2 -4
- package/src/utils/agents-installer.js +57 -12
- package/src/utils/banner.js +1 -1
- package/src/commands/index.js +0 -13
- package/src/commands/project/checkpoint.js +0 -209
- package/src/core/state/phase-state-machine.js +0 -197
- package/src/dashboard/app/hooks/useDetailPanel.js +0 -15
- package/src/dashboard/app/pages/LiveState.jsx +0 -206
- package/src/dashboard/scanner/squads-scanner.js +0 -13
- package/src/dashboard/scanner/state-scanner.js +0 -85
- package/src/lib/generators/index.js +0 -10
- package/src/lib/hooks/hook-executor.js +0 -167
- package/src/lib/validators/checkpoint-persistence.js +0 -39
- package/src/lib/validators/shared/emit-validator-dispatch.js +0 -14
- package/src/lib/validators/shared/index.js +0 -12
- package/src/lib/validators/shared/issue-counter.js +0 -14
- package/src/lib/validators/shared/result-formatter.js +0 -124
package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/GoogleImageProvider.cs
CHANGED
|
@@ -2,24 +2,7 @@ using Google.GenAI.Types;
|
|
|
2
2
|
|
|
3
3
|
namespace Morph.AiKit.Media.Providers;
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
/// Adaptador de imagem do Google (familia Nano Banana). <b><c>internal</c></b> — ver
|
|
7
|
-
/// <see cref="IMediaProvider"/>.
|
|
8
|
-
/// </summary>
|
|
9
|
-
/// <remarks>
|
|
10
|
-
/// <para>
|
|
11
|
-
/// <b>Nao ha adaptador MEAI oficial para o Google</b> (medido em 2026-09-08: o unico
|
|
12
|
-
/// <c>AsIImageGenerator()</c> publicado e o de OpenAI/Azure OpenAI). O caminho e
|
|
13
|
-
/// <c>Google.GenAI</c> direto — <c>Models.GenerateContentAsync</c> com
|
|
14
|
-
/// <c>ResponseModalities</c> incluindo <c>"IMAGE"</c>, lendo os bytes de
|
|
15
|
-
/// <c>Candidates[0].Content.Parts[..].InlineData</c>.
|
|
16
|
-
/// </para>
|
|
17
|
-
/// <para>
|
|
18
|
-
/// <c>GenerateImagesAsync</c> do SDK <b>nao serve</b>: ela e a API do Imagen, cuja familia
|
|
19
|
-
/// <c>imagen-4.0-*</c> esta na tabela de deprecacoes de
|
|
20
|
-
/// <c>ai-agents/modalities-image-gen.md</c>.
|
|
21
|
-
/// </para>
|
|
22
|
-
/// </remarks>
|
|
5
|
+
// GenerateImagesAsync e a API do Imagen (familia deprecada); Nano Banana vai por GenerateContentAsync.
|
|
23
6
|
internal sealed class GoogleImageProvider : IMediaProvider
|
|
24
7
|
{
|
|
25
8
|
private readonly IMediaClientFactory _clients;
|
|
@@ -30,13 +13,10 @@ internal sealed class GoogleImageProvider : IMediaProvider
|
|
|
30
13
|
_clients = clients;
|
|
31
14
|
}
|
|
32
15
|
|
|
33
|
-
/// <inheritdoc />
|
|
34
16
|
public string Provider => "google";
|
|
35
17
|
|
|
36
|
-
/// <inheritdoc />
|
|
37
18
|
public bool Supports(MediaKind kind) => kind == MediaKind.Image;
|
|
38
19
|
|
|
39
|
-
/// <inheritdoc />
|
|
40
20
|
public async Task<MediaResult<MediaArtifact>> ProduceAsync(
|
|
41
21
|
MediaRequest request,
|
|
42
22
|
MediaAliasBinding binding,
|
|
@@ -55,9 +35,7 @@ internal sealed class GoogleImageProvider : IMediaProvider
|
|
|
55
35
|
|
|
56
36
|
var parts = new List<Part> { new() { Text = request.Prompt } };
|
|
57
37
|
|
|
58
|
-
//
|
|
59
|
-
// ("image 1 = o sofa"). Referencia nao indexada e referencia cujo uso o modelo
|
|
60
|
-
// escolhe sozinho.
|
|
38
|
+
// A ordem das referencias e a que o prompt indexa ("image 1 = o sofa").
|
|
61
39
|
foreach (var reference in request.References)
|
|
62
40
|
{
|
|
63
41
|
parts.Add(new Part
|
|
@@ -91,8 +69,6 @@ internal sealed class GoogleImageProvider : IMediaProvider
|
|
|
91
69
|
|
|
92
70
|
if (blob is null)
|
|
93
71
|
{
|
|
94
|
-
// Sem bytes e sem excecao: o caso tipico e filtro de conteudo. Terminal, e nao
|
|
95
|
-
// um erro tecnico que um retry resolveria.
|
|
96
72
|
return MediaResult<MediaArtifact>.Fail(
|
|
97
73
|
MediaErrorCode.PolicyBlocked,
|
|
98
74
|
$"alias '{binding.Alias}': a resposta nao trouxe imagem. O caso tipico e filtro de " +
|
|
@@ -105,8 +81,7 @@ internal sealed class GoogleImageProvider : IMediaProvider
|
|
|
105
81
|
blob.Data,
|
|
106
82
|
RevisedPrompt: null,
|
|
107
83
|
ResponseId: response.ResponseId,
|
|
108
|
-
// SynthID vem embutido
|
|
109
|
-
// chegaram: nenhum re-encode entre a resposta e o storage.
|
|
84
|
+
// SynthID vem embutido e os bytes nao sao re-encodados ate o storage.
|
|
110
85
|
C2paPreserved: true));
|
|
111
86
|
}
|
|
112
87
|
}
|
package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/OpenAIImageProvider.cs
CHANGED
|
@@ -3,32 +3,8 @@ using OpenAI.Images;
|
|
|
3
3
|
|
|
4
4
|
namespace Morph.AiKit.Media.Providers;
|
|
5
5
|
|
|
6
|
-
/// <summary>
|
|
7
|
-
/// Adaptador de imagem da OpenAI. <b><c>internal</c></b> — ver <see cref="IMediaProvider"/>.
|
|
8
|
-
/// </summary>
|
|
9
|
-
/// <remarks>
|
|
10
|
-
/// <para>
|
|
11
|
-
/// A chamada passa pela abstracao <see cref="IImageGenerator"/> de
|
|
12
|
-
/// <c>Microsoft.Extensions.AI</c>, e nao pelo <c>ImageClient</c> cru, para que o pipeline
|
|
13
|
-
/// nao fique acoplado ao SDK de um provider. A ponte oficial
|
|
14
|
-
/// (<c>Microsoft.Extensions.AI.OpenAI.AsIImageGenerator()</c>) <b>nao esta no pin deste
|
|
15
|
-
/// kit</b>, entao o adaptador <see cref="OpenAIImageGenerator"/> vive aqui, escrito a mao
|
|
16
|
-
/// sobre o <c>ImageClient</c>. Trocar por <c>AsIImageGenerator()</c> e uma linha, no dia em
|
|
17
|
-
/// que o pacote entrar em <c>framework/ai-pin.json</c>.
|
|
18
|
-
/// </para>
|
|
19
|
-
/// <para>
|
|
20
|
-
/// <b>Medido contra o pin em 2026-09-08, nao suposto:</b>
|
|
21
|
-
/// <c>Microsoft.Extensions.AI.IImageGenerator</c> carrega <c>[Experimental("MEAI001")]</c>
|
|
22
|
-
/// e herda <c>IDisposable</c>; <c>ImageGenerationRequest</c> tem <c>Prompt</c> e
|
|
23
|
-
/// <c>OriginalImages</c> (<c>IEnumerable<AIContent></c>), e tambem e experimental;
|
|
24
|
-
/// <c>ImageGenerationOptions</c> <b>nao</b> tem propriedade tipada para
|
|
25
|
-
/// <c>quality</c>/<c>background</c>/<c>input_fidelity</c> — por isso a qualidade viaja em
|
|
26
|
-
/// <c>AdditionalProperties</c> e e este adaptador que a traduz para o SDK.
|
|
27
|
-
/// </para>
|
|
28
|
-
/// </remarks>
|
|
29
6
|
internal sealed class OpenAIImageProvider : IMediaProvider
|
|
30
7
|
{
|
|
31
|
-
/// <summary>A chave sob a qual a qualidade viaja em <c>AdditionalProperties</c>.</summary>
|
|
32
8
|
internal const string QualityKey = "quality";
|
|
33
9
|
|
|
34
10
|
private readonly IMediaClientFactory _clients;
|
|
@@ -39,13 +15,10 @@ internal sealed class OpenAIImageProvider : IMediaProvider
|
|
|
39
15
|
_clients = clients;
|
|
40
16
|
}
|
|
41
17
|
|
|
42
|
-
/// <inheritdoc />
|
|
43
18
|
public string Provider => "openai";
|
|
44
19
|
|
|
45
|
-
/// <inheritdoc />
|
|
46
20
|
public bool Supports(MediaKind kind) => kind == MediaKind.Image;
|
|
47
21
|
|
|
48
|
-
/// <inheritdoc />
|
|
49
22
|
public async Task<MediaResult<MediaArtifact>> ProduceAsync(
|
|
50
23
|
MediaRequest request,
|
|
51
24
|
MediaAliasBinding binding,
|
|
@@ -69,9 +42,6 @@ internal sealed class OpenAIImageProvider : IMediaProvider
|
|
|
69
42
|
|
|
70
43
|
if (request.References.Count > 0)
|
|
71
44
|
{
|
|
72
|
-
// OriginalImages e o gancho de EDICAO: preserva o produto real em vez de
|
|
73
|
-
// recria-lo. Gerar do zero quando a foto existe inventa material, medida ou cor
|
|
74
|
-
// que o produto nao tem — o claim visual falso que o AI Act art. 50 mira.
|
|
75
45
|
generationRequest.OriginalImages =
|
|
76
46
|
[.. request.References.Select(r => new DataContent(r.Bytes, r.MediaType))];
|
|
77
47
|
}
|
|
@@ -90,8 +60,7 @@ internal sealed class OpenAIImageProvider : IMediaProvider
|
|
|
90
60
|
|
|
91
61
|
if (request.Options.Quality is { } quality)
|
|
92
62
|
{
|
|
93
|
-
//
|
|
94
|
-
// o adaptador acima e quem traduz para o SDK.
|
|
63
|
+
// MEAI 10.9.0 nao tem propriedade tipada para qualidade.
|
|
95
64
|
options.AdditionalProperties = new AdditionalPropertiesDictionary
|
|
96
65
|
{
|
|
97
66
|
[QualityKey] = quality.ToString(),
|
|
@@ -119,16 +88,13 @@ internal sealed class OpenAIImageProvider : IMediaProvider
|
|
|
119
88
|
data.Data,
|
|
120
89
|
RevisedPrompt: (response.RawRepresentation as GeneratedImage)?.RevisedPrompt,
|
|
121
90
|
ResponseId: null,
|
|
122
|
-
//
|
|
123
|
-
// resposta e o blob. E o que mantem a credencial C2PA viva.
|
|
91
|
+
// Nenhum re-encode entre a resposta e o blob: e o que mantem a credencial C2PA viva.
|
|
124
92
|
C2paPreserved: true));
|
|
125
93
|
}
|
|
126
94
|
#pragma warning restore MEAI001
|
|
127
95
|
catch (System.ClientModel.ClientResultException ex)
|
|
128
96
|
{
|
|
129
|
-
//
|
|
130
|
-
// gasta para receber o mesmo "nao". A distincao mora aqui, no unico ponto que
|
|
131
|
-
// ve o codigo HTTP.
|
|
97
|
+
// A OpenAI devolve bloqueio de moderacao como HTTP 400: terminal, nunca retry.
|
|
132
98
|
return ex.Status == 400
|
|
133
99
|
? MediaResult<MediaArtifact>.Fail(
|
|
134
100
|
MediaErrorCode.PolicyBlocked,
|
|
@@ -140,20 +106,8 @@ internal sealed class OpenAIImageProvider : IMediaProvider
|
|
|
140
106
|
}
|
|
141
107
|
}
|
|
142
108
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
/// </summary>
|
|
146
|
-
/// <remarks>
|
|
147
|
-
/// Existe porque <c>Microsoft.Extensions.AI.OpenAI</c> — que traz
|
|
148
|
-
/// <c>AsIImageGenerator()</c> — nao esta em <c>framework/ai-pin.json</c>, e acrescentar
|
|
149
|
-
/// pacote e mexer no pin. O contrato imitado e o mesmo: <c>OriginalImages</c> vazio vira
|
|
150
|
-
/// <c>GenerateImageAsync</c>; com referencia, vira <c>GenerateImageEditAsync</c>, que e o
|
|
151
|
-
/// caminho que PRESERVA o produto real.
|
|
152
|
-
/// </remarks>
|
|
153
|
-
// OPENAI001 entra junto, e nao por preguica: GeneratedImageQuality.{Low,Medium,High}Quality
|
|
154
|
-
// e ImageEditOptions.Quality carregam o diagnostico de "evaluation only" no OpenAI 2.13.0 —
|
|
155
|
-
// medido pelo compilador, nao suposto. Suprimir os dois com o motivo escrito e o item 3 do
|
|
156
|
-
// checklist de ai-agents/modalities-image-gen.md.
|
|
109
|
+
// Escrito a mao porque AsIImageGenerator() (Microsoft.Extensions.AI.OpenAI) nao esta no pin.
|
|
110
|
+
// OPENAI001: GeneratedImageQuality e ImageEditOptions.Quality sao "evaluation only" no OpenAI 2.13.0.
|
|
157
111
|
#pragma warning disable MEAI001, OPENAI001
|
|
158
112
|
internal sealed class OpenAIImageGenerator : IImageGenerator
|
|
159
113
|
{
|
|
@@ -211,9 +165,7 @@ internal sealed class OpenAIImageGenerator : IImageGenerator
|
|
|
211
165
|
|
|
212
166
|
public void Dispose()
|
|
213
167
|
{
|
|
214
|
-
// O ImageClient
|
|
215
|
-
// clientes cria, o projeto descarta. Descartar aqui seria uma segunda dona do mesmo
|
|
216
|
-
// recurso — foi assim que um cliente vazou de um teste, uma vez.
|
|
168
|
+
// O ImageClient pertence a fabrica de clientes; descartar aqui seria uma segunda dona.
|
|
217
169
|
}
|
|
218
170
|
|
|
219
171
|
private static OpenAI.Images.ImageGenerationOptions BuildGenerationOptions(
|
|
@@ -221,8 +173,6 @@ internal sealed class OpenAIImageGenerator : IImageGenerator
|
|
|
221
173
|
{
|
|
222
174
|
var sdk = new OpenAI.Images.ImageGenerationOptions
|
|
223
175
|
{
|
|
224
|
-
// Bytes, sempre. Uma URL de provider expira, e um caminho quebrado depois e um
|
|
225
|
-
// arquivo pelo qual se pagou e que nao existe mais.
|
|
226
176
|
ResponseFormat = GeneratedImageFormat.Bytes,
|
|
227
177
|
};
|
|
228
178
|
|
|
@@ -256,10 +206,6 @@ internal sealed class OpenAIImageGenerator : IImageGenerator
|
|
|
256
206
|
return sdk;
|
|
257
207
|
}
|
|
258
208
|
|
|
259
|
-
/// <summary>
|
|
260
|
-
/// Traduz a qualidade que viajou em <c>AdditionalProperties</c>. Chave ausente devolve
|
|
261
|
-
/// nulo — e nulo significa "nao envia ao provider", nunca "manda o default do kit".
|
|
262
|
-
/// </summary>
|
|
263
209
|
private static GeneratedImageQuality? QualityOf(Microsoft.Extensions.AI.ImageGenerationOptions? options)
|
|
264
210
|
{
|
|
265
211
|
if (options?.AdditionalProperties is not { } extra
|
|
@@ -285,4 +231,4 @@ internal sealed class OpenAIImageGenerator : IImageGenerator
|
|
|
285
231
|
_ => "reference.png",
|
|
286
232
|
};
|
|
287
233
|
}
|
|
288
|
-
#pragma warning restore MEAI001
|
|
234
|
+
#pragma warning restore MEAI001, OPENAI001
|
package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/VeoVideoProvider.cs
CHANGED
|
@@ -2,40 +2,11 @@ using Google.GenAI.Types;
|
|
|
2
2
|
|
|
3
3
|
namespace Morph.AiKit.Media.Providers;
|
|
4
4
|
|
|
5
|
-
/// <summary>
|
|
6
|
-
/// Adaptador de video do Veo. <b><c>internal</c></b> — ver <see cref="IMediaProvider"/>.
|
|
7
|
-
/// </summary>
|
|
8
|
-
/// <remarks>
|
|
9
|
-
/// <para>
|
|
10
|
-
/// <b>E o unico caminho de video com SDK .NET oficial</b> depois do desligamento da Sora
|
|
11
|
-
/// (data em <c>ai-agents/media-video.md</c>, secao Deprecacoes). Seedance, Kling e Runway
|
|
12
|
-
/// exigem <c>HttpClient</c> tipado com DTOs proprios, e por isso nao tem adaptador aqui.
|
|
13
|
-
/// </para>
|
|
14
|
-
/// <para>
|
|
15
|
-
/// <b>Assinatura medida contra Google.GenAI 1.21.0, nao lida em blog:</b>
|
|
16
|
-
/// <c>Models.GenerateVideosAsync(string model, GenerateVideosSource source,
|
|
17
|
-
/// GenerateVideosConfig config, CancellationToken)</c> devolve
|
|
18
|
-
/// <c>GenerateVideosOperation</c> — o prompt vai DENTRO de <c>GenerateVideosSource</c>, e
|
|
19
|
-
/// nao como parametro solto. O polling e
|
|
20
|
-
/// <c>Operations.GetAsync(operation, config, ct)</c>, e o veredito e
|
|
21
|
-
/// <c>operation.Done</c>.
|
|
22
|
-
/// </para>
|
|
23
|
-
/// <para>
|
|
24
|
-
/// <b>Os bytes sao baixados AQUI.</b> O Veo retem o arquivo por 2 dias; devolver a URL e
|
|
25
|
-
/// entregar um link que morre. Quando a resposta so traz <c>Uri</c>, este adaptador recusa
|
|
26
|
-
/// com motivo em vez de guardar a URL fingindo que e permanente.
|
|
27
|
-
/// </para>
|
|
28
|
-
/// </remarks>
|
|
29
5
|
internal sealed class VeoVideoProvider : IMediaProvider
|
|
30
6
|
{
|
|
31
|
-
/// <summary>Intervalo entre voltas do polling. ~10 s e o intervalo que a doc do Veo usa.</summary>
|
|
32
7
|
internal static readonly TimeSpan PollInterval = TimeSpan.FromSeconds(10);
|
|
33
8
|
|
|
34
|
-
|
|
35
|
-
/// Teto de voltas. Existe porque <c>Done</c> que nunca vira <c>true</c> e um caso real,
|
|
36
|
-
/// e um laco sem saida e indistinguivel de um worker travado. 60 voltas cobrem os 6 min
|
|
37
|
-
/// documentados como pior caso, com folga.
|
|
38
|
-
/// </summary>
|
|
9
|
+
// Cobre os 6 min documentados como pior caso do Veo; Done pode nunca virar true.
|
|
39
10
|
internal const int MaxPolls = 60;
|
|
40
11
|
|
|
41
12
|
private readonly IMediaClientFactory _clients;
|
|
@@ -48,13 +19,10 @@ internal sealed class VeoVideoProvider : IMediaProvider
|
|
|
48
19
|
_delay = delay ?? Task.Delay;
|
|
49
20
|
}
|
|
50
21
|
|
|
51
|
-
/// <inheritdoc />
|
|
52
22
|
public string Provider => "google";
|
|
53
23
|
|
|
54
|
-
/// <inheritdoc />
|
|
55
24
|
public bool Supports(MediaKind kind) => kind == MediaKind.Video;
|
|
56
25
|
|
|
57
|
-
/// <inheritdoc />
|
|
58
26
|
public async Task<MediaResult<MediaArtifact>> ProduceAsync(
|
|
59
27
|
MediaRequest request,
|
|
60
28
|
MediaAliasBinding binding,
|
|
@@ -80,8 +48,7 @@ internal sealed class VeoVideoProvider : IMediaProvider
|
|
|
80
48
|
|
|
81
49
|
if (request.References.Count > 0)
|
|
82
50
|
{
|
|
83
|
-
// O Veo exige 8 s quando ha imagens de referencia.
|
|
84
|
-
// requisicao invalida, e descobrir isso em producao custa uma rodada de suporte.
|
|
51
|
+
// O Veo exige 8 s quando ha imagens de referencia.
|
|
85
52
|
config.ReferenceImages =
|
|
86
53
|
[
|
|
87
54
|
.. request.References.Select(r => new VideoGenerationReferenceImage
|
|
@@ -127,8 +94,6 @@ internal sealed class VeoVideoProvider : IMediaProvider
|
|
|
127
94
|
|
|
128
95
|
if (response?.RaiMediaFilteredCount is > 0)
|
|
129
96
|
{
|
|
130
|
-
// Bloqueio de moderacao. No Veo ele NAO cobra — e nao vira retry, porque
|
|
131
|
-
// retentar recebe o mesmo "nao".
|
|
132
97
|
return MediaResult<MediaArtifact>.Fail(
|
|
133
98
|
MediaErrorCode.PolicyBlocked,
|
|
134
99
|
$"alias '{binding.Alias}': {response.RaiMediaFilteredCount} saida(s) bloqueada(s) por politica: " +
|
|
@@ -1,29 +1,7 @@
|
|
|
1
1
|
namespace Morph.AiKit.Middleware;
|
|
2
2
|
|
|
3
|
-
/// <summary>
|
|
4
|
-
/// Para onde vai o custo de cada volta ao modelo. <b>A costura de saida do
|
|
5
|
-
/// <see cref="UsageCostMiddleware"/></b>: o kit calcula, o projeto decide onde guardar
|
|
6
|
-
/// (tabela, metrica, log estruturado, fila).
|
|
7
|
-
/// </summary>
|
|
8
|
-
/// <remarks>
|
|
9
|
-
/// <para>
|
|
10
|
-
/// <b>Por que uma interface de um metodo so e nao um evento nem um logger:</b> custo e
|
|
11
|
-
/// dado de negocio, nao diagnostico. Um projeto multi-tenant precisa somar por conta e por
|
|
12
|
-
/// mes; um <c>ILogger</c> nao devolve soma. A interface e minima de proposito — quem quiser
|
|
13
|
-
/// um logger implementa um sink de duas linhas.
|
|
14
|
-
/// </para>
|
|
15
|
-
/// <para>
|
|
16
|
-
/// <b>Contrato de execucao:</b> <see cref="Record"/> e chamado no caminho da resposta,
|
|
17
|
-
/// <b>sincronamente</b>, uma vez por volta ao modelo. Uma implementacao que faca I/O
|
|
18
|
-
/// bloqueante aqui paga esse custo na latencia de cada resposta — enfileire e persista fora
|
|
19
|
-
/// do caminho quente. E ela pode ser chamada de varias threads ao mesmo tempo (um mesmo
|
|
20
|
-
/// pipeline atende varias conversas): a implementacao e responsavel pela propria seguranca
|
|
21
|
-
/// sob concorrencia.
|
|
22
|
-
/// </para>
|
|
23
|
-
/// </remarks>
|
|
24
3
|
public interface IUsageCostSink
|
|
25
4
|
{
|
|
26
|
-
|
|
27
|
-
/// <param name="record">O registro ja calculado. Nunca nulo.</param>
|
|
5
|
+
// Chamado sincronamente no caminho da resposta e de varias threads: I/O aqui vira latencia.
|
|
28
6
|
void Record(UsageCostRecord record);
|
|
29
7
|
}
|
|
@@ -4,68 +4,12 @@ using Morph.AiKit.Providers;
|
|
|
4
4
|
|
|
5
5
|
namespace Morph.AiKit.Middleware;
|
|
6
6
|
|
|
7
|
-
/// <summary>
|
|
8
|
-
/// Middleware de <see cref="IChatClient"/> que le o <c>Usage</c> da resposta, aplica a
|
|
9
|
-
/// tarifa do alias e publica uma linha de custo em <see cref="IUsageCostSink"/>.
|
|
10
|
-
/// </summary>
|
|
11
|
-
/// <remarks>
|
|
12
|
-
/// <para>
|
|
13
|
-
/// <b>Onde ele entra no pipeline:</b> <b>ACIMA</b> da invocacao de funcoes, com a
|
|
14
|
-
/// telemetria de chat abaixo dela (<c>usage-cost -> function-invocation ->
|
|
15
|
-
/// OpenTelemetry -> cliente do provider</c>). Na pratica: a
|
|
16
|
-
/// <c>AgentFactory.BuildChatPipeline</c> registra o encaixe de custo <b>antes</b> de
|
|
17
|
-
/// <c>UseFunctionInvocation</c>, e no <c>ChatClientBuilder</c> o primeiro registrado e o
|
|
18
|
-
/// mais <b>externo</b>. A ordem nao e estetica, e o motivo e o
|
|
19
|
-
/// <c>FunctionInvokingChatClient</c> (FICC): acima dele o middleware ve <b>uma</b> resposta
|
|
20
|
-
/// cujo <c>Usage</c> ja vem com o total de todas as voltas de tool (medido: duas voltas de
|
|
21
|
-
/// 10/4 e 30/6 chegam como <c>in=40, out=10</c>) e publica <b>uma</b> linha por chamada.
|
|
22
|
-
/// Abaixo dele veria as respostas parciais — e leria o mesmo objeto <c>UsageDetails</c> que
|
|
23
|
-
/// o proprio FICC <b>muta</b> para acumular, o que faria a mesma conta dar resultado
|
|
24
|
-
/// diferente conforme a hora de ler. Isso e medido, nao argumentado, em
|
|
25
|
-
/// <c>AgentFactoryTests.ThePipelineOrderIsUsageCostAboveFunctionInvocationAndTelemetryBelowIt</c>
|
|
26
|
-
/// (sequencia <c>usage-cost:enter -> telemetry:enter/exit x2 -> usage-cost:exit</c>) e
|
|
27
|
-
/// em <c>AgentFactoryTests.UsageCostAboveFunctionInvocationSeesTheUsageOfEveryToolRoundSummedOnce</c>.
|
|
28
|
-
/// A montagem do pipeline e da <c>AgentFactory</c> (T10); daqui so depende a extensao
|
|
29
|
-
/// <see cref="UsageCostChatClientBuilderExtensions.UseUsageCost"/>.
|
|
30
|
-
/// </para>
|
|
31
|
-
/// <para>
|
|
32
|
-
/// <b>Nota de errata:</b> o <c>spec.md</c> sec. 3.1 desta feature prescrevia a seta
|
|
33
|
-
/// literal <c>function-invocation -> usage-cost -> OpenTelemetry</c>, que se le ao
|
|
34
|
-
/// contrario do que esta implementado aqui. A divergencia foi medida na T10 sec. 3.2 e esta
|
|
35
|
-
/// registrada como <b>Errata E9</b> no proprio <c>spec.md</c> — o corpo do plano e
|
|
36
|
-
/// append-only e continua com a seta antiga; a verdade e esta.
|
|
37
|
-
/// </para>
|
|
38
|
-
/// <para>
|
|
39
|
-
/// <b>A conta e a do <see cref="ModelPricing.ComputeCostUsd"/>, nao uma segunda.</b> Este
|
|
40
|
-
/// tipo nao contem aritmetica de tarifa: ele converte <c>Usage</c> em argumentos, chama a
|
|
41
|
-
/// conta que ja existe e carimba o resultado. Reescrever a formula aqui daria dois numeros
|
|
42
|
-
/// para o mesmo custo — que e literalmente a dor no nome desta epica (em ProspectPRO a
|
|
43
|
-
/// conta esta repetida em cinco agentes).
|
|
44
|
-
/// </para>
|
|
45
|
-
/// <para>
|
|
46
|
-
/// <b>Nunca lanca por causa de tarifa.</b> Alias sem tarifa e caminho previsto: sai
|
|
47
|
-
/// <c>CostUsd = 0</c> com <see cref="UsageCostStatus.Waived"/> e o motivo escrito no
|
|
48
|
-
/// record. Inventar tarifa e pior que nao ter. E resposta sem <c>Usage</c> sai como
|
|
49
|
-
/// <see cref="UsageCostStatus.UsageMissing"/> — visivel, em vez de silenciosa.
|
|
50
|
-
/// </para>
|
|
51
|
-
/// </remarks>
|
|
52
7
|
public sealed class UsageCostMiddleware : DelegatingChatClient
|
|
53
8
|
{
|
|
54
9
|
private readonly ModelAlias _alias;
|
|
55
10
|
private readonly IUsageCostSink _sink;
|
|
56
11
|
private readonly TimeProvider _timeProvider;
|
|
57
12
|
|
|
58
|
-
/// <summary>Monta o middleware sobre um cliente interno.</summary>
|
|
59
|
-
/// <param name="innerClient">O proximo elo do pipeline.</param>
|
|
60
|
-
/// <param name="alias">
|
|
61
|
-
/// O alias que atende este cliente — de onde vem a tarifa e o nome que vai no record.
|
|
62
|
-
/// </param>
|
|
63
|
-
/// <param name="sink">Para onde as linhas de custo vao.</param>
|
|
64
|
-
/// <param name="timeProvider">
|
|
65
|
-
/// Relogio. Nulo usa <see cref="TimeProvider.System"/>. Injetavel porque o standard
|
|
66
|
-
/// <c>backend-dotnet-testing</c> proibe <c>DateTimeOffset.UtcNow</c> direto num tipo
|
|
67
|
-
/// testavel — sem isso, o instante do record e intestavel.
|
|
68
|
-
/// </param>
|
|
69
13
|
public UsageCostMiddleware(
|
|
70
14
|
IChatClient innerClient,
|
|
71
15
|
ModelAlias alias,
|
|
@@ -81,7 +25,6 @@ public sealed class UsageCostMiddleware : DelegatingChatClient
|
|
|
81
25
|
_timeProvider = timeProvider ?? TimeProvider.System;
|
|
82
26
|
}
|
|
83
27
|
|
|
84
|
-
/// <inheritdoc />
|
|
85
28
|
public override async Task<ChatResponse> GetResponseAsync(
|
|
86
29
|
IEnumerable<ChatMessage> messages,
|
|
87
30
|
ChatOptions? options = null,
|
|
@@ -95,13 +38,7 @@ public sealed class UsageCostMiddleware : DelegatingChatClient
|
|
|
95
38
|
return response;
|
|
96
39
|
}
|
|
97
40
|
|
|
98
|
-
|
|
99
|
-
/// <remarks>
|
|
100
|
-
/// No streaming o <c>Usage</c> chega como <see cref="UsageContent"/> avulso, tipicamente
|
|
101
|
-
/// no ultimo update — e pode chegar em mais de um pedaco. Os pedacos sao somados e a
|
|
102
|
-
/// linha de custo sai <b>uma unica vez</b>, ao fim da enumeracao: emitir por update daria
|
|
103
|
-
/// N linhas para uma volta so.
|
|
104
|
-
/// </remarks>
|
|
41
|
+
// No streaming o Usage pode chegar em mais de um UsageContent: soma e publica uma linha so, ao fim.
|
|
105
42
|
public override async IAsyncEnumerable<ChatResponseUpdate> GetStreamingResponseAsync(
|
|
106
43
|
IEnumerable<ChatMessage> messages,
|
|
107
44
|
ChatOptions? options = null,
|
|
@@ -132,8 +69,6 @@ public sealed class UsageCostMiddleware : DelegatingChatClient
|
|
|
132
69
|
|
|
133
70
|
if (usage is null || (usage.InputTokenCount is null && usage.OutputTokenCount is null))
|
|
134
71
|
{
|
|
135
|
-
// Sem relato de uso nao ha o que tarifar. A linha sai assim mesmo, marcada: um
|
|
136
|
-
// custo que ninguem consegue medir precisa aparecer, nao sumir.
|
|
137
72
|
_sink.Record(new UsageCostRecord(_alias.Name, 0, 0, null, 0m, at)
|
|
138
73
|
{
|
|
139
74
|
Status = UsageCostStatus.UsageMissing,
|
|
@@ -147,8 +82,6 @@ public sealed class UsageCostMiddleware : DelegatingChatClient
|
|
|
147
82
|
|
|
148
83
|
if (_alias.Pricing is not { } pricing)
|
|
149
84
|
{
|
|
150
|
-
// Caminho previsto, nunca excecao: o alias declarou por escrito que nao tem
|
|
151
|
-
// tarifa (o registry recusa a carga de um alias sem tarifa E sem dispensa).
|
|
152
85
|
_sink.Record(new UsageCostRecord(_alias.Name, input, output, cached, 0m, at)
|
|
153
86
|
{
|
|
154
87
|
Status = UsageCostStatus.Waived,
|
|
@@ -157,9 +90,7 @@ public sealed class UsageCostMiddleware : DelegatingChatClient
|
|
|
157
90
|
return;
|
|
158
91
|
}
|
|
159
92
|
|
|
160
|
-
//
|
|
161
|
-
// do T8 recusa esse par. Aqui o dado inconsistente vira lacuna declarada, e nao uma
|
|
162
|
-
// excecao que derruba a resposta do usuario por causa de um contador.
|
|
93
|
+
// ComputeCostUsd recusa esse par; um contador incoerente do provider nao pode derrubar a resposta.
|
|
163
94
|
if (cached > input)
|
|
164
95
|
{
|
|
165
96
|
_sink.Record(new UsageCostRecord(_alias.Name, input, output, cached, 0m, at)
|
|
@@ -182,19 +113,8 @@ public sealed class UsageCostMiddleware : DelegatingChatClient
|
|
|
182
113
|
}
|
|
183
114
|
}
|
|
184
115
|
|
|
185
|
-
/// <summary>
|
|
186
|
-
/// Registro do <see cref="UsageCostMiddleware"/> no pipeline de <see cref="IChatClient"/>.
|
|
187
|
-
/// </summary>
|
|
188
116
|
public static class UsageCostChatClientBuilderExtensions
|
|
189
117
|
{
|
|
190
|
-
/// <summary>
|
|
191
|
-
/// Insere a contabilizacao de custo no pipeline, com a tarifa do alias dado.
|
|
192
|
-
/// </summary>
|
|
193
|
-
/// <param name="builder">O builder do pipeline.</param>
|
|
194
|
-
/// <param name="alias">Alias que atende este cliente.</param>
|
|
195
|
-
/// <param name="sink">Destino das linhas de custo.</param>
|
|
196
|
-
/// <param name="timeProvider">Relogio; nulo usa <see cref="TimeProvider.System"/>.</param>
|
|
197
|
-
/// <returns>O proprio builder, para encadear.</returns>
|
|
198
118
|
public static ChatClientBuilder UseUsageCost(
|
|
199
119
|
this ChatClientBuilder builder,
|
|
200
120
|
ModelAlias alias,
|
|
@@ -2,74 +2,16 @@ using Morph.AiKit.Providers;
|
|
|
2
2
|
|
|
3
3
|
namespace Morph.AiKit.Middleware;
|
|
4
4
|
|
|
5
|
-
/// <summary>
|
|
6
|
-
/// Como o custo de uma volta ao modelo ficou conhecido. Existe para que
|
|
7
|
-
/// <see cref="UsageCostRecord.CostUsd"/> possa ser um <see cref="decimal"/> nao-anulavel
|
|
8
|
-
/// <b>sem que zero passe a significar tres coisas diferentes</b>.
|
|
9
|
-
/// </summary>
|
|
10
|
-
/// <remarks>
|
|
11
|
-
/// A dor que esta feature nomeia e <c>cost_usd</c> saindo nulo. Trocar o nulo por um zero
|
|
12
|
-
/// mudo seria trocar um defeito por outro pior — zero soma, nulo nao. Aqui o valor e sempre
|
|
13
|
-
/// um numero e o status diz se aquele numero e uma medicao, uma decisao ou uma lacuna.
|
|
14
|
-
/// </remarks>
|
|
15
5
|
public enum UsageCostStatus
|
|
16
6
|
{
|
|
17
|
-
/// <summary>
|
|
18
|
-
/// Tarifa do alias aplicada sobre uso reportado pelo provider. O unico status em que
|
|
19
|
-
/// <see cref="UsageCostRecord.CostUsd"/> e uma <b>medicao</b> — e o unico que pode
|
|
20
|
-
/// entrar numa soma de fatura sem ressalva.
|
|
21
|
-
/// </summary>
|
|
22
7
|
Computed = 0,
|
|
23
8
|
|
|
24
|
-
/// <summary>
|
|
25
|
-
/// O alias nao tem tarifa e <b>tem dispensa escrita</b>
|
|
26
|
-
/// (<c>ModelAlias.PricingWaiver</c>). Custo zero por decisao registrada — modelo local,
|
|
27
|
-
/// por exemplo, onde a conta e a maquina e nao o token. O motivo viaja em
|
|
28
|
-
/// <see cref="UsageCostRecord.PricingWaiver"/>: um zero aqui nunca e anonimo.
|
|
29
|
-
/// </summary>
|
|
30
9
|
Waived = 1,
|
|
31
10
|
|
|
32
|
-
|
|
33
|
-
/// O provider <b>nao reportou</b> <c>Usage</c> nesta volta. Os tokens sao desconhecidos,
|
|
34
|
-
/// logo o custo tambem e: o zero em <see cref="UsageCostRecord.CostUsd"/> e ausencia de
|
|
35
|
-
/// medicao, <b>nao</b> gratuidade. Some com <see cref="Computed"/> apenas ciente disso.
|
|
36
|
-
/// </summary>
|
|
11
|
+
// O zero de CostUsd aqui e ausencia de medicao, nao gratuidade.
|
|
37
12
|
UsageMissing = 2,
|
|
38
13
|
}
|
|
39
14
|
|
|
40
|
-
/// <summary>
|
|
41
|
-
/// O custo de uma volta ao modelo, ja calculado. <b>Uma linha por resposta</b>, pronta para
|
|
42
|
-
/// virar registro de banco, metrica ou linha de fatura.
|
|
43
|
-
/// </summary>
|
|
44
|
-
/// <remarks>
|
|
45
|
-
/// <para>
|
|
46
|
-
/// <b><see cref="CostUsd"/> nunca e nulo — por construcao do tipo.</b> Esse e o ponto
|
|
47
|
-
/// inteiro: a dor catalogada na issue e um <c>cost_usd</c> nulo atravessando tres camadas
|
|
48
|
-
/// e aparecendo como buraco no relatorio. A ausencia de tarifa deixou de poder chegar ate
|
|
49
|
-
/// aqui por esquecimento: o <c>ModelRegistry</c> (T8) <b>recusa a carga</b> de um alias que
|
|
50
|
-
/// nao declare <c>pricing</c> nem <c>pricingWaiver</c>. Logo, quando este record sai com
|
|
51
|
-
/// <see cref="UsageCostStatus.Waived"/>, existe necessariamente um motivo escrito em
|
|
52
|
-
/// <see cref="PricingWaiver"/>.
|
|
53
|
-
/// </para>
|
|
54
|
-
/// <para>
|
|
55
|
-
/// <b>A conta nao mora aqui.</b> Ela mora em <see cref="ModelPricing.ComputeCostUsd"/>
|
|
56
|
-
/// (T8), e o <see cref="UsageCostMiddleware"/> apenas a chama. Uma segunda aritmetica do
|
|
57
|
-
/// mesmo custo e o defeito que esta epica tem no nome: em ProspectPRO ela esta repetida
|
|
58
|
-
/// literalmente em cinco agentes, e duas copias da mesma conta divergem — a divergencia
|
|
59
|
-
/// aparece na fatura.
|
|
60
|
-
/// </para>
|
|
61
|
-
/// </remarks>
|
|
62
|
-
/// <param name="Alias">O alias do <c>ModelRegistry</c> que atendeu a volta.</param>
|
|
63
|
-
/// <param name="InputTokens">Tokens de entrada reportados. Zero quando nao houve relato.</param>
|
|
64
|
-
/// <param name="OutputTokens">Tokens de saida reportados. Zero quando nao houve relato.</param>
|
|
65
|
-
/// <param name="CachedInputTokens">
|
|
66
|
-
/// Tokens de entrada servidos do cache do provider, ou nulo quando o provider nao reporta.
|
|
67
|
-
/// E <b>subconjunto</b> de <paramref name="InputTokens"/>, nunca uma parcela a mais.
|
|
68
|
-
/// </param>
|
|
69
|
-
/// <param name="CostUsd">
|
|
70
|
-
/// Custo em USD. Nunca nulo; leia <see cref="Status"/> antes de somar.
|
|
71
|
-
/// </param>
|
|
72
|
-
/// <param name="At">Instante da volta, vindo do <see cref="TimeProvider"/> injetado.</param>
|
|
73
15
|
public sealed record UsageCostRecord(
|
|
74
16
|
string Alias,
|
|
75
17
|
long InputTokens,
|
|
@@ -78,18 +20,9 @@ public sealed record UsageCostRecord(
|
|
|
78
20
|
decimal CostUsd,
|
|
79
21
|
DateTimeOffset At)
|
|
80
22
|
{
|
|
81
|
-
/// <summary>Como o custo ficou conhecido. Ver <see cref="UsageCostStatus"/>.</summary>
|
|
82
23
|
public required UsageCostStatus Status { get; init; }
|
|
83
24
|
|
|
84
|
-
/// <summary>
|
|
85
|
-
/// O motivo escrito de o alias nao ter tarifa, copiado de
|
|
86
|
-
/// <c>ModelAlias.PricingWaiver</c>. Preenchido <b>somente</b> com
|
|
87
|
-
/// <see cref="UsageCostStatus.Waived"/>.
|
|
88
|
-
/// </summary>
|
|
89
25
|
public string? PricingWaiver { get; init; }
|
|
90
26
|
|
|
91
|
-
/// <summary>
|
|
92
|
-
/// Se <see cref="CostUsd"/> e uma medicao — o unico caso em que somar e honesto.
|
|
93
|
-
/// </summary>
|
|
94
27
|
public bool IsMeasuredCost => Status == UsageCostStatus.Computed;
|
|
95
28
|
}
|
|
@@ -4,48 +4,8 @@ using Morph.AiKit.Agents;
|
|
|
4
4
|
|
|
5
5
|
namespace Morph.AiKit.Observability;
|
|
6
6
|
|
|
7
|
-
/// <summary>
|
|
8
|
-
/// Liga as GenAI semantic conventions nos dois niveis do kit — agente e chat client — com
|
|
9
|
-
/// <b>um guard contra span duplicado em cada um</b>.
|
|
10
|
-
/// </summary>
|
|
11
|
-
/// <remarks>
|
|
12
|
-
/// <para>
|
|
13
|
-
/// <b>O que foi medido no pin (Microsoft.Agents.AI 1.20.0), e nao suposto:</b>
|
|
14
|
-
/// </para>
|
|
15
|
-
/// <list type="bullet">
|
|
16
|
-
/// <item>
|
|
17
|
-
/// Uma invocacao instrumentada produz <c>invoke_agent</c> (raiz) e <c>chat</c> (filho).
|
|
18
|
-
/// Duas camadas de instrumentacao no MESMO nivel produzem o span daquele nivel duas
|
|
19
|
-
/// vezes: dois <c>OpenTelemetryChatClient</c> dao dois <c>chat</c>; dois
|
|
20
|
-
/// <c>OpenTelemetryAgent</c> dao dois <c>invoke_agent</c>.
|
|
21
|
-
/// </item>
|
|
22
|
-
/// <item>
|
|
23
|
-
/// <c>GetService</c> atravessa a cadeia de delegates nos dois niveis — e por isso que
|
|
24
|
-
/// "ja existe instrumentacao aqui embaixo?" e uma pergunta respondivel, e nao um
|
|
25
|
-
/// palpite. E o predicado dos dois guards.
|
|
26
|
-
/// </item>
|
|
27
|
-
/// <item>
|
|
28
|
-
/// O MAF 1.20.0 tem um guard proprio, mas ele so alcanca o pipeline que o
|
|
29
|
-
/// <c>ChatClientAgent</c> monta sozinho. A <c>AgentFactory</c> monta o proprio
|
|
30
|
-
/// (<c>UseProvidedChatClientAsIs = true</c>) para poder posicionar o custo e ajustar o
|
|
31
|
-
/// tool calling — e nesse arranjo o slot do MAF nao existe. Medido: agente
|
|
32
|
-
/// instrumentado + pipeline proprio sem estas extensoes = <b>zero</b> spans de chat.
|
|
33
|
-
/// O guard daqui nao duplica o do MAF; ele cobre o pipeline que o MAF nao ve.
|
|
34
|
-
/// </item>
|
|
35
|
-
/// </list>
|
|
36
|
-
/// </remarks>
|
|
37
7
|
public static class MorphOpenTelemetryExtensions
|
|
38
8
|
{
|
|
39
|
-
/// <summary>
|
|
40
|
-
/// Instrumenta o pipeline de chat. Aplicado pela <c>AgentFactory</c> ABAIXO do
|
|
41
|
-
/// <c>FunctionInvokingChatClient</c>, que e onde o MAF 1.20.0 documenta que a
|
|
42
|
-
/// instrumentacao tem de ficar para o span de chat fechar antes das tools.
|
|
43
|
-
/// </summary>
|
|
44
|
-
/// <remarks>
|
|
45
|
-
/// O guard vive na decisao de <see cref="ShouldInstrument"/>: com
|
|
46
|
-
/// <see cref="MorphTelemetryOptions.InstrumentChatClient"/> nulo (o default), um
|
|
47
|
-
/// pipeline que ja carrega um <see cref="OpenTelemetryChatClient"/> e devolvido intacto.
|
|
48
|
-
/// </remarks>
|
|
49
9
|
public static ChatClientBuilder UseMorphTelemetry(this ChatClientBuilder builder, MorphTelemetryOptions options)
|
|
50
10
|
{
|
|
51
11
|
ArgumentNullException.ThrowIfNull(builder);
|
|
@@ -59,10 +19,6 @@ public static class MorphOpenTelemetryExtensions
|
|
|
59
19
|
: inner);
|
|
60
20
|
}
|
|
61
21
|
|
|
62
|
-
/// <summary>
|
|
63
|
-
/// Instrumenta o agente — o span <c>invoke_agent</c>, o mais externo, que cobre a
|
|
64
|
-
/// invocacao inteira. Um agente que ja e instrumentado e devolvido intacto.
|
|
65
|
-
/// </summary>
|
|
66
22
|
public static AIAgentBuilder UseMorphTelemetry(this AIAgentBuilder builder, MorphTelemetryOptions options)
|
|
67
23
|
{
|
|
68
24
|
ArgumentNullException.ThrowIfNull(builder);
|
|
@@ -76,20 +32,6 @@ public static class MorphOpenTelemetryExtensions
|
|
|
76
32
|
});
|
|
77
33
|
}
|
|
78
34
|
|
|
79
|
-
/// <summary>
|
|
80
|
-
/// Preenche os dois encaixes de telemetria da <see cref="AgentFactoryOptions"/> de uma
|
|
81
|
-
/// vez, respeitando <see cref="AgentSpecOptions.EnableSensitiveTelemetry"/> quando a
|
|
82
|
-
/// spec opinou.
|
|
83
|
-
/// </summary>
|
|
84
|
-
/// <remarks>
|
|
85
|
-
/// A fabrica e dona da POSICAO de cada encaixe; esta extensao so diz o que ocupa cada
|
|
86
|
-
/// um. E por aqui que a T10 e a T12 se encontram sem uma referenciar a outra.
|
|
87
|
-
/// <para>
|
|
88
|
-
/// <c>EnableSensitiveTelemetry</c> nulo na spec significa "nao opinei" e mantem o valor
|
|
89
|
-
/// destas opcoes; <c>false</c> significa "decidi que nao" e vence. A diferenca entre os
|
|
90
|
-
/// dois e preservada de proposito desde a T7.
|
|
91
|
-
/// </para>
|
|
92
|
-
/// </remarks>
|
|
93
35
|
public static AgentFactoryOptions WithMorphTelemetry(
|
|
94
36
|
this AgentFactoryOptions factoryOptions,
|
|
95
37
|
MorphTelemetryOptions options)
|
|
@@ -109,10 +51,7 @@ public static class MorphOpenTelemetryExtensions
|
|
|
109
51
|
? options with { EnableSensitiveData = sensitive }
|
|
110
52
|
: options;
|
|
111
53
|
|
|
112
|
-
|
|
113
|
-
/// <b>O guard.</b> Nulo = instrumenta so o que ainda nao esta instrumentado;
|
|
114
|
-
/// <c>true</c> = instrumenta sempre (e duplica, quando ja havia); <c>false</c> = nunca.
|
|
115
|
-
/// </summary>
|
|
54
|
+
// GetService atravessa a cadeia de delegates (MAF 1.20.0), entao detecta instrumentacao ja aplicada abaixo.
|
|
116
55
|
private static bool ShouldInstrument(IChatClient inner, MorphTelemetryOptions options)
|
|
117
56
|
=> options.InstrumentChatClient
|
|
118
57
|
?? inner.GetService(typeof(OpenTelemetryChatClient)) is null;
|