council.ts 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. import { shortModelLabel } from '../utils/session';
  2. import { type AgentDefinition, resolvePrompt } from './orchestrator';
  3. // NOTE: Councillor and master system prompts live in their respective agent
  4. // factories (councillor.ts, council-master.ts). The format functions below
  5. // only structure the USER message content — the agent factory provides the
  6. // system prompt. This avoids duplicate system prompts (Oracle finding #1/#2).
  7. const COUNCIL_AGENT_PROMPT = `You are the Council agent — a multi-LLM \
  8. orchestration system that runs consensus across multiple models.
  9. **Tool**: You have access to the \`council_session\` tool.
  10. **When to use**:
  11. - When invoked by a user with a request
  12. - When you want multiple expert opinions on a complex problem
  13. - When higher confidence is needed through model consensus
  14. **Usage**:
  15. 1. Call the \`council_session\` tool with the user's prompt
  16. 2. Optionally specify a preset (default: "default")
  17. 3. Receive the synthesized response from the council master
  18. 4. Present the result to the user
  19. **Behavior**:
  20. - Delegate requests directly to council_session
  21. - Don't pre-analyze or filter the prompt
  22. - Present the synthesized result verbatim — do not re-summarize or condense
  23. - Briefly explain the consensus if requested`;
  24. export function createCouncilAgent(
  25. model: string,
  26. customPrompt?: string,
  27. customAppendPrompt?: string,
  28. ): AgentDefinition {
  29. const prompt = resolvePrompt(
  30. COUNCIL_AGENT_PROMPT,
  31. customPrompt,
  32. customAppendPrompt,
  33. );
  34. const definition: AgentDefinition = {
  35. name: 'council',
  36. description:
  37. 'Multi-LLM council agent that synthesizes responses from multiple models for higher-quality outputs',
  38. config: {
  39. temperature: 0.1,
  40. prompt,
  41. },
  42. };
  43. // Council's model comes from config override or is resolved at
  44. // runtime; only set if a non-empty string is provided.
  45. if (model) {
  46. definition.config.model = model;
  47. }
  48. return definition;
  49. }
  50. /**
  51. * Build the prompt for a specific councillor session.
  52. *
  53. * Returns the raw user prompt — the agent factory (councillor.ts) provides
  54. * the system prompt with tool-aware instructions. No duplication.
  55. *
  56. * If a per-councillor prompt override is provided, it is prepended as
  57. * role/guidance context before the user's question.
  58. */
  59. export function formatCouncillorPrompt(
  60. userPrompt: string,
  61. councillorPrompt?: string,
  62. ): string {
  63. if (!councillorPrompt) return userPrompt;
  64. return `${councillorPrompt}\n\n---\n\n${userPrompt}`;
  65. }
  66. /**
  67. * Build the synthesis prompt for the council master.
  68. *
  69. * Formats councillor results as structured data — the agent factory
  70. * (council-master.ts) provides the system prompt with synthesis instructions.
  71. * Returns a special prompt when all councillors failed to produce output.
  72. *
  73. * @param masterPrompt - Optional per-master guidance appended to the synthesis.
  74. */
  75. export function formatMasterSynthesisPrompt(
  76. originalPrompt: string,
  77. councillorResults: Array<{
  78. name: string;
  79. model: string;
  80. status: string;
  81. result?: string;
  82. error?: string;
  83. }>,
  84. masterPrompt?: string,
  85. ): string {
  86. const completedWithResults = councillorResults.filter(
  87. (cr) => cr.status === 'completed' && cr.result,
  88. );
  89. const councillorSection = completedWithResults
  90. .map((cr) => {
  91. const shortModel = shortModelLabel(cr.model);
  92. return `**${cr.name}** (${shortModel}):\n${cr.result}`;
  93. })
  94. .join('\n\n');
  95. const failedSection = councillorResults
  96. .filter((cr) => cr.status !== 'completed')
  97. .map((cr) => `**${cr.name}**: ${cr.status} — ${cr.error ?? 'Unknown'}`)
  98. .join('\n');
  99. if (completedWithResults.length === 0) {
  100. return `---\n\n**Original Prompt**:\n${originalPrompt}\n\n---\n\n**Councillor Responses**:\nAll councillors failed to produce output. Please generate a response based on the original prompt alone.`;
  101. }
  102. let prompt = `---\n\n**Original Prompt**:\n${originalPrompt}\n\n---\n\n**Councillor Responses**:\n${councillorSection}`;
  103. if (failedSection) {
  104. prompt += `\n\n---\n\n**Failed/Timed-out Councillors**:\n${failedSection}`;
  105. }
  106. prompt += '\n\n---\n\nSynthesize the optimal response based on the above.';
  107. if (masterPrompt) {
  108. prompt += `\n\n---\n\n**Master Guidance**:\n${masterPrompt}`;
  109. }
  110. return prompt;
  111. }