-
Notifications
You must be signed in to change notification settings - Fork 614
fix(playground): chat turns show identical token counts across turns #5822
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
Open
Christian-Sidak
wants to merge
2
commits into
Agenta-AI:main
Choose a base branch
from
Christian-Sidak:fix/issue-5789
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
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
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
147 changes: 147 additions & 0 deletions
147
web/packages/agenta-playground/tests/unit/chatTurnTokenKeys.test.ts
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,147 @@ | ||
| /** | ||
| * Regression tests for issue #5789 -- token-usage numbers identical across | ||
| * every chat turn in a session. | ||
| * | ||
| * Three bugs caused the symptom: | ||
| * | ||
| * 1. `extractLogicalRowId` in webWorkerIntegration used a regex that only | ||
| * matched "lt-" prefixed logical IDs; current message IDs start with "msg-", | ||
| * so the function returned the full compound turn ID unchanged. The wrong | ||
| * ID was then used as the execution step key, making all turns collide on | ||
| * the last shared user message. | ||
| * | ||
| * 2. `handleExecutionResultAtom` in executionItems only searched for "-lt-" to | ||
| * locate the logical boundary in a compound "turn-<entityId>-<logicalId>" | ||
| * rowId. Same root cause; fallback always resolved to the last shared user | ||
| * message, so every turn wrote its result at the same key and therefore | ||
| * showed the same trace / same token counts. | ||
| * | ||
| * 3. `runStatusByRowEntityAtom` in selectors sliced result keys with | ||
| * `sepIdx + 5` instead of `sepIdx + 6`, leaving a leading ":" on the | ||
| * entityId segment and producing a malformed lookup key that could never | ||
| * match the UI-side `"${rowId}:${entityId}"` key. | ||
| */ | ||
|
|
||
| import {describe, expect, it} from "vitest" | ||
|
|
||
| import {extractLogicalRowId} from "../../src/state/execution/webWorkerIntegration" | ||
|
|
||
| // ── extractLogicalRowId (Bug 1 fix) ───────────────────────────────────────── | ||
|
|
||
| describe("extractLogicalRowId", () => { | ||
| it("returns a plain msg-<uuid> rowId unchanged (single-entity mode)", () => { | ||
| expect(extractLogicalRowId("msg-abc123")).toBe("msg-abc123") | ||
| }) | ||
|
|
||
| it("returns a plain lt-<id> rowId unchanged (legacy single-entity)", () => { | ||
| expect(extractLogicalRowId("lt-old-id")).toBe("lt-old-id") | ||
| }) | ||
|
|
||
| it("strips the turn-<entityId>- prefix from a current msg-<uuid> logical ID", () => { | ||
| expect(extractLogicalRowId("turn-rev1-msg-abc123")).toBe("msg-abc123") | ||
| }) | ||
|
|
||
| it("handles a UUID entity ID with hyphens in the compound rowId", () => { | ||
| // Entity IDs are hex UUIDs; "msg-" cannot appear in a UUID segment, | ||
| // so the sentinel search is unambiguous even when revId contains hyphens. | ||
| expect(extractLogicalRowId("turn-a1b2c3d4-e5f6-msg-00000000-dead-beef")).toBe( | ||
| "msg-00000000-dead-beef", | ||
| ) | ||
| }) | ||
|
|
||
| it("strips the turn-<entityId>- prefix from a legacy lt-<id> logical ID", () => { | ||
| expect(extractLogicalRowId("turn-rev1-lt-old-id")).toBe("lt-old-id") | ||
| }) | ||
|
|
||
| it("prefers msg- over lt- when both appear (current IDs take precedence)", () => { | ||
| // Contrived but guards against a mis-ordered search. | ||
| expect(extractLogicalRowId("turn-rev1-msg-foo-lt-bar")).toBe("msg-foo-lt-bar") | ||
| }) | ||
|
|
||
| it("returns a non-turn rowId unchanged (no sentinel found)", () => { | ||
| expect(extractLogicalRowId("step-abc")).toBe("step-abc") | ||
| expect(extractLogicalRowId("")).toBe("") | ||
| }) | ||
| }) | ||
|
|
||
| // ── resultsByKey entity extraction (Bug 3 fix) ────────────────────────────── | ||
| // | ||
| // `runStatusByRowEntityAtom` parses keys of the form "stepId:sess:entityId". | ||
| // The separator ":sess:" is 6 characters; the old code used `sepIdx + 5`, | ||
| // leaving a leading ":" on the entityId and producing a malformed map key. | ||
| // We test the parsing logic directly here as a pure string operation. | ||
|
|
||
| function parseResultKey(key: string): {stepId: string; entityId: string} | null { | ||
| const sepIdx = key.indexOf(":sess:") | ||
| if (sepIdx === -1) return null | ||
| return { | ||
| stepId: key.slice(0, sepIdx), | ||
| // fixed: was sepIdx + 5 (off-by-one) | ||
| entityId: key.slice(sepIdx + 6), | ||
| } | ||
| } | ||
|
|
||
| describe("resultsByKey entity extraction (sepIdx + 6)", () => { | ||
| it("extracts stepId and entityId from a well-formed result key", () => { | ||
| expect(parseResultKey("msg-abc:sess:entity123")).toEqual({ | ||
| stepId: "msg-abc", | ||
| entityId: "entity123", | ||
| }) | ||
| }) | ||
|
|
||
| it("handles a UUID entityId with hyphens", () => { | ||
| expect(parseResultKey("msg-abc:sess:a1b2-c3d4-e5f6")).toEqual({ | ||
| stepId: "msg-abc", | ||
| entityId: "a1b2-c3d4-e5f6", | ||
| }) | ||
| }) | ||
|
|
||
| it("returns null when the key has no :sess: segment", () => { | ||
| expect(parseResultKey("msg-abc:entity123")).toBeNull() | ||
| }) | ||
|
|
||
| it("produces a map key that matches the UI-side lookup", () => { | ||
| const key = "msg-abc:sess:entity123" | ||
| const parsed = parseResultKey(key)! | ||
| const mapKey = `${parsed.stepId}:${parsed.entityId}` | ||
| // The UI (TurnMessageAdapter) looks up by `"${rowId}:${entityId}"`. | ||
| expect(mapKey).toBe("msg-abc:entity123") | ||
| }) | ||
| }) | ||
|
|
||
| // ── logical rowId extraction in handleExecutionResultAtom (Bug 2 fix) ──────── | ||
| // | ||
| // In comparison mode rowId = "turn-<entityUUID>-<logicalId>". The old code | ||
| // only searched for "-lt-"; with current message IDs ("msg-<uuid>") the | ||
| // search always failed, leaving logicalRowId equal to the full compound string. | ||
| // flatById[compoundId] is always undefined, so the fallback fired and returned | ||
| // the LAST shared user message -- causing every turn to collide on that key. | ||
|
|
||
| function extractLogicalFromRowId(rowId: string): string { | ||
| const msgIndex = rowId.indexOf("-msg-") | ||
| const ltIndex = rowId.indexOf("-lt-") | ||
| const sepIndex = msgIndex >= 0 ? msgIndex : ltIndex | ||
| return sepIndex >= 0 ? rowId.slice(sepIndex + 1) : rowId | ||
| } | ||
|
|
||
| describe("handleExecutionResultAtom logical rowId extraction", () => { | ||
| it("returns a plain msg-<uuid> rowId unchanged", () => { | ||
| expect(extractLogicalFromRowId("msg-abc123")).toBe("msg-abc123") | ||
| }) | ||
|
|
||
| it("extracts the msg-<uuid> logical ID from a comparison-mode compound rowId", () => { | ||
| expect(extractLogicalFromRowId("turn-rev1-msg-abc123")).toBe("msg-abc123") | ||
| }) | ||
|
|
||
| it("extracts the lt-<id> logical ID from a legacy comparison-mode compound rowId", () => { | ||
| expect(extractLogicalFromRowId("turn-rev1-lt-old-id")).toBe("lt-old-id") | ||
| }) | ||
|
|
||
| it("handles a UUID entity ID with hyphens", () => { | ||
| expect(extractLogicalFromRowId("turn-a1b2-c3d4-msg-000-beef")).toBe("msg-000-beef") | ||
| }) | ||
|
|
||
| it("returns the rowId unchanged when no sentinel is present", () => { | ||
| expect(extractLogicalFromRowId("step-abc")).toBe("step-abc") | ||
| }) | ||
| }) |
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.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Use the earliest logical-ID delimiter.
The code selects
-msg-whenever it exists. This conflicts with the first-separator contract. Forturn-rev1-lt-old-msg-fragment, it extractsmsg-fragmentinstead oflt-old-msg-fragment. This can map a result to the wrong chat turn.web/packages/agenta-playground/src/state/execution/webWorkerIntegration.ts#L819-L822: select the lowest non-negative index of-msg-and-lt-.web/packages/agenta-playground/src/state/execution/executionItems.ts#L1473-L1476: apply the same earliest-index selection.web/packages/agenta-playground/tests/unit/chatTurnTokenKeys.test.ts#L56-L59: add a case where-lt-occurs before-msg-and expect the legacy logical ID.📍 Affects 3 files
web/packages/agenta-playground/src/state/execution/webWorkerIntegration.ts#L819-L822(this comment)web/packages/agenta-playground/src/state/execution/executionItems.ts#L1473-L1476web/packages/agenta-playground/tests/unit/chatTurnTokenKeys.test.ts#L56-L59