Skip to content

Commit bc45071

Browse files
authored
Merge pull request #9 from FlowLLM-AI/bugfix
[bugfix] cache
2 parents b90ef7c + a6bb55b commit bc45071

5 files changed

Lines changed: 44 additions & 45 deletions

File tree

flowllm/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,4 @@
1111
from . import gallery # noqa: E402, F401 # pylint: disable=wrong-import-position,unused-import
1212
from . import extensions # noqa: E402, F401 # pylint: disable=wrong-import-position,unused-import
1313

14-
__version__ = "0.2.0.6"
14+
__version__ = "0.2.0.7"

flowllm/core/flow/base_flow.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,9 @@ def _maybe_load_cached(self, params: dict) -> Optional[FlowResponse]:
127127
cached = self.cache.load(key) if self.cache else None
128128
if cached is not None:
129129
logger.info(f"load flow response from cache with params={params}")
130-
return cached
130+
return FlowResponse(**cached)
131+
132+
return None
131133

132134
def _maybe_save_cache(self, params: dict, response: FlowResponse):
133135
"""Save a response into cache for non-streaming requests.

flowllm/core/service/mcp_service.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,5 @@ def run(self):
5858
transport=mcp_config.transport,
5959
host=mcp_config.host,
6060
port=mcp_config.port,
61-
show_banner=False)
61+
show_banner=False,
62+
)

flowllm/core/vector_store/pgvector_vector_store.py

Lines changed: 33 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,11 @@ def __init__(
5252
"""
5353
super().__init__(**kwargs)
5454
self.connection_string = connection_string or os.getenv(
55-
"FLOW_PGVECTOR_CONNECTION_STRING", "postgresql://localhost/postgres"
55+
"FLOW_PGVECTOR_CONNECTION_STRING",
56+
"postgresql://localhost/postgres",
5657
)
5758
self.async_connection_string = async_connection_string or os.getenv(
58-
"FLOW_PGVECTOR_ASYNC_CONNECTION_STRING"
59+
"FLOW_PGVECTOR_ASYNC_CONNECTION_STRING",
5960
)
6061
self.batch_size = batch_size
6162

