Repository navigation
[AI-2748] Expose redaction members and kcap eval --baseline-out #1054
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
2dcdb9f
Expose secret-key and value redaction members
realtonyyoung 9555d2c
Anchor the env-var secret regex so redaction scales to large values
realtonyyoung 5ece91a
Fix env-var redaction that leaked a secret after a digit
realtonyyoung 138b372
Add kcap eval --baseline-out and judge usage/route/timing on the obse…
realtonyyoung a7b6d04
Make the eval baseline honest about failed judges and writes
realtonyyoung File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,110 @@ | ||
| using Capacitor.Cli.Core.Eval.Contracts; | ||
|
|
||
| namespace Capacitor.Cli.Core.Eval; | ||
|
|
||
| /// <summary>Decorates an <see cref="IEvalObserver"/> to collect each question's and the | ||
| /// retrospective's judge usage, then writes a <see cref="BaselineOutput"/> to <paramref name="path"/> | ||
| /// on the run's one terminal callback (<see cref="OnFinished"/> or <see cref="OnFailed"/>), so a | ||
| /// baseline is still written for a partially-completed run.</summary> | ||
| public sealed class BaselineObserver( | ||
| IEvalObserver inner, | ||
| string path, | ||
| string sessionId, | ||
| string model, | ||
| bool chain, | ||
| TimeProvider time | ||
| ) : IEvalObserver { | ||
| readonly List<BaselineQuestionOutput> _questions = []; | ||
| readonly List<BaselineQuestionFailure> _failures = []; | ||
| readonly long _started = time.GetTimestamp(); | ||
| BaselineRetrospectiveOutput? _retrospective; | ||
| string _evalRunId = ""; | ||
|
|
||
| /// <summary>True once a terminal callback tried and failed to write the baseline file, so the | ||
| /// caller can exit non-zero instead of reporting a success that produced no baseline.</summary> | ||
| public bool WriteFailed { get; private set; } | ||
|
|
||
| public void OnInfo(string message) => inner.OnInfo(message); | ||
|
|
||
| public void OnStarted(string evalRunId, string judgeModel, int totalQuestions) { | ||
| _evalRunId = evalRunId; | ||
| inner.OnStarted(evalRunId, judgeModel, totalQuestions); | ||
| } | ||
|
|
||
| public void OnContextFetched(int traceEntries, int traceChars, int toolResultsTotal, int toolResultsTruncated, long bytesSaved) => | ||
| inner.OnContextFetched(traceEntries, traceChars, toolResultsTotal, toolResultsTruncated, bytesSaved); | ||
|
|
||
| public void OnQuestionStarted(int index, int total, string category, string questionId) => | ||
| inner.OnQuestionStarted(index, total, category, questionId); | ||
|
|
||
| public void OnQuestionCompleted(int index, int total, EvalQuestionAssessment assessment, EvalUsage usage, string route, TimeSpan elapsed, int runnerInvocations) { | ||
| _questions.Add(new BaselineQuestionOutput { | ||
| QuestionId = assessment.QuestionId, | ||
| Route = route, | ||
| Usage = usage, | ||
| Calls = runnerInvocations, | ||
| ElapsedMs = (long)elapsed.TotalMilliseconds | ||
| }); | ||
| inner.OnQuestionCompleted(index, total, assessment, usage, route, elapsed, runnerInvocations); | ||
| } | ||
|
|
||
| public void OnQuestionFailed(int index, int total, string category, string questionId, string reason) { | ||
| _failures.Add(new BaselineQuestionFailure { | ||
| QuestionId = questionId, | ||
| Category = category, | ||
| Reason = reason | ||
| }); | ||
| inner.OnQuestionFailed(index, total, category, questionId, reason); | ||
| } | ||
|
|
||
| public void OnFactRetained(string category, string fact) => inner.OnFactRetained(category, fact); | ||
|
|
||
| public void OnRetrospectiveStarted() => inner.OnRetrospectiveStarted(); | ||
|
|
||
| public void OnRetrospectiveCompleted(EvalRetrospectiveV2 retrospective, EvalUsage usage, TimeSpan elapsed) { | ||
| _retrospective = new BaselineRetrospectiveOutput { | ||
| Usage = usage, | ||
| Calls = 1, | ||
| ElapsedMs = (long)elapsed.TotalMilliseconds | ||
| }; | ||
| inner.OnRetrospectiveCompleted(retrospective, usage, elapsed); | ||
| } | ||
|
|
||
| public void OnRetrospectiveFailed(string reason) => inner.OnRetrospectiveFailed(reason); | ||
|
|
||
| public void OnFinished(SessionEvalCompletedPayloadV4 aggregate) { | ||
| WriteBaseline(); | ||
| inner.OnFinished(aggregate); | ||
| } | ||
|
|
||
| public void OnFailed(string reason) { | ||
| WriteBaseline(); | ||
| inner.OnFailed(reason); | ||
| } | ||
|
|
||
| void WriteBaseline() { | ||
| var usages = new List<EvalUsage>(_questions.Select(q => q.Usage)); | ||
| if (_retrospective is not null) usages.Add(_retrospective.Usage); | ||
|
|
||
| var output = new BaselineOutput { | ||
| SessionId = sessionId, | ||
| EvalRunId = _evalRunId, | ||
| Model = model, | ||
| Chain = chain, | ||
| Questions = _questions, | ||
| Failures = _failures, | ||
| Retrospective = _retrospective, | ||
| Totals = EvalUsage.Sum(usages), | ||
| TotalElapsedMs = (long)time.GetElapsedTime(_started).TotalMilliseconds | ||
| }; | ||
|
|
||
| try { | ||
| BaselineOutputWriter.Write(path, output); | ||
| } catch (Exception ex) { | ||
| // Throwing here would rob the inner observer of its one terminal callback, and swallowing | ||
| // the failure would report a baseline that was never written — flag it so the run fails. | ||
| WriteFailed = true; | ||
| inner.OnInfo($"failed to write baseline output to '{path}': {ex.Message}"); | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| using System.Text.Json.Serialization; | ||
|
|
||
| namespace Capacitor.Cli.Core.Eval; | ||
|
|
||
| /// <summary>The full record <c>kcap eval --baseline-out</c> writes for one run. Snake_case keys are | ||
| /// a cross-repo contract with the server's parsing mirror — do not rename without updating both.</summary> | ||
| public sealed record BaselineOutput { | ||
| [JsonPropertyName("session_id")] public string SessionId { get; init; } = ""; | ||
| [JsonPropertyName("eval_run_id")] public string EvalRunId { get; init; } = ""; | ||
| [JsonPropertyName("model")] public string Model { get; init; } = ""; | ||
| [JsonPropertyName("chain")] public bool Chain { get; init; } | ||
| [JsonPropertyName("questions")] public IReadOnlyList<BaselineQuestionOutput> Questions { get; init; } = []; | ||
| [JsonPropertyName("failures")] public IReadOnlyList<BaselineQuestionFailure> Failures { get; init; } = []; | ||
| [JsonPropertyName("retrospective")] public BaselineRetrospectiveOutput? Retrospective { get; init; } | ||
| [JsonPropertyName("totals")] public EvalUsage Totals { get; init; } = new(); | ||
| [JsonPropertyName("total_elapsed_ms")] public long TotalElapsedMs { get; init; } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| using System.Text.Json; | ||
|
|
||
| namespace Capacitor.Cli.Core.Eval; | ||
|
|
||
| /// <summary>Writes a completed <see cref="BaselineOutput"/> as JSON via the source-generated | ||
| /// <see cref="CapacitorJsonContext"/> — the AOT-compiled CLI has no reflection-based fallback.</summary> | ||
| public static class BaselineOutputWriter { | ||
| public static void Write(string path, BaselineOutput output) { | ||
| var json = JsonSerializer.Serialize(output, CapacitorJsonContext.Default.BaselineOutput); | ||
| File.WriteAllText(path, json); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| using System.Text.Json.Serialization; | ||
|
|
||
| namespace Capacitor.Cli.Core.Eval; | ||
|
|
||
| /// <summary>A question that failed before producing a verdict, recorded in a <c>--baseline-out</c> | ||
| /// file so a failed judge is not silently absent from the run's record. It carries no route, usage | ||
| /// or timing — only which question failed and why. Snake_case keys are a cross-repo contract with | ||
| /// the server's parsing mirror — do not rename without updating both.</summary> | ||
| public sealed record BaselineQuestionFailure { | ||
| [JsonPropertyName("question_id")] public string QuestionId { get; init; } = ""; | ||
| [JsonPropertyName("category")] public string Category { get; init; } = ""; | ||
| [JsonPropertyName("reason")] public string Reason { get; init; } = ""; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| using System.Text.Json.Serialization; | ||
|
|
||
| namespace Capacitor.Cli.Core.Eval; | ||
|
|
||
| /// <summary>One question's record in a <c>--baseline-out</c> file: how <c>kcap eval</c> routed it, | ||
| /// its judge usage, how many times the runner was called, and wall time. Snake_case keys are a | ||
| /// cross-repo contract with the server's parsing mirror — do not rename without updating both.</summary> | ||
| public sealed record BaselineQuestionOutput { | ||
| [JsonPropertyName("question_id")] public string QuestionId { get; init; } = ""; | ||
| [JsonPropertyName("route")] public string Route { get; init; } = ""; | ||
| [JsonPropertyName("usage")] public EvalUsage Usage { get; init; } = new(); | ||
| [JsonPropertyName("calls")] public int Calls { get; init; } | ||
| [JsonPropertyName("elapsed_ms")] public long ElapsedMs { get; init; } | ||
| } |
12 changes: 12 additions & 0 deletions
12
src/Capacitor.Cli.Core/Eval/BaselineRetrospectiveOutput.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| using System.Text.Json.Serialization; | ||
|
|
||
| namespace Capacitor.Cli.Core.Eval; | ||
|
|
||
| /// <summary>The retrospective synthesis record in a <c>--baseline-out</c> file — its judge usage, | ||
| /// runner invocation count, and wall time. Snake_case keys are a cross-repo contract with the | ||
| /// server's parsing mirror — do not rename without updating both.</summary> | ||
| public sealed record BaselineRetrospectiveOutput { | ||
| [JsonPropertyName("usage")] public EvalUsage Usage { get; init; } = new(); | ||
| [JsonPropertyName("calls")] public int Calls { get; init; } | ||
| [JsonPropertyName("elapsed_ms")] public long ElapsedMs { get; init; } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| using System.Text.Json.Serialization; | ||
| using Capacitor.Cli.Core.Harness.Claude; | ||
|
|
||
| namespace Capacitor.Cli.Core.Eval; | ||
|
|
||
| /// <summary>Token usage and self-reported cost for one judge invocation, or a run's total, as | ||
| /// written to <c>--baseline-out</c>. Snake_case keys are a cross-repo contract with the server's | ||
| /// parsing mirror — do not rename without updating both.</summary> | ||
| public sealed record EvalUsage { | ||
| [JsonPropertyName("input_tokens")] public long InputTokens { get; init; } | ||
| [JsonPropertyName("output_tokens")] public long OutputTokens { get; init; } | ||
| [JsonPropertyName("cache_read_tokens")] public long CacheReadTokens { get; init; } | ||
| [JsonPropertyName("cache_write_tokens")] public long CacheWriteTokens { get; init; } | ||
| [JsonPropertyName("reported_cost_usd")] public double? ReportedCostUsd { get; init; } | ||
|
|
||
| internal static EvalUsage FromResult(ClaudeCliResult result) => new() { | ||
| InputTokens = result.InputTokens, | ||
| OutputTokens = result.OutputTokens, | ||
| CacheReadTokens = result.CacheReadTokens, | ||
| CacheWriteTokens = result.CacheWriteTokens, | ||
| ReportedCostUsd = result.CostUsd | ||
| }; | ||
|
|
||
| /// <summary>Sums token counts across every entry; cost sums only the entries that reported one, | ||
| /// staying null when none did.</summary> | ||
| public static EvalUsage Sum(IEnumerable<EvalUsage> usages) { | ||
| long inputTokens = 0, outputTokens = 0, cacheReadTokens = 0, cacheWriteTokens = 0; | ||
| double? cost = null; | ||
|
|
||
| foreach (var u in usages) { | ||
| inputTokens += u.InputTokens; | ||
| outputTokens += u.OutputTokens; | ||
| cacheReadTokens += u.CacheReadTokens; | ||
| cacheWriteTokens += u.CacheWriteTokens; | ||
| if (u.ReportedCostUsd is { } c) cost = (cost ?? 0) + c; | ||
| } | ||
|
|
||
| return new EvalUsage { | ||
| InputTokens = inputTokens, | ||
| OutputTokens = outputTokens, | ||
| CacheReadTokens = cacheReadTokens, | ||
| CacheWriteTokens = cacheWriteTokens, | ||
| ReportedCostUsd = cost | ||
| }; | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
2. Server eval tests stop compiling
🔗 Cross-repo conflict≡ CorrectnessAgent Prompt
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools