forked from langflow-ai/langflow
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmodel.py
More file actions
203 lines (175 loc) · 7.69 KB
/
Copy pathmodel.py
File metadata and controls
203 lines (175 loc) · 7.69 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
import json
import math
from datetime import datetime, timezone
from typing import TYPE_CHECKING, Annotated
from uuid import UUID, uuid4
from pydantic import ConfigDict, field_serializer, field_validator
from sqlalchemy import Text
from sqlmodel import JSON, Column, Field, SQLModel
from langflow.schema.content_block import ContentBlock
from langflow.schema.properties import Properties
from langflow.schema.validators import str_to_timestamp_validator
if TYPE_CHECKING:
from langflow.schema.message import Message
class MessageBase(SQLModel):
timestamp: Annotated[datetime, str_to_timestamp_validator] = Field(
default_factory=lambda: datetime.now(timezone.utc)
)
sender: str
sender_name: str
session_id: str
context_id: str | None = Field(default=None)
text: str = Field(sa_column=Column(Text))
files: list[str] = Field(default_factory=list)
error: bool = Field(default=False)
edit: bool = Field(default=False)
properties: Properties = Field(default_factory=Properties)
category: str = Field(default="message")
content_blocks: list[ContentBlock] = Field(default_factory=list)
@field_serializer("timestamp")
def serialize_timestamp(self, value):
if isinstance(value, datetime):
if value.tzinfo is None:
value = value.replace(tzinfo=timezone.utc)
return value.strftime("%Y-%m-%d %H:%M:%S %Z")
if isinstance(value, str):
# Make sure the timestamp is in UTC
value = datetime.fromisoformat(value).replace(tzinfo=timezone.utc)
return value.strftime("%Y-%m-%d %H:%M:%S %Z")
return value
@field_validator("files", mode="before")
@classmethod
def validate_files(cls, value):
if not value:
value = []
return value
@field_validator("session_id", mode="before")
@classmethod
def validate_session_id(cls, value):
if isinstance(value, UUID):
value = str(value)
return value
@classmethod
def from_message(cls, message: "Message", flow_id: str | UUID | None = None):
# first check if the record has all the required fields
if message.text is None or not message.sender or not message.sender_name:
msg = "The message does not have the required fields (text, sender, sender_name)."
raise ValueError(msg)
if message.files:
image_paths = []
for file in message.files:
if hasattr(file, "path") and hasattr(file, "url") and file.path:
session_id = message.session_id
if session_id:
image_paths.append(f"{session_id}{file.path.split(str(session_id))[1]}")
else:
image_paths.append(file.path)
if image_paths:
message.files = image_paths
if isinstance(message.timestamp, str):
# Convert timestamp string in format "YYYY-MM-DD HH:MM:SS UTC" to datetime
try:
timestamp = datetime.strptime(message.timestamp, "%Y-%m-%d %H:%M:%S %Z").replace(tzinfo=timezone.utc)
except ValueError:
# Fallback for ISO format if the above fails
timestamp = datetime.fromisoformat(message.timestamp).replace(tzinfo=timezone.utc)
else:
timestamp = message.timestamp
if not flow_id and message.flow_id:
flow_id = message.flow_id
# If the text is not a string, it means it could be
# async iterator so we simply add it as an empty string
message_text = "" if not isinstance(message.text, str) else message.text
properties = (
message.properties.model_dump_json()
if hasattr(message.properties, "model_dump_json")
else message.properties
)
content_blocks = []
for content_block in message.content_blocks or []:
content = content_block.model_dump_json() if hasattr(content_block, "model_dump_json") else content_block
content_blocks.append(content)
if isinstance(flow_id, str):
try:
flow_id = UUID(flow_id)
except ValueError as exc:
msg = f"Flow ID {flow_id} is not a valid UUID"
raise ValueError(msg) from exc
return cls(
sender=message.sender,
sender_name=message.sender_name,
text=message_text,
session_id=message.session_id,
context_id=message.context_id,
files=message.files or [],
timestamp=timestamp,
flow_id=flow_id,
properties=properties,
category=message.category,
content_blocks=content_blocks,
)
class MessageTable(MessageBase, table=True): # type: ignore[call-arg]
model_config = ConfigDict(validate_assignment=True, arbitrary_types_allowed=True)
__tablename__ = "message"
id: UUID = Field(default_factory=uuid4, primary_key=True)
flow_id: UUID | None = Field(default=None)
files: list[str] = Field(sa_column=Column(JSON))
properties: dict | Properties = Field(default_factory=lambda: Properties().model_dump(), sa_column=Column(JSON)) # type: ignore[assignment]
category: str = Field(sa_column=Column(Text))
content_blocks: list[dict | ContentBlock] = Field(default_factory=list, sa_column=Column(JSON)) # type: ignore[assignment]
# We need to make sure the datetimes have timezone after running session.refresh
# because we are losing the timezone information when we save the message to the database
# and when we read it back. We use field_validator to make sure the datetimes have timezone
# after running session.refresh
@field_validator("flow_id", mode="before")
@classmethod
def validate_flow_id(cls, value):
if value is None:
return value
if isinstance(value, str):
value = UUID(value)
return value
@field_validator("properties", "content_blocks", mode="before")
@classmethod
def validate_properties_or_content_blocks(cls, value):
if isinstance(value, list):
return [cls.validate_properties_or_content_blocks(item) for item in value]
if hasattr(value, "model_dump"):
value = value.model_dump()
if isinstance(value, str):
value = json.loads(value)
return cls._sanitize_json_value(value)
@field_serializer("properties", "content_blocks")
@classmethod
def serialize_properties_or_content_blocks(cls, value) -> dict | list[dict]:
if isinstance(value, list):
return [cls.serialize_properties_or_content_blocks(item) for item in value]
if hasattr(value, "model_dump"):
value = value.model_dump()
if isinstance(value, str):
value = json.loads(value)
return cls._sanitize_json_value(value)
@staticmethod
def _sanitize_json_value(value):
if isinstance(value, dict):
return {key: MessageTable._sanitize_json_value(val) for key, val in value.items()}
if isinstance(value, list):
return [MessageTable._sanitize_json_value(item) for item in value]
if isinstance(value, float) and not math.isfinite(value):
return None
return value
class MessageRead(MessageBase):
id: UUID
flow_id: UUID | None = Field()
class MessageCreate(MessageBase):
pass
class MessageUpdate(SQLModel):
text: str | None = None
sender: str | None = None
sender_name: str | None = None
session_id: str | None = None
context_id: str | None = None
files: list[str] | None = None
edit: bool | None = None
error: bool | None = None
properties: Properties | None = None