council.ts 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. import { shortModelLabel } from '../utils/session';
  2. import { type AgentDefinition, resolvePrompt } from './orchestrator';
  3. // NOTE: Councillor system prompts live in the councillor agent factory.
  4. // The format functions below only structure the USER message content — the
  5. // agent factory provides the system prompt.
  6. const COUNCIL_AGENT_PROMPT = `You are the Council agent — a multi-LLM \
  7. orchestration system that runs consensus across multiple models.
  8. **Tool**: You have access to the \`council_session\` tool.
  9. **When to use**:
  10. - When invoked by a user with a request
  11. - When you want multiple expert opinions on a complex problem
  12. - When higher confidence is needed through model consensus
  13. **Usage**:
  14. 1. Call the \`council_session\` tool with the user's prompt
  15. 2. Optionally specify a preset (default: "default")
  16. 3. Receive the councillor responses formatted for synthesis
  17. 4. Synthesize the optimal final answer from the councillor responses
  18. 5. Present the synthesized result to the user
  19. **Synthesis Guidelines**:
  20. When you receive councillor responses, synthesize them into the optimal final answer:
  21. - Review all councillor responses thoroughly and create the best possible answer
  22. - Credit specific insights from individual councillors by name (e.g., "alpha noted that...", "beta suggested...")
  23. - Clearly explain your reasoning for the chosen approach
  24. - Be transparent about trade-offs when different approaches have valid pros/cons
  25. - Note any remaining uncertainties or areas where further investigation is needed
  26. - If councillors disagree, explain the resolution and your reasoning
  27. - Acknowledge if consensus was impossible and explain why
  28. - Don't just average responses — choose the best approach and improve upon it
  29. - Present the synthesized solution with relevant code examples, concrete details, and clear explanations
  30. **Behavior**:
  31. - Delegate requests directly to council_session
  32. - Don't pre-analyze or filter the prompt before calling council_session
  33. - Synthesize the councillor results into a comprehensive, coherent answer
  34. - Include attribution for valuable insights from specific councillors
  35. - If councillors disagree, explain why you chose one approach over another`;
  36. export function createCouncilAgent(
  37. model: string,
  38. customPrompt?: string,
  39. customAppendPrompt?: string,
  40. ): AgentDefinition {
  41. const prompt = resolvePrompt(
  42. COUNCIL_AGENT_PROMPT,
  43. customPrompt,
  44. customAppendPrompt,
  45. );
  46. const definition: AgentDefinition = {
  47. name: 'council',
  48. description:
  49. 'Multi-LLM council agent that synthesizes responses from multiple models for higher-quality outputs',
  50. config: {
  51. temperature: 0.1,
  52. prompt,
  53. },
  54. };
  55. // Council's model comes from config override or is resolved at
  56. // runtime; only set if a non-empty string is provided.
  57. if (model) {
  58. definition.config.model = model;
  59. }
  60. return definition;
  61. }
  62. /**
  63. * Build the prompt for a specific councillor session.
  64. *
  65. * Returns the raw user prompt — the agent factory (councillor.ts) provides
  66. * the system prompt with tool-aware instructions. No duplication.
  67. *
  68. * If a per-councillor prompt override is provided, it is prepended as
  69. * role/guidance context before the user's question.
  70. */
  71. export function formatCouncillorPrompt(
  72. userPrompt: string,
  73. councillorPrompt?: string,
  74. ): string {
  75. if (!councillorPrompt) return userPrompt;
  76. return `${councillorPrompt}\n\n---\n\n${userPrompt}`;
  77. }
  78. /**
  79. * Format councillor results for the council agent to synthesize.
  80. *
  81. * Formats councillor results as structured data that the council agent
  82. * (which called the tool) will receive as the tool response. The council
  83. * agent's system prompt contains synthesis instructions.
  84. * Returns a special message when all councillors failed to produce output.
  85. */
  86. export function formatCouncillorResults(
  87. originalPrompt: string,
  88. councillorResults: Array<{
  89. name: string;
  90. model: string;
  91. status: string;
  92. result?: string;
  93. error?: string;
  94. }>,
  95. ): string {
  96. const completedWithResults = councillorResults.filter(
  97. (cr) => cr.status === 'completed' && cr.result,
  98. );
  99. const councillorSection = completedWithResults
  100. .map((cr) => {
  101. const shortModel = shortModelLabel(cr.model);
  102. return `**${cr.name}** (${shortModel}):\n${cr.result}`;
  103. })
  104. .join('\n\n');
  105. const failedSection = councillorResults
  106. .filter((cr) => cr.status !== 'completed')
  107. .map((cr) => `**${cr.name}**: ${cr.status} — ${cr.error ?? 'Unknown'}`)
  108. .join('\n');
  109. // Defensive guard: caller (runCouncil) short-circuits when all fail,
  110. // but this function may be reused in other contexts.
  111. if (completedWithResults.length === 0) {
  112. const errorDetails = councillorResults
  113. .map(
  114. (cr) =>
  115. `**${cr.name}** (${shortModelLabel(cr.model)}): ${cr.status} — ${cr.error ?? 'Unknown'}`,
  116. )
  117. .join('\n');
  118. return `---\n\n**Original Prompt**:\n${originalPrompt}\n\n---\n\n**Councillor Responses**:\nAll councillors failed to produce output:\n${errorDetails}\n\nPlease generate a response based on the original prompt alone.`;
  119. }
  120. let prompt = `---\n\n**Original Prompt**:\n${originalPrompt}\n\n---\n\n**Councillor Responses**:\n${councillorSection}`;
  121. if (failedSection) {
  122. prompt += `\n\n---\n\n**Failed/Timed-out Councillors**:\n${failedSection}`;
  123. }
  124. prompt += '\n\n---\n\nSynthesize the optimal response based on the above.';
  125. return prompt;
  126. }