You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

LangGraph课程生成流程中course_reviewer与save_course步骤标题和描述不一致问题排查求助

LangGraph课程生成流程中course_reviewer与save_course步骤标题和描述不一致问题排查求助

最近几天一直在折腾LangGraph,还属于刚入门的新手。学习过程中我做了一个LangGraph代理,用来根据用户的课程想法和目标受众生成课程内容。前面的步骤都顺顺利利的,直到评估环节——但每次走到save_course步骤时,发现传入的课程标题和描述,竟然和course_reviewer里用的不是同一版!有没有大佬能帮我分析下原因?是不是我在流程设计或者工具调用上写错了什么?

以下是我写的完整代码(基于NestJS+LangGraph+Google Gemini):

import { AIMessage, BaseMessage, HumanMessage } from '@langchain/core/messages';
import { DynamicStructuredTool } from '@langchain/core/tools';
import { Annotation, MemorySaver, StateGraph } from '@langchain/langgraph';
import { ToolNode } from '@langchain/langgraph/prebuilt';
import { Injectable } from '@nestjs/common';
import z from 'zod';
import { ChatGoogleGenerativeAI } from '@langchain/google-genai';

type courseData = {
  user_thought: string;
  targeted_audience: string;
  enable_knowledge_store: boolean;
  knowledge_store_id: string;
  course_structure_id?: string;
};

@Injectable()
export class LangCourseService {
  private readonly app;

  constructor() {
    this.app = this.buildflow();
  }

  private buildflow() {
    const CourseMetadataAgentAnnotation = Annotation.Root({
      messages: Annotation<BaseMessage[]>,
      userInput: Annotation<string>,
      courseInput: Annotation<courseData>,
      courseScore: Annotation<number>,
      courseOutput: Annotation<{
        course_title: string;
        short_Desc: string;
        detailed_desc: string;
        tags: string[];
      }>,
    });

    const course_reviewer = new DynamicStructuredTool({
      name: 'course_reviewer',
      description: ``,
      schema: z.object({
        course_title: z.string().describe('Title of the course'),
        course_description: z.string().describe('Description of the course'),
      }),
      func: async ({ course_title, course_description }) => {
        console.log("Course Title", course_title)
        console.log("course description", course_description)
        const prompt = `Evaluate the following course based on clarity, relevance, and detail. Return a score between 1 and 10, and provide a short comment.
Course Title: ${course_title}
Course Description: ${course_description}
Format:
Score: <number>
Comment: <text> `
        const agents = await model.invoke([new HumanMessage(prompt)]);
        const text = agents.content as string;
        const match = text.match(/Score:\s*(\d+)/i);
        const commentMatch = text.match(/Comment:\s*(.*)/i);
        console.log("Score",match ? parseInt(match[1]) : 0);
        return {
          score: match ? parseInt(match[1]) : 0,
          comments: commentMatch ? commentMatch[1] : 'No comment',
        };
      },
    });

    const save_course = new DynamicStructuredTool({
      name: 'save_course',
      description: `Saves the course details to the database.
* PURPOSE: Saves the course details to the database.
* INPUT: A data object containing course details.
* OUTPUT: An object with the course ID and a success message.
* EXAMPLES:
- "Save the course details to the database."
- "Store the course information in the system."`,
      schema: z.object({
        course_title: z.string().describe('Title of the course'),
        course_description: z.string().describe('Description of the course'),
      }),
      func: async ({ course_title, course_description }) => {
        console.log('Saving course:', { course_title, course_description });
        return {
          course_id: 'course-12345',
          message: 'Course saved successfully',
        };
      },
    });

    const shouldContinue = ({ messages, }: typeof CourseMetadataAgentAnnotation.State) => {
      const lastMessage = messages[messages.length - 1] as AIMessage;
      return lastMessage.tool_calls?.length ? 'tools' : '__end__';
    };

    const tools = [course_reviewer, save_course];
    const tool = new ToolNode(tools);

    const model = new ChatGoogleGenerativeAI({
      apiKey: 'AIzaSyBs1GoNE5PScHx8_U6ErN2Y2CdNVutsIjQ',
      model: 'models/gemini-2.5-flash',
    });

    const chatModel = async (
      state: typeof CourseMetadataAgentAnnotation.State,
    ) => {
      const messages: BaseMessage[] = [
        new AIMessage(`You are a helpful course generation assistant. Your job is to:
1. Generate a course based on the user's course idea and targeted audience.
2. Use the "course_reviewer" tool to evaluate the generated course.
3. If the score is less than or equal to 5, regenerate a new course and re-evaluate.
4. Repeat until the score is greater than 5.
5. Once a good-quality course is achieved (score > 5), call the "save_course" tool with the course details.
Always ensure:
- The course is well-structured, relevant to the audience, and clearly described.
- You follow the tool call format properly and wait for score feedback before proceeding.
- Always use the same course content that was evaluated.`),
        ...state.messages
      ];
      const response = await model.bindTools(tools).invoke(messages);
      return {
        ...state,
        messages: [...state.messages, response],
      };
    };

    const checkpointSaver = new MemorySaver();

    return new StateGraph(CourseMetadataAgentAnnotation)
      .addNode('chatModel', chatModel)
      .addNode('tools', tool)
      .addEdge('__start__', 'chatModel')
      .addEdge('tools', 'chatModel')
      .addConditionalEdges('chatModel', shouldContinue)
      .compile({ checkpointer: checkpointSaver });
  }
}

我本来的逻辑很明确:生成课程后用course_reviewer打分,如果分≤5就重新生成再评估,直到分>5,然后调用save_course把那版通过评估的课程内容存起来——而且在给大模型的提示里还特意强调了"Always use the same course content that was evaluated",但实际运行时,save_course的console里打出来的标题和描述,就是和course_reviewer里的不一样,完全摸不着头脑哪里出问题了...

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.07 11:24:38