Skip to content

Commit cd3fb38

Browse files
authored
Reinvent conversation metadata API and implementation (#71)
- Changed the ConversationMetadata table to be a key/value list -- more flexible. - When initializing a database, after the schema is initialized, also initialize that table - We're now using schema_version==1 Almost all of this was written by Claude Sonnet 4.5; I also briefly tried Gemini 2.5 Pro and GPT-5-Codex (Preview), all using agent mode in VS Code.
1 parent c94dd7b commit cd3fb38

10 files changed

Lines changed: 508 additions & 198 deletions

File tree

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ that make changes to the repository. Not even `git add`**
1010
When moving, copying or deleting files, use the git commands: `git mv`, `git cp`, `git rm`
1111

1212
- Don't use '!' on the command line, it's some bash magic (even inside single quotes)
13-
- Activate `.venv`: make venv; source .venv/bin/activate
13+
- Activate `.venv`: `make venv; source .venv/bin/activate` (run this only once)
1414
- To get API keys in ad-hoc code, call `typeagent.aitools.utils.load_dotenv()`
1515
- Use `pytest test` to run tests in test/
1616
- Use `pyright` to check type annotations in tools/, test/, typeagent/, gmail/

test/test_conversation_metadata.py

Lines changed: 177 additions & 95 deletions
Large diffs are not rendered by default.

tools/ingest_vtt.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
from typeagent.aitools import utils
2727
from typeagent.aitools.embeddings import AsyncEmbeddingModel
2828
from typeagent.storage.utils import create_storage_provider
29-
from typeagent.storage.sqlite.provider import SqliteStorageProvider
3029
from typeagent.transcripts.transcript_ingest import (
3130
get_transcript_duration,
3231
get_transcript_speakers,

typeagent/knowpro/conversation_base.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"""Base class for conversations with incremental indexing support."""
55

66
from dataclasses import dataclass
7+
from datetime import datetime, timezone
78
from typing import Generic, Self, TypeVar
89

910
import typechat
@@ -160,6 +161,11 @@ async def add_messages_with_indexing(
160161
- start_points.semref_count,
161162
)
162163

164+
# Update the updated_at timestamp
165+
storage.update_conversation_timestamps(
166+
updated_at=datetime.now(timezone.utc)
167+
)
168+
163169
return result
164170

165171
async def _add_metadata_knowledge_incremental(

typeagent/knowpro/interfaces.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -777,6 +777,24 @@ class ConversationDataWithIndexes[TMessageData](ConversationData[TMessageData]):
777777
# --------
778778

779779

780+
@dataclass
781+
class ConversationMetadata:
782+
"""Storage-provider-agnostic metadata for a conversation.
783+
784+
This dataclass represents metadata that can be read from and written to
785+
any storage provider (SQLite, in-memory, etc.). Providers may store this
786+
internally in different formats (e.g., key-value pairs), but this provides
787+
a uniform interface for accessing conversation metadata.
788+
"""
789+
790+
name_tag: str
791+
schema_version: int
792+
created_at: Datetime
793+
updated_at: Datetime
794+
tags: list[str]
795+
extra: dict[str, str] # All extra values stored as strings
796+
797+
780798
class IReadonlyCollection[T, TOrdinal](AsyncIterable[T], Protocol):
781799
async def size(self) -> int: ...
782800

@@ -832,6 +850,39 @@ async def get_related_terms_index(self) -> ITermToRelatedTermsIndex: ...
832850

833851
async def get_conversation_threads(self) -> IConversationThreads: ...
834852

853+
# Metadata management
854+
def get_conversation_metadata(self) -> Any | None:
855+
"""Get conversation metadata.
856+
857+
Returns ConversationMetadata or None if no metadata exists.
858+
Return type is Any to avoid circular import with storage layer.
859+
"""
860+
...
861+
862+
def set_conversation_metadata(self, **kwds: str | list[str] | None) -> None:
863+
"""Set or update conversation metadata key-value pairs.
864+
865+
Args:
866+
**kwds: Metadata keys and values where:
867+
- str value: Sets a single key-value pair (replaces existing)
868+
- list[str] value: Sets multiple values for the same key
869+
- None value: Deletes all rows for the given key
870+
"""
871+
...
872+
873+
def update_conversation_timestamps(
874+
self,
875+
created_at: Datetime | None = None,
876+
updated_at: Datetime | None = None,
877+
) -> None:
878+
"""Update conversation timestamps.
879+
880+
Args:
881+
created_at: Optional creation timestamp
882+
updated_at: Optional last updated timestamp
883+
"""
884+
...
885+
835886
# Transaction management
836887
async def __aenter__(self) -> Self:
837888
"""Enter transaction context. Calls begin_transaction()."""

typeagent/podcasts/podcast.py

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,33 +18,14 @@
1818
from ..storage.memory.convthreads import ConversationThreads
1919
from ..knowpro.convsettings import ConversationSettings
2020
from ..knowpro.interfaces import (
21-
AddMessagesResult,
2221
ConversationDataWithIndexes,
23-
Datetime,
24-
ICollection,
25-
IConversation,
26-
IConversationSecondaryIndexes,
27-
IKnowledgeSource,
28-
IMessage,
29-
IMessageCollection,
30-
IMessageMetadata,
31-
ISemanticRefCollection,
32-
IStorageProvider,
33-
ITermToSemanticRefIndex,
34-
IndexingStartPoints,
35-
MessageOrdinal,
3622
SemanticRef,
3723
Term,
38-
Timedelta,
3924
)
4025
from ..storage.memory.messageindex import MessageTextIndex
4126
from ..storage.memory.reltermsindex import TermToRelatedTermsMap
4227
from ..storage.utils import create_storage_provider
4328
from ..knowpro import serialization
44-
from ..storage.memory.collections import (
45-
MemoryMessageCollection,
46-
MemorySemanticRefCollection,
47-
)
4829
from ..knowpro.universal_message import (
4930
ConversationMessage,
5031
ConversationMessageMeta,

typeagent/storage/memory/provider.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
# Copyright (c) Microsoft Corporation.
22
# Licensed under the MIT License.
33

4-
"""Memory storage provider implementation."""
4+
"""In-memory storage provider implementation."""
5+
6+
7+
from datetime import datetime
8+
9+
from ...knowpro import interfaces
510

611
from .collections import MemoryMessageCollection, MemorySemanticRefCollection
712
from .semrefindex import TermToSemanticRefIndex
@@ -95,3 +100,37 @@ async def get_semantic_ref_collection(self) -> MemorySemanticRefCollection:
95100
async def close(self) -> None:
96101
"""Close the storage provider."""
97102
pass
103+
104+
def get_conversation_metadata(self) -> None:
105+
"""Get conversation metadata (no-op for in-memory storage).
106+
107+
Returns None since in-memory storage doesn't persist metadata.
108+
"""
109+
return None
110+
111+
def set_conversation_metadata(self, **kwds: str | list[str] | None) -> None:
112+
"""Set conversation metadata (no-op for in-memory storage).
113+
114+
This method exists for API compatibility with SqliteStorageProvider
115+
but does nothing since in-memory storage doesn't persist metadata.
116+
117+
Args:
118+
**kwds: Metadata keys and values (ignored)
119+
"""
120+
pass
121+
122+
def update_conversation_timestamps(
123+
self,
124+
created_at: datetime | None = None,
125+
updated_at: datetime | None = None,
126+
) -> None:
127+
"""Update conversation timestamps (no-op for in-memory storage).
128+
129+
This method exists for API compatibility with SqliteStorageProvider
130+
but does nothing since in-memory storage doesn't persist metadata.
131+
132+
Args:
133+
created_at: Optional creation timestamp (ignored)
134+
updated_at: Optional last updated timestamp (ignored)
135+
"""
136+
pass

typeagent/storage/sqlite/__init__.py

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,24 @@
66
from .collections import SqliteMessageCollection, SqliteSemanticRefCollection
77
from .messageindex import SqliteMessageTextIndex
88
from .propindex import SqlitePropertyIndex
9-
from .reltermsindex import SqliteRelatedTermsIndex
10-
from .semrefindex import SqliteTermToSemanticRefIndex
11-
from .timestampindex import SqliteTimestampToTextRangeIndex
129
from .provider import SqliteStorageProvider
10+
from .reltermsindex import SqliteRelatedTermsIndex
1311
from .schema import (
14-
ConversationMetadata,
1512
init_db_schema,
1613
get_db_schema_version,
1714
)
15+
from .semrefindex import SqliteTermToSemanticRefIndex
16+
from .timestampindex import SqliteTimestampToTextRangeIndex
1817

1918
__all__ = [
19+
"get_db_schema_version",
20+
"init_db_schema",
2021
"SqliteMessageCollection",
21-
"SqliteSemanticRefCollection",
2222
"SqliteMessageTextIndex",
2323
"SqlitePropertyIndex",
2424
"SqliteRelatedTermsIndex",
25+
"SqliteSemanticRefCollection",
26+
"SqliteStorageProvider",
2527
"SqliteTermToSemanticRefIndex",
2628
"SqliteTimestampToTextRangeIndex",
27-
"SqliteStorageProvider",
28-
"ConversationMetadata",
29-
"init_db_schema",
30-
"get_db_schema_version",
3129
]

0 commit comments

Comments
 (0)