@@ -68,15 +69,15 @@ def __init__(
6869
# Initialize async connection if async_connection_string is provided
6970
self._async_conn = None
7071
if self.async_connection_string:
71-
import asyncpg
72-
7372
# We'll create the async connection lazily in async methods
7473
self._async_conn_string = self.async_connection_string
7574
else:
7675
# Convert sync connection string to asyncpg format
7776
if self.connection_string.startswith("postgresql://"):
7877
self._async_conn_string = self.connection_string.replace(
79-
"postgresql://", "postgresql+asyncpg://", 1
78+
"postgresql://",
79+
"postgresql+asyncpg://",
80+
1,
8081
)
8182
else:
8283
self._async_conn_string = self.connection_string
@@ -87,7 +88,7 @@ def __init__(
8788
self._conn.commit()
8889

8990
logger.info(
90-
f"PostgreSQL pgvector client initialized with connection_string={self.connection_string}"
91+
f"PostgreSQL pgvector client initialized with connection_string={self.connection_string}",
9192
)
9293

9394
def _get_table_name(self, workspace_id: str) -> str:
@@ -166,22 +167,22 @@ def create_workspace(self, workspace_id: str, **kwargs):
166167
metadata JSONB NOT NULL,
167168
vector vector({dimensions}) NOT NULL
168169
)
169-
"""
170+
""",
170171
)
171172
# Create index for vector similarity search
172173
cur.execute(
173174
f"""
174175
CREATE INDEX IF NOT EXISTS "{table_name}_vector_idx"
175176
ON "{table_name}" USING ivfflat (vector vector_cosine_ops)
176177
WITH (lists = 100)
177-
"""
178+
""",
178179
)
179180
# Create index for metadata filtering
180181
cur.execute(
181182
f"""
182183
CREATE INDEX IF NOT EXISTS "{table_name}_metadata_idx"
183184
ON "{table_name}" USING gin (metadata)
184-
"""
185+
""",
185186
)
186187
self._conn.commit()
187188
logger.info(f"Created workspace table: {table_name} with vector({dimensions})")
@@ -201,7 +202,7 @@ def list_workspace(self, **kwargs) -> List[str]:
201202
WHERE table_schema = 'public'
202203
AND table_name LIKE 'workspace_%'
203204
ORDER BY table_name
204-
"""
205+
""",
205206
)
206207
table_names = [row[0] for row in cur.fetchall()]
207208
# Remove 'workspace_' prefix
@@ -249,7 +250,6 @@ def refresh(self, workspace_id: str):
249250
workspace_id: The identifier of the workspace (unused).
250251
"""
251252
# PostgreSQL doesn't need explicit refresh like Elasticsearch
252-
pass
253253

254254
@staticmethod
255255
def _row2node(row: Tuple, workspace_id: str) -> VectorNode:
@@ -268,13 +268,13 @@ def _row2node(row: Tuple, workspace_id: str) -> VectorNode:
268268

269269
# pgvector returns vector as string like '[0.1,0.2,0.3]'
270270
vector = json.loads(vector_str)
271-
271+
272272
# Parse metadata if it's a string (psycopg may return JSONB as string in some cases)
273273
if isinstance(metadata, str):
274274
metadata = json.loads(metadata)
275275
elif metadata is None:
276276
metadata = {}
277-
277+
278278
node = VectorNode(
279279
unique_id=unique_id,
280280
workspace_id=workspace_id_col or workspace_id,
@@ -285,7 +285,10 @@ def _row2node(row: Tuple, workspace_id: str) -> VectorNode:
285285
return node
286286

287287
@staticmethod
288-
def _build_sql_filters(filter_dict: Optional[Dict[str, Any]] = None, use_async: bool = False) -> Tuple[str, List[Any]]:
288+
def _build_sql_filters(
289+
filter_dict: Optional[Dict[str, Any]] = None,
290+
use_async: bool = False,
291+
) -> Tuple[str, List[Any]]:
289292
"""Build SQL WHERE clause from filter_dict.
290293
291294
Converts a filter dictionary into SQL WHERE conditions.
@@ -306,7 +309,6 @@ def _build_sql_filters(filter_dict: Optional[Dict[str, Any]] = None, use_async:
306309
conditions = []
307310
params = []
308311
param_idx = 1
309-
placeholder = "$%d" if use_async else "%s"
310312

311313
for key, filter_value in filter_dict.items():
312314
# Handle nested keys by using JSONB path operators
@@ -394,7 +396,7 @@ def search(
394396
# Cosine similarity = 1 - cosine distance
395397
# Convert query_vector to string format for pgvector
396398
query_vector_str = "[" + ",".join(str(v) for v in query_vector) + "]"
397-
399+
398400
with self._conn.cursor() as cur:
399401
# Parameter order in SQL: SELECT %s::vector, WHERE %s..., ORDER BY %s::vector, LIMIT %s
400402
# So parameters should be: [query_vector_str] + filter_params + [query_vector_str, top_k]
@@ -418,13 +420,13 @@ def search(
418420

419421
# pgvector returns vector as string like '[0.1,0.2,0.3]'
420422
vector = json.loads(vector_str)
421-
423+
422424
# Parse metadata if it's a string (psycopg may return JSONB as string in some cases)
423425
if isinstance(metadata, str):
424426
metadata = json.loads(metadata)
425427
elif metadata is None:
426428
metadata = {}
427-
429+
428430
node = VectorNode(
429431
unique_id=unique_id,
430432
workspace_id=workspace_id_col or workspace_id,
@@ -437,13 +439,12 @@ def search(
437439

438440
return nodes
439441

440-
def insert(self, nodes: VectorNode | List[VectorNode], workspace_id: str, refresh: bool = True, **kwargs):
442+
def insert(self, nodes: VectorNode | List[VectorNode], workspace_id: str, **kwargs):
441443
"""Insert vector nodes into the PostgreSQL table.
442444
443445
Args:
444446
nodes: A single VectorNode or list of VectorNodes to insert.
445447
workspace_id: The identifier of the workspace to insert into.
446-
refresh: Whether to refresh after insertion (default: True, kept for API compatibility).
447448
**kwargs: Additional keyword arguments (unused).
448449
"""
449450
if not self.exist_workspace(workspace_id=workspace_id):
@@ -474,15 +475,15 @@ def insert(self, nodes: VectorNode | List[VectorNode], workspace_id: str, refres
474475
vector_str = "[" + ",".join(str(v) for v in vector_value) + "]"
475476
else:
476477
vector_str = str(vector_value)
477-
478+
478479
values.append(
479480
(
480481
node.unique_id,
481482
workspace_id,
482483
node.content,
483484
json.dumps(node.metadata),
484485
vector_str,
485-
)
486+
),
486487
)
487488

488489
# Use INSERT ... ON CONFLICT for upsert
@@ -502,13 +503,12 @@ def insert(self, nodes: VectorNode | List[VectorNode], workspace_id: str, refres
502503
self._conn.commit()
503504
logger.info(f"insert nodes.size={len(all_nodes)} into workspace_id={workspace_id}")
504505

505-
def delete(self, node_ids: str | List[str], workspace_id: str, refresh: bool = True, **kwargs):
506+
def delete(self, node_ids: str | List[str], workspace_id: str, **kwargs):
506507
"""Delete vector nodes from the PostgreSQL table.
507508
508509
Args:
509510
node_ids: A single node ID or list of node IDs to delete.
510511
workspace_id: The identifier of the workspace to delete from.
511-
refresh: Whether to refresh after deletion (default: True, kept for API compatibility).
512512
**kwargs: Additional keyword arguments (unused).
513513
"""
514514
if not self.exist_workspace(workspace_id=workspace_id):
@@ -543,7 +543,7 @@ async def _get_async_conn(self):
543543

544544
logger.debug(f"Establishing async PostgreSQL connection: {conn_str}")
545545
self._async_conn = await asyncpg.connect(conn_str)
546-
logger.debug(f"Async PostgreSQL connection established successfully")
546+
logger.debug("Async PostgreSQL connection established successfully")
547547
return self._async_conn
548548

549549
async def async_exist_workspace(self, workspace_id: str, **kwargs) -> bool:
@@ -607,22 +607,22 @@ async def async_create_workspace(self, workspace_id: str, **kwargs):
607607
metadata JSONB NOT NULL,
608608
vector vector({dimensions}) NOT NULL
609609
)
610-
"""
610+
""",
611611
)
612612
# Create index for vector similarity search
613613
await conn.execute(
614614
f"""
615615
CREATE INDEX IF NOT EXISTS "{table_name}_vector_idx"
616616
ON "{table_name}" USING ivfflat (vector vector_cosine_ops)
617617
WITH (lists = 100)
618-
"""
618+
""",
619619
)
620620
# Create index for metadata filtering
621621
await conn.execute(
622622
f"""
623623
CREATE INDEX IF NOT EXISTS "{table_name}_metadata_idx"
624624
ON "{table_name}" USING gin (metadata)
625-
"""
625+
""",
626626
)
627627
logger.info(f"Created workspace table: {table_name} with vector({dimensions})")
628628

@@ -633,7 +633,6 @@ async def async_refresh(self, workspace_id: str):
633633
workspace_id: The identifier of the workspace (unused).
634634
"""
635635
# PostgreSQL doesn't need explicit refresh like Elasticsearch
636-
pass
637636

638637
async def async_search(
639638
self,
@@ -686,7 +685,7 @@ async def async_search(
686685
new_placeholder = f"${param_offset + i + 1}"
687686
adjusted_where = adjusted_where.replace(old_placeholder, new_placeholder)
688687
where_sql = f"WHERE {adjusted_where}" if adjusted_where else ""
689-
688+
690689
# Calculate the last parameter index for top_k
691690
top_k_param_idx = 1 + len(filter_params) + 1 # $1 (query_vector) + filter_params + 1
692691

@@ -700,7 +699,7 @@ async def async_search(
700699
ORDER BY vector <=> $1::vector
701700
LIMIT ${top_k_param_idx}
702701
"""
703-
702+
704703
# Parameter order: query_vector ($1), filter_params ($2, $3, ...), top_k ($last)
705704
rows = await conn.fetch(query_sql, query_vector_str, *filter_params, top_k)
706705

@@ -718,13 +717,13 @@ async def async_search(
718717

719718
# pgvector returns vector as string like '[0.1,0.2,0.3]'
720719
vector = json.loads(vector_str)
721-
720+
722721
# Parse metadata if it's a string (asyncpg may return JSONB as string)
723722
if isinstance(metadata, str):
724723
metadata = json.loads(metadata)
725724
elif metadata is None:
726725
metadata = {}
727-
726+
728727
node = VectorNode(
729728
unique_id=unique_id,
730729
workspace_id=workspace_id_col or workspace_id,
@@ -741,15 +740,13 @@ async def async_insert(
741740
self,
742741
nodes: VectorNode | List[VectorNode],
743742
workspace_id: str,
744-
refresh: bool = True,
745743
**kwargs,
746744
):
747745
"""Insert vector nodes into the PostgreSQL table (async).
748746
749747
Args:
750748
nodes: A single VectorNode or list of VectorNodes to insert.
751749
workspace_id: The identifier of the workspace to insert into.
752-
refresh: Whether to refresh after insertion (default: True, kept for API compatibility).
753750
**kwargs: Additional keyword arguments (unused).
754751
"""
755752
if not await self.async_exist_workspace(workspace_id=workspace_id):
@@ -802,13 +799,12 @@ async def async_insert(
802799

803800
logger.info(f"async insert nodes.size={len(all_nodes)} into workspace_id={workspace_id}")
804801

805-
async def async_delete(self, node_ids: str | List[str], workspace_id: str, refresh: bool = True, **kwargs):
802+
async def async_delete(self, node_ids: str | List[str], workspace_id: str, **kwargs):
806803
"""Delete vector nodes from the PostgreSQL table (async).
807804
808805
Args:
809806
node_ids: A single node ID or list of node IDs to delete.
810807
workspace_id: The identifier of the workspace to delete from.
811-
refresh: Whether to refresh after deletion (default: True, kept for API compatibility).
812808
**kwargs: Additional keyword arguments (unused).
813809
"""
814810
if not await self.async_exist_workspace(workspace_id=workspace_id):
@@ -836,4 +832,3 @@ async def async_close(self):
836832
if self._async_conn:
837833
await self._async_conn.close()
838834
self._async_conn = None
839-

tests/test_pgvector_vector_store.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@ def main():
3939
embedding_model = OpenAICompatibleEmbeddingModel(dimensions=64, model_name="text-embedding-v4")
4040
workspace_id = "rag_nodes_index"
4141
connection_string = "postgresql://localhost/postgres"
42-
42+
4343
pg = PgVectorStore(connection_string=connection_string, embedding_model=embedding_model)
44-
44+
4545
# Clean up and create workspace
4646
if pg.exist_workspace(workspace_id=workspace_id):
4747
pg.delete_workspace(workspace_id=workspace_id)
@@ -153,7 +153,7 @@ async def async_main():
153153
async_connection_string=async_connection_string,
154154
embedding_model=embedding_model,
155155
)
156-
156+
157157
# Clean up and create workspace
158158
if await pg.async_exist_workspace(workspace_id=workspace_id):
159159
await pg.async_delete_workspace(workspace_id=workspace_id)
@@ -264,6 +264,7 @@ async def async_main():
264264
except Exception as e:
265265
logger.error(f"Sync test failed: {e}")
266266
import traceback
267+
267268
traceback.print_exc()
268269

269270
# Run async test
@@ -273,5 +274,5 @@ async def async_main():
273274
except Exception as e:
274275
logger.error(f"Async test failed: {e}")
275276
import traceback
276-
traceback.print_exc()
277277

278+
traceback.print_exc()

0 commit comments

Comments
 (0)