mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-08-31 08:56:48 +00:00
✂️ fix: Deduplicate Skill Bodies Across Fresh Primes and History (#13610)
When a skill is primed fresh this turn (manual $-popover or always-apply) AND also appears in history as a `skill` tool_call, its SKILL.md body was injected twice — once by injectSkillPrimes and once reconstructed by formatAgentMessages. - add `collectFreshSkillPrimeNames` helper (packages/api) — union of manual + always-apply prime names - client.js: pass the set as `skipSkillBodyNames` to formatAgentMessages for both the initialMessages and memoryMessages paths so the body reconstructs once. Names not primed this turn still reconstruct (sticky manual re-prime). Requires `@librechat/agents` with `skipSkillBodyNames` support; the published dist silently ignores the unknown option until upgraded.
This commit is contained in:
parent
d7fc4a73a3
commit
793cbd49f0
3 changed files with 98 additions and 3 deletions
|
|
@ -45,6 +45,7 @@ import {
|
|||
resolveAlwaysApplySkills,
|
||||
injectManualSkillPrimes,
|
||||
injectSkillPrimes,
|
||||
collectFreshSkillPrimeNames,
|
||||
extractManualSkills,
|
||||
isSkillPrimeMessage,
|
||||
buildSkillPrimeContentParts,
|
||||
|
|
@ -2247,3 +2248,45 @@ describe('injectSkillPrimes', () => {
|
|||
expect(result.inserted).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('collectFreshSkillPrimeNames', () => {
|
||||
it('returns the union of manual + always-apply prime names', () => {
|
||||
const names = collectFreshSkillPrimeNames({
|
||||
manualSkillPrimes: [{ name: 'pdf-analyzer' }, { name: 'code-review' }],
|
||||
alwaysApplySkillPrimes: [{ name: 'clickhouse-best-practices' }],
|
||||
});
|
||||
expect(names).toEqual(new Set(['pdf-analyzer', 'code-review', 'clickhouse-best-practices']));
|
||||
});
|
||||
|
||||
it('dedupes a skill that is both manual and always-apply', () => {
|
||||
const names = collectFreshSkillPrimeNames({
|
||||
manualSkillPrimes: [{ name: 'clickhouse-best-practices' }],
|
||||
alwaysApplySkillPrimes: [{ name: 'clickhouse-best-practices' }],
|
||||
});
|
||||
expect(names.size).toBe(1);
|
||||
expect(names.has('clickhouse-best-practices')).toBe(true);
|
||||
});
|
||||
|
||||
it('handles undefined / empty inputs', () => {
|
||||
expect(collectFreshSkillPrimeNames({}).size).toBe(0);
|
||||
expect(
|
||||
collectFreshSkillPrimeNames({
|
||||
manualSkillPrimes: [],
|
||||
alwaysApplySkillPrimes: [],
|
||||
}).size,
|
||||
).toBe(0);
|
||||
});
|
||||
|
||||
it('collects names from only one side when the other is absent', () => {
|
||||
expect(
|
||||
collectFreshSkillPrimeNames({
|
||||
alwaysApplySkillPrimes: [{ name: 'only-always' }],
|
||||
}),
|
||||
).toEqual(new Set(['only-always']));
|
||||
expect(
|
||||
collectFreshSkillPrimeNames({
|
||||
manualSkillPrimes: [{ name: 'only-manual' }],
|
||||
}),
|
||||
).toEqual(new Set(['only-manual']));
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1087,6 +1087,32 @@ export function injectManualSkillPrimes(
|
|||
return { initialMessages, indexTokenCountMap, inserted: numPrimes, insertIdx };
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects the set of skill names primed fresh this turn — the union of manual
|
||||
* ($-popover) and always-apply (frontmatter) primes. Passed to
|
||||
* `formatAgentMessages` as `skipSkillBodyNames` so a skill that is BOTH primed
|
||||
* this turn AND present in history (as a `skill` tool_call) has its SKILL.md
|
||||
* body injected exactly once — by the fresh prime via `injectSkillPrimes` — and
|
||||
* is NOT also reconstructed from history. Names absent from this set still
|
||||
* reconstruct from history, preserving sticky manual re-priming across turns.
|
||||
*/
|
||||
export function collectFreshSkillPrimeNames({
|
||||
manualSkillPrimes,
|
||||
alwaysApplySkillPrimes,
|
||||
}: {
|
||||
manualSkillPrimes?: Pick<ResolvedManualSkill, 'name'>[];
|
||||
alwaysApplySkillPrimes?: Pick<ResolvedAlwaysApplySkill, 'name'>[];
|
||||
}): Set<string> {
|
||||
const names = new Set<string>();
|
||||
for (const prime of manualSkillPrimes ?? []) {
|
||||
names.add(prime.name);
|
||||
}
|
||||
for (const prime of alwaysApplySkillPrimes ?? []) {
|
||||
names.add(prime.name);
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
export interface InjectSkillPrimesParams {
|
||||
/** Formatted LangChain messages produced by `formatAgentMessages`. Mutated in place. */
|
||||
initialMessages: BaseMessage[];
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue