2025-03-28 11:40:46 +08:00
|
|
|
from __future__ import annotations
|
|
|
|
|
|
2025-03-31 22:20:29 +08:00
|
|
|
import os
|
|
|
|
|
|
2025-03-24 19:07:51 +08:00
|
|
|
from typing import Type, Annotated, Optional, TypedDict
|
|
|
|
|
from abc import abstractmethod
|
2025-03-28 11:40:46 +08:00
|
|
|
from dataclasses import dataclass, fields, field
|
2025-03-24 19:07:51 +08:00
|
|
|
|
2025-03-28 11:40:46 +08:00
|
|
|
from langchain_core.runnables import RunnableConfig
|
2025-03-25 05:40:07 +08:00
|
|
|
from langchain_core.messages import BaseMessage
|
2025-03-24 19:07:51 +08:00
|
|
|
from langgraph.graph.state import CompiledStateGraph
|
2025-03-25 05:40:07 +08:00
|
|
|
from langgraph.graph.message import add_messages
|
2025-03-24 19:07:51 +08:00
|
|
|
|
2025-03-29 17:33:09 +08:00
|
|
|
from src.config import SimpleConfig
|
|
|
|
|
|
2025-03-24 19:07:51 +08:00
|
|
|
|
|
|
|
|
class State(TypedDict):
|
|
|
|
|
"""
|
|
|
|
|
定义一个基础 State 供 各类 graph 继承, 其中:
|
|
|
|
|
1. messages 为所有 graph 的核心信息队列, 所有聊天工作流均应该将关键信息补充到此队列中;
|
|
|
|
|
2. history 为所有工作流单次启动时获取 history_len 的 messages 所用(节约成本, 及防止单轮对话 tokens 占用长度达到 llm 支持上限),
|
|
|
|
|
history 中的信息理应是可以被丢弃的.
|
|
|
|
|
"""
|
|
|
|
|
messages: Annotated[list[BaseMessage], add_messages]
|
|
|
|
|
history: Optional[list[BaseMessage]]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass(kw_only=True)
|
2025-03-29 17:33:09 +08:00
|
|
|
class Configuration(SimpleConfig):
|
2025-03-24 19:07:51 +08:00
|
|
|
"""
|
|
|
|
|
定义一个基础 Configuration 供 各类 graph 继承
|
|
|
|
|
"""
|
2025-03-28 11:40:46 +08:00
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def from_runnable_config(
|
|
|
|
|
cls, config: Optional[RunnableConfig] = None
|
|
|
|
|
) -> Configuration:
|
|
|
|
|
"""Create a Configuration instance from a RunnableConfig object."""
|
|
|
|
|
configurable = (config.get("configurable") or {}) if config else {}
|
|
|
|
|
_fields = {f.name for f in fields(cls) if f.init}
|
|
|
|
|
return cls(**{k: v for k, v in configurable.items() if k in _fields})
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def to_dict(cls):
|
2025-04-02 00:00:04 +08:00
|
|
|
# 创建一个实例来处理 default_factory
|
|
|
|
|
instance = cls()
|
|
|
|
|
return {f.name: getattr(instance, f.name) for f in fields(cls) if f.init}
|
2025-03-25 05:40:07 +08:00
|
|
|
|
2025-03-24 19:07:51 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class BaseAgent():
|
|
|
|
|
|
2025-03-31 22:20:29 +08:00
|
|
|
"""
|
|
|
|
|
定义一个基础 Agent 供 各类 graph 继承
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
name: str = field(default="base_agent")
|
|
|
|
|
description: str = field(default="base_agent")
|
|
|
|
|
config_schema: Configuration = Configuration
|
|
|
|
|
requirements: list[str]
|
|
|
|
|
|
2025-03-28 11:40:46 +08:00
|
|
|
def __init__(self, **kwargs):
|
2025-03-31 22:20:29 +08:00
|
|
|
self.check_requirements()
|
|
|
|
|
|
2025-04-01 22:16:07 +08:00
|
|
|
@classmethod
|
|
|
|
|
def get_info(cls):
|
|
|
|
|
return {
|
|
|
|
|
"name": cls.name,
|
|
|
|
|
"description": cls.description,
|
|
|
|
|
"config_schema": cls.config_schema.to_dict(),
|
|
|
|
|
"requirements": cls.requirements if hasattr(cls, "requirements") else [],
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-31 22:20:29 +08:00
|
|
|
def check_requirements(self):
|
|
|
|
|
if not hasattr(self, "requirements") or not self.requirements:
|
|
|
|
|
return
|
|
|
|
|
for requirement in self.requirements:
|
|
|
|
|
if requirement not in os.environ:
|
|
|
|
|
raise ValueError(f"{requirement} is not set")
|
|
|
|
|
|
|
|
|
|
def stream_values(self, messages: list[str], config_schema: RunnableConfig = None, **kwargs):
|
|
|
|
|
graph = self.get_graph(config_schema=config_schema, **kwargs)
|
|
|
|
|
for event in graph.stream({"messages": messages}, stream_mode="values", config=config_schema):
|
|
|
|
|
yield event["messages"]
|
|
|
|
|
|
|
|
|
|
def stream_messages(self, messages: list[str], config_schema: RunnableConfig = None, **kwargs):
|
|
|
|
|
graph = self.get_graph(config_schema=config_schema, **kwargs)
|
|
|
|
|
conf = self.config_schema.from_runnable_config(config_schema)
|
|
|
|
|
|
2025-04-02 00:00:04 +08:00
|
|
|
for msg, metadata in graph.stream({"messages": messages}, stream_mode="messages", config=config_schema):
|
|
|
|
|
# msg_type = msg.type
|
|
|
|
|
# return_keys = conf.get("return_keys", [])
|
|
|
|
|
# if not return_keys or msg_type in return_keys:
|
|
|
|
|
# yield msg, metadata
|
|
|
|
|
yield msg, metadata
|
2025-03-24 19:07:51 +08:00
|
|
|
|
|
|
|
|
@abstractmethod
|
2025-03-31 22:20:29 +08:00
|
|
|
def get_graph(self, **kwargs) -> CompiledStateGraph:
|
2025-03-24 19:07:51 +08:00
|
|
|
pass
|