langgraph-studio icon indicating copy to clipboard operation
langgraph-studio copied to clipboard

TypeError: Type is not JSON serializable: ModelMetaclass

Open austinmw opened this issue 1 year ago • 1 comments

Hi, I am using tool calling functions with dynamically created Pydantic BaseModel's as type hints. For example:


def configure_workout_plan_template(state: AgentState, config: dict) -> AgentState:
    num_weeks = config.get("configurable", {}).get("num_weeks", 4)
    logger.debug(f"Dynamically configuring WorkoutPlan template with {num_weeks} weeks")
    workout_plan_template = create_workout_plan_template(num_weeks)

    def create_workout_plan(request: workout_plan_template) -> dict:  # type: ignore[valid-type]
        """Return a generated workout program"""
        return request.dict()

    tools = (create_workout_plan,)
    logger.debug(f"Tools available: {tools}")

    state["workout_planner_tools"] = tools
    state["workout_plan_template"] = workout_plan_template

    return state


def call_model(state: AgentState, config: dict) -> AgentState:
    """Call the model with the messages"""
    messages = state["messages"]
    default_model_id = config.get("configurable", {}).get("default_model_id", "anthropic.claude-3-haiku-20240307-v1:0")
    tool_choice = config.get("configurable", {}).get("planner_tool_choice", "auto")
    tools = state["workout_planner_tools"]
    model = _get_model(default_model_id, tools, tool_choice)
    response = model.invoke([SystemMessage(SYSTEM_PROMPT), *messages])
    state["messages"] = [response]
    return state


def create_workout_plan_template(num_weeks: int = 2) -> Type[BaseModel]:
    """Create a generic workout plan template"""

    class Day(BaseModel):
        """A single day of the workout program"""

        description: str = Field(
            description="Description of the workout for this day.",
        )
        duration: int = Field(
            ge=0,
            description="Duration of the workout in minutes. Every day requires a duration, even rest days (use 0).",
        )

    class Week(BaseModel):
        """7 days of the workout program (may inlcude rest days)"""

        monday: Day
        tuesday: Day
        wednesday: Day
        thursday: Day
        friday: Day
        saturday: Day
        sunday: Day

    class WorkoutPlan(BaseModel):
        """Multi-week workout program"""
        weeks: list[Week] = Field(description=f"List of {num_weeks} weeks to structure the workout program.")

        # Use validator instead of `conlist` to provide a custom error message
        @validator("weeks", allow_reuse=True)
        def validate_days_length(cls, value) -> Any:
            actual_weeks = len(value)
            if actual_weeks != num_weeks:
                raise ValueError(  # noqa: TRY003
                    f"Invalid number of workout weeks. Expected {num_weeks} weeks, but got {actual_weeks} weeks. "
                    "Try the tool call again!"
                )
            return value

    return WorkoutPlan

I'm able to run my graph successfully end-to-end using graph.invoke, but when I try to execute it in LangGraph Studio I get this error:

TypeError: Type is not JSON serializable: ModelMetaclass

I'm not sure if there's a more recommended way for me to implement dynamic BaseModel's which LangGraph Studio already supports, or if it's currently not possible to do this?

austinmw avatar Aug 22 '24 20:08 austinmw