-
Notifications
You must be signed in to change notification settings - Fork 3
State schema #85
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
State schema #85
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -28,6 +28,36 @@ | |
| DEFAULT_CHECKPOINT_WRITES_TABLE = "checkpoint_writes" | ||
| DEFAULT_CHECKPOINT_BLOBS_TABLE = "checkpoint_blobs" | ||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # OTS Schema 版本管理 | ||
| # | ||
| # 每张表独立计数,用于 SDK 写入端与 Core 读取端(funagent-core)的兼容性协调。 | ||
| # 每次 PutRow 时在 attribute_columns 中写入 _schema_version 字段。 | ||
| # Core 端读取时检查该字段,版本不匹配时打 WARN 日志并尽力解析。 | ||
| # 历史数据(无此字段)视为 v0。 | ||
| # | ||
| # 升级流程: | ||
| # 1. 递增对应表的 *_SCHEMA_VERSION 常量 | ||
| # 2. 在 PR 描述中记录变更的列名/类型/语义 | ||
| # 3. 通知 funagent-core 侧同步更新解析逻辑和版本常量 | ||
| # 4. 如涉及 breaking change,提供数据迁移指引 | ||
| # | ||
| # 兼容性规则: | ||
| # - 只加不删:新增列允许,删除/重命名列视为 breaking change | ||
| # - PK 不可变:主键结构永不改变 | ||
| # - 索引名不可变:Search Index 名称一旦确定不再修改 | ||
| # - 语义不可变:已有列的类型和含义不改变 | ||
| # --------------------------------------------------------------------------- | ||
|
|
||
| SCHEMA_VERSION_COLUMN = "_schema_version" | ||
|
|
||
| CONVERSATION_SCHEMA_VERSION = 1 | ||
| EVENT_SCHEMA_VERSION = 1 | ||
| STATE_SCHEMA_VERSION = 1 # state / app_state / user_state 共享 | ||
| CHECKPOINT_SCHEMA_VERSION = 1 | ||
|
||
| CHECKPOINT_WRITES_SCHEMA_VERSION = 1 | ||
| CHECKPOINT_BLOBS_SCHEMA_VERSION = 1 | ||
|
|
||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # 枚举 | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -45,6 +45,10 @@ | |
| ) | ||
|
|
||
| from agentrun.conversation_service.model import ( | ||
| CHECKPOINT_BLOBS_SCHEMA_VERSION, | ||
| CHECKPOINT_SCHEMA_VERSION, | ||
| CHECKPOINT_WRITES_SCHEMA_VERSION, | ||
| CONVERSATION_SCHEMA_VERSION, | ||
| ConversationEvent, | ||
| ConversationSession, | ||
| DEFAULT_APP_STATE_TABLE, | ||
|
|
@@ -58,6 +62,9 @@ | |
| DEFAULT_STATE_SEARCH_INDEX, | ||
| DEFAULT_STATE_TABLE, | ||
| DEFAULT_USER_STATE_TABLE, | ||
| EVENT_SCHEMA_VERSION, | ||
| SCHEMA_VERSION_COLUMN, | ||
| STATE_SCHEMA_VERSION, | ||
| StateData, | ||
| StateScope, | ||
| ) | ||
|
|
@@ -242,6 +249,20 @@ def init_search_index(self) -> None: | |
| self._create_conversation_search_index() | ||
| self._create_state_search_index() | ||
|
|
||
| async def init_conversation_search_index_async(self) -> None: | ||
| """仅创建 Conversation 多元索引(异步)。 | ||
|
|
||
| 索引已存在时跳过,可重复调用。 | ||
| """ | ||
| await self._create_conversation_search_index_async() | ||
|
|
||
| def init_conversation_search_index(self) -> None: | ||
| """仅创建 Conversation 多元索引(同步)。 | ||
|
|
||
| 索引已存在时跳过,可重复调用。 | ||
| """ | ||
| self._create_conversation_search_index() | ||
|
|
||
| async def init_checkpoint_tables_async(self) -> None: | ||
| """创建 LangGraph checkpoint 相关的 3 张表(异步)。 | ||
|
|
||
|
|
@@ -991,21 +1012,13 @@ async def _create_state_search_index_async(self) -> None: | |
| self._state_table, | ||
| ) | ||
| except OTSServiceError as e: | ||
| err_str = str(e).lower() | ||
| if "already exist" in err_str or ( | ||
| if "already exist" in str(e).lower() or ( | ||
| hasattr(e, "code") and e.code == "OTSObjectAlreadyExist" | ||
| ): | ||
| logger.warning( | ||
| "Search index %s already exists, skipping.", | ||
| self._state_search_index, | ||
| ) | ||
| elif "does not exist" in err_str and "table" in err_str: | ||
| logger.warning( | ||
| "Table %s does not exist, skipping search index creation" | ||
| " for %s.", | ||
| self._state_table, | ||
| self._state_search_index, | ||
| ) | ||
| else: | ||
| raise | ||
|
|
||
|
|
@@ -1084,21 +1097,13 @@ def _create_state_search_index(self) -> None: | |
| self._state_table, | ||
| ) | ||
| except OTSServiceError as e: | ||
| err_str = str(e).lower() | ||
| if "already exist" in err_str or ( | ||
| if "already exist" in str(e).lower() or ( | ||
| hasattr(e, "code") and e.code == "OTSObjectAlreadyExist" | ||
| ): | ||
| logger.warning( | ||
| "Search index %s already exists, skipping.", | ||
| self._state_search_index, | ||
| ) | ||
| elif "does not exist" in err_str and "table" in err_str: | ||
| logger.warning( | ||
| "Table %s does not exist, skipping search index creation" | ||
| " for %s.", | ||
| self._state_table, | ||
| self._state_search_index, | ||
| ) | ||
| else: | ||
| raise | ||
|
|
||
|
|
@@ -1115,6 +1120,7 @@ async def put_session_async(self, session: ConversationSession) -> None: | |
| ] | ||
|
|
||
| attribute_columns = [ | ||
| (SCHEMA_VERSION_COLUMN, CONVERSATION_SCHEMA_VERSION), | ||
| ("created_at", session.created_at), | ||
| ("updated_at", session.updated_at), | ||
| ("is_pinned", session.is_pinned), | ||
|
|
@@ -1148,6 +1154,7 @@ def put_session(self, session: ConversationSession) -> None: | |
| ] | ||
|
|
||
| attribute_columns = [ | ||
| (SCHEMA_VERSION_COLUMN, CONVERSATION_SCHEMA_VERSION), | ||
| ("created_at", session.created_at), | ||
| ("updated_at", session.updated_at), | ||
| ("is_pinned", session.is_pinned), | ||
|
|
@@ -1844,6 +1851,7 @@ async def put_event_async( | |
|
|
||
| content_json = json.dumps(content, ensure_ascii=False) | ||
| attribute_columns = [ | ||
| (SCHEMA_VERSION_COLUMN, EVENT_SCHEMA_VERSION), | ||
| ("type", event_type), | ||
| ("content", content_json), | ||
| ("created_at", created_at), | ||
|
|
@@ -1918,6 +1926,7 @@ def put_event( | |
|
|
||
| content_json = json.dumps(content, ensure_ascii=False) | ||
| attribute_columns = [ | ||
| (SCHEMA_VERSION_COLUMN, EVENT_SCHEMA_VERSION), | ||
| ("type", event_type), | ||
| ("content", content_json), | ||
| ("created_at", created_at), | ||
|
|
@@ -2282,6 +2291,7 @@ async def put_state_async( | |
| state_json = serialize_state(state) | ||
|
|
||
| put_cols: list[tuple[str, Any]] = [ | ||
| (SCHEMA_VERSION_COLUMN, STATE_SCHEMA_VERSION), | ||
| ("updated_at", now), | ||
| ("version", version + 1), | ||
| ] | ||
|
|
@@ -2374,6 +2384,7 @@ def put_state( | |
| state_json = serialize_state(state) | ||
|
|
||
| put_cols: list[tuple[str, Any]] = [ | ||
| (SCHEMA_VERSION_COLUMN, STATE_SCHEMA_VERSION), | ||
| ("updated_at", now), | ||
| ("version", version + 1), | ||
| ] | ||
|
|
@@ -2542,7 +2553,7 @@ async def delete_state_row_async( | |
| await self._async_client.delete_row(table_name, row, condition) | ||
|
|
||
| # ----------------------------------------------------------------------- | ||
| # State CRUD(同步) | ||
| # Checkpoint CRUD(LangGraph)(异步) | ||
| # ----------------------------------------------------------------------- | ||
|
|
||
| def delete_state_row( | ||
|
|
@@ -2561,7 +2572,7 @@ def delete_state_row( | |
| self._client.delete_row(table_name, row, condition) | ||
|
|
||
| # ----------------------------------------------------------------------- | ||
| # Checkpoint CRUD(LangGraph)(异步) | ||
| # Checkpoint CRUD(LangGraph)(同步) | ||
| # ----------------------------------------------------------------------- | ||
|
Comment on lines
2555
to
2576
|
||
|
|
||
| async def put_checkpoint_async( | ||
|
|
@@ -2582,6 +2593,7 @@ async def put_checkpoint_async( | |
| ("checkpoint_id", checkpoint_id), | ||
| ] | ||
| attribute_columns = [ | ||
| (SCHEMA_VERSION_COLUMN, CHECKPOINT_SCHEMA_VERSION), | ||
| ("checkpoint_type", checkpoint_type), | ||
| ("checkpoint_data", checkpoint_data), | ||
| ("metadata", metadata_json), | ||
|
|
@@ -2591,10 +2603,6 @@ async def put_checkpoint_async( | |
| condition = Condition(RowExistenceExpectation.IGNORE) | ||
| await self._async_client.put_row(self._checkpoint_table, row, condition) | ||
|
|
||
| # ----------------------------------------------------------------------- | ||
| # Checkpoint CRUD(LangGraph)(同步) | ||
| # ----------------------------------------------------------------------- | ||
|
|
||
| def put_checkpoint( | ||
| self, | ||
| thread_id: str, | ||
|
|
@@ -2613,6 +2621,7 @@ def put_checkpoint( | |
| ("checkpoint_id", checkpoint_id), | ||
| ] | ||
| attribute_columns = [ | ||
| (SCHEMA_VERSION_COLUMN, CHECKPOINT_SCHEMA_VERSION), | ||
| ("checkpoint_type", checkpoint_type), | ||
| ("checkpoint_data", checkpoint_data), | ||
| ("metadata", metadata_json), | ||
|
|
@@ -2881,6 +2890,7 @@ async def put_checkpoint_writes_async( | |
| ("task_idx", w["task_idx"]), | ||
| ] | ||
| attrs = [ | ||
| (SCHEMA_VERSION_COLUMN, CHECKPOINT_WRITES_SCHEMA_VERSION), | ||
| ("task_id", w["task_id"]), | ||
| ("task_path", w.get("task_path", "")), | ||
| ("channel", w["channel"]), | ||
|
|
@@ -2928,6 +2938,7 @@ def put_checkpoint_writes( | |
| ("task_idx", w["task_idx"]), | ||
| ] | ||
| attrs = [ | ||
| (SCHEMA_VERSION_COLUMN, CHECKPOINT_WRITES_SCHEMA_VERSION), | ||
| ("task_id", w["task_id"]), | ||
| ("task_path", w.get("task_path", "")), | ||
| ("channel", w["channel"]), | ||
|
|
@@ -3048,6 +3059,7 @@ async def put_checkpoint_blob_async( | |
| ("version", version), | ||
| ] | ||
| attribute_columns = [ | ||
| (SCHEMA_VERSION_COLUMN, CHECKPOINT_BLOBS_SCHEMA_VERSION), | ||
| ("blob_type", blob_type), | ||
| ("blob_data", blob_data), | ||
| ] | ||
|
|
@@ -3075,6 +3087,7 @@ def put_checkpoint_blob( | |
| ("version", version), | ||
| ] | ||
| attribute_columns = [ | ||
| (SCHEMA_VERSION_COLUMN, CHECKPOINT_BLOBS_SCHEMA_VERSION), | ||
| ("blob_type", blob_type), | ||
| ("blob_data", blob_data), | ||
| ] | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -111,7 +111,7 @@ async def init_langchain_tables_async(self) -> None: | |
| 表或索引已存在时跳过,可重复调用。 | ||
| """ | ||
| await self._backend.init_core_tables_async() | ||
| await self._backend.init_search_index_async() | ||
| await self._backend.init_conversation_search_index_async() | ||
|
Comment on lines
109
to
+114
|
||
|
|
||
| def init_langchain_tables(self) -> None: | ||
| """创建 LangChain 所需的全部表和索引(同步)。 | ||
|
|
@@ -120,7 +120,7 @@ def init_langchain_tables(self) -> None: | |
| 表或索引已存在时跳过,可重复调用。 | ||
| """ | ||
| self._backend.init_core_tables() | ||
| self._backend.init_search_index() | ||
| self._backend.init_conversation_search_index() | ||
|
|
||
| async def init_langgraph_tables_async(self) -> None: | ||
| """创建 LangGraph 所需的全部表和索引(异步)。 | ||
|
|
@@ -130,7 +130,7 @@ async def init_langgraph_tables_async(self) -> None: | |
| 表或索引已存在时跳过,可重复调用。 | ||
| """ | ||
| await self._backend.init_core_tables_async() | ||
| await self._backend.init_search_index_async() | ||
| await self._backend.init_conversation_search_index_async() | ||
| await self._backend.init_checkpoint_tables_async() | ||
|
|
||
| def init_langgraph_tables(self) -> None: | ||
|
|
@@ -141,7 +141,7 @@ def init_langgraph_tables(self) -> None: | |
| 表或索引已存在时跳过,可重复调用。 | ||
| """ | ||
| self._backend.init_core_tables() | ||
| self._backend.init_search_index() | ||
| self._backend.init_conversation_search_index() | ||
| self._backend.init_checkpoint_tables() | ||
|
|
||
| async def init_adk_tables_async(self) -> None: | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
这里的升级流程要求“在 PR 描述中记录变更的列名/类型/语义”,但当前 PR 描述仍是未填写的模板内容,缺少对新增 _schema_version 列与索引初始化 API 调整的说明。建议补全 PR 描述以便评审与发布时追踪兼容性影响。