planner.ts 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. import { BaseAgent, type BaseAgentOptions, type ExtraAgentOptions } from './base';
  2. import { createLogger } from '@src/background/log';
  3. import { z } from 'zod';
  4. import type { AgentOutput } from '../types';
  5. import { HumanMessage } from '@langchain/core/messages';
  6. import { Actors, ExecutionState } from '../event/types';
  7. import {
  8. ChatModelAuthError,
  9. ChatModelForbiddenError,
  10. isAuthenticationError,
  11. isForbiddenError,
  12. LLM_FORBIDDEN_ERROR_MESSAGE,
  13. } from './errors';
  14. const logger = createLogger('PlannerAgent');
  15. // Define Zod schema for planner output
  16. export const plannerOutputSchema = z.object({
  17. observation: z.string(),
  18. challenges: z.string(),
  19. done: z.union([
  20. z.boolean(),
  21. z.string().transform(val => {
  22. if (val.toLowerCase() === 'true') return true;
  23. if (val.toLowerCase() === 'false') return false;
  24. throw new Error('Invalid boolean string');
  25. }),
  26. ]),
  27. next_steps: z.string(),
  28. reasoning: z.string(),
  29. web_task: z.union([
  30. z.boolean(),
  31. z.string().transform(val => {
  32. if (val.toLowerCase() === 'true') return true;
  33. if (val.toLowerCase() === 'false') return false;
  34. throw new Error('Invalid boolean string');
  35. }),
  36. ]),
  37. });
  38. export type PlannerOutput = z.infer<typeof plannerOutputSchema>;
  39. export class PlannerAgent extends BaseAgent<typeof plannerOutputSchema, PlannerOutput> {
  40. constructor(options: BaseAgentOptions, extraOptions?: Partial<ExtraAgentOptions>) {
  41. super(plannerOutputSchema, options, { ...extraOptions, id: 'planner' });
  42. }
  43. async execute(): Promise<AgentOutput<PlannerOutput>> {
  44. try {
  45. this.context.emitEvent(Actors.PLANNER, ExecutionState.STEP_START, 'Planning...');
  46. // get all messages from the message manager, state message should be the last one
  47. const messages = this.context.messageManager.getMessages();
  48. // Use full message history except the first one
  49. const plannerMessages = [this.prompt.getSystemMessage(), ...messages.slice(1)];
  50. // Remove images from last message if vision is not enabled for planner but vision is enabled
  51. if (!this.context.options.useVisionForPlanner && this.context.options.useVision) {
  52. const lastStateMessage = plannerMessages[plannerMessages.length - 1];
  53. let newMsg = '';
  54. if (Array.isArray(lastStateMessage.content)) {
  55. for (const msg of lastStateMessage.content) {
  56. if (msg.type === 'text') {
  57. newMsg += msg.text;
  58. }
  59. // Skip image_url messages
  60. }
  61. } else {
  62. newMsg = lastStateMessage.content;
  63. }
  64. plannerMessages[plannerMessages.length - 1] = new HumanMessage(newMsg);
  65. }
  66. const modelOutput = await this.invoke(plannerMessages);
  67. if (!modelOutput) {
  68. throw new Error('未能验证规划器输出');
  69. }
  70. this.context.emitEvent(Actors.PLANNER, ExecutionState.STEP_OK, modelOutput.next_steps);
  71. return {
  72. id: this.id,
  73. result: modelOutput,
  74. };
  75. } catch (error) {
  76. // Check if this is an authentication error
  77. if (isAuthenticationError(error)) {
  78. throw new ChatModelAuthError('Planner API鉴权失败。请验证您的API密钥', error);
  79. }
  80. if (isForbiddenError(error)) {
  81. throw new ChatModelForbiddenError(LLM_FORBIDDEN_ERROR_MESSAGE, error);
  82. }
  83. const errorMessage = error instanceof Error ? error.message : String(error);
  84. this.context.emitEvent(Actors.PLANNER, ExecutionState.STEP_FAIL, `策划失败: ${errorMessage}`);
  85. return {
  86. id: this.id,
  87. error: errorMessage,
  88. };
  89. }
  90. }
  91. }