mirror of
https://github.com/MervinPraison/PraisonAI.git
synced 2026-11-07 17:33:33 -10:00
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
85ae215025 | ||
|
|
056e4f5baa | ||
|
|
8f06d18e5a |
No files matched your search
@@ -23,12 +23,22 @@ def main():
|
||||
@register_tool("custom.calculate")
|
||||
def calculate(expression: str) -> str:
|
||||
"""Safely evaluate a mathematical expression."""
|
||||
import ast
|
||||
import operator
|
||||
try:
|
||||
# Only allow safe math operations
|
||||
allowed = set("0123456789+-*/.(). ")
|
||||
if not all(c in allowed for c in expression):
|
||||
return "Error: Invalid characters in expression"
|
||||
result = eval(expression) # Safe due to character filtering
|
||||
# Use ast.parse for safe evaluation of math expressions
|
||||
tree = ast.parse(expression, mode='eval')
|
||||
# Only allow numeric literals and basic math operators
|
||||
for node in ast.walk(tree):
|
||||
if not isinstance(node, (ast.Expression, ast.BinOp, ast.UnaryOp,
|
||||
ast.Constant, ast.Add, ast.Sub, ast.Mult,
|
||||
ast.Div, ast.USub, ast.UAdd)):
|
||||
return "Error: Only basic math operations are allowed"
|
||||
result = eval(compile(tree, '<expr>', 'eval'), {"__builtins__": {}})
|
||||
return f"Result: {result}"
|
||||
except Exception as e:
|
||||
return f"Error: {e}"
|
||||
|
||||
@@ -44,12 +44,20 @@ def main():
|
||||
|
||||
def calculate(expression: str) -> str:
|
||||
"""Calculate a math expression safely."""
|
||||
import ast
|
||||
try:
|
||||
# Safe eval for basic math
|
||||
allowed = set("0123456789+-*/.() ")
|
||||
if all(c in allowed for c in expression):
|
||||
return str(eval(expression))
|
||||
return "Error: Invalid expression"
|
||||
if not all(c in allowed for c in expression):
|
||||
return "Error: Invalid expression"
|
||||
# Use ast.parse to validate expression safety
|
||||
tree = ast.parse(expression, mode='eval')
|
||||
for node in ast.walk(tree):
|
||||
if not isinstance(node, (ast.Expression, ast.BinOp, ast.UnaryOp,
|
||||
ast.Constant, ast.Add, ast.Sub, ast.Mult,
|
||||
ast.Div, ast.USub, ast.UAdd)):
|
||||
return "Error: Only basic math operations are allowed"
|
||||
return str(eval(compile(tree, '<expr>', 'eval'), {"__builtins__": {}}))
|
||||
except Exception as e:
|
||||
return f"Error: {e}"
|
||||
|
||||
|
||||
@@ -135,9 +135,30 @@ def main():
|
||||
return {"error": str(e)}
|
||||
|
||||
# MCP endpoints
|
||||
def safe_calculate(expression: str) -> str:
|
||||
"""Safely evaluate a mathematical expression using AST."""
|
||||
import ast
|
||||
try:
|
||||
# Only allow safe math characters
|
||||
allowed = set("0123456789+-*/.() ")
|
||||
if not all(c in allowed for c in expression):
|
||||
return "Error: Invalid characters in expression"
|
||||
# Use ast.parse for safe evaluation
|
||||
tree = ast.parse(expression, mode='eval')
|
||||
# Only allow numeric literals and basic math operators
|
||||
for node in ast.walk(tree):
|
||||
if not isinstance(node, (ast.Expression, ast.BinOp, ast.UnaryOp,
|
||||
ast.Constant, ast.Add, ast.Sub, ast.Mult,
|
||||
ast.Div, ast.USub, ast.UAdd)):
|
||||
return "Error: Only basic math operations are allowed"
|
||||
result = eval(compile(tree, '<expr>', 'eval'), {"__builtins__": {}})
|
||||
return str(result)
|
||||
except Exception as e:
|
||||
return f"Error: {e}"
|
||||
|
||||
mcp_tools = {
|
||||
"search": {"description": "Search the web", "func": lambda q: f"Results for: {q}"},
|
||||
"calculate": {"description": "Calculate expression", "func": lambda e: str(eval(e)) if e.replace(" ", "").replace("+", "").replace("-", "").replace("*", "").replace("/", "").replace(".", "").isdigit() or True else "Error"},
|
||||
"calculate": {"description": "Calculate expression", "func": safe_calculate},
|
||||
}
|
||||
|
||||
@app.get("/mcp/tools")
|
||||
|
||||
@@ -93,4 +93,5 @@ chroma_db/
|
||||
|
||||
.praisonai
|
||||
.windsurf
|
||||
.agent
|
||||
.agent
|
||||
.sfdx
|
||||
@@ -1,3 +1,5 @@
|
||||
import os
|
||||
|
||||
from praisonaiagents import Agent, Task, AgentTeam
|
||||
from praisonaiagents.memory import Memory
|
||||
from praisonaiagents.agent import ContextAgent
|
||||
@@ -10,7 +12,7 @@ context_output = context_agent.start("https://github.com/MervinPraison/PraisonAI
|
||||
mongodb_memory_config = {
|
||||
"provider": "mongodb",
|
||||
"config": {
|
||||
"connection_string": "mongodb+srv://username:[email protected]/?retryWrites=true&w=majority&appName=Cluster2",
|
||||
"connection_string": os.environ.get("MONGODB_CONNECTION_STRING", "mongodb+srv://localhost:27017/?retryWrites=true&w=majority"),
|
||||
"database": "praisonai_memory",
|
||||
"use_vector_search": True,
|
||||
"max_pool_size": 50,
|
||||
@@ -49,7 +51,7 @@ print("\n=== MEMORY VALIDATION ===")
|
||||
|
||||
try:
|
||||
# Check MongoDB connection and collections
|
||||
client = pymongo.MongoClient("mongodb+srv://username:[email protected]/?retryWrites=true&w=majority&appName=Cluster2")
|
||||
client = pymongo.MongoClient(os.environ.get("MONGODB_CONNECTION_STRING", "mongodb+srv://localhost:27017/?retryWrites=true&w=majority"))
|
||||
db = client["praisonai_memory"]
|
||||
collections = db.list_collection_names()
|
||||
print(f"Available collections: {collections}")
|
||||
|
||||
@@ -286,12 +286,26 @@ class FileTools:
|
||||
}
|
||||
|
||||
try:
|
||||
# Validate URL to prevent SSRF
|
||||
from urllib.parse import urlparse
|
||||
parsed_url = urlparse(url)
|
||||
if parsed_url.scheme not in ("http", "https"):
|
||||
return {"success": False, "path": "", "size": 0, "error": f"Unsupported URL scheme: {parsed_url.scheme}"}
|
||||
import ipaddress, socket
|
||||
try:
|
||||
hostname = parsed_url.hostname or ""
|
||||
ip = ipaddress.ip_address(socket.gethostbyname(hostname))
|
||||
if ip.is_private or ip.is_loopback or ip.is_reserved or ip.is_link_local:
|
||||
return {"success": False, "path": "", "size": 0, "error": f"URL resolves to private/reserved IP: {ip}"}
|
||||
except (socket.gaierror, ValueError):
|
||||
pass # Let the request handle DNS resolution errors
|
||||
|
||||
# Validate destination path
|
||||
safe_path = FileTools._validate_path(destination)
|
||||
|
||||
|
||||
# Create directory if needed
|
||||
os.makedirs(os.path.dirname(safe_path) or ".", exist_ok=True)
|
||||
|
||||
|
||||
# Download file
|
||||
with httpx.stream("GET", url, timeout=timeout, follow_redirects=True) as response:
|
||||
response.raise_for_status()
|
||||
|
||||
+20
-1
@@ -1,4 +1,5 @@
|
||||
from flask import Flask
|
||||
from markupsafe import escape
|
||||
import markdown
|
||||
|
||||
app = Flask(__name__)
|
||||
@@ -11,7 +12,25 @@ def basic():
|
||||
@app.route('/')
|
||||
def home():
|
||||
output = basic()
|
||||
html_output = markdown.markdown(output)
|
||||
# Convert markdown to HTML, then sanitize the output to prevent XSS
|
||||
html_output = markdown.markdown(str(output))
|
||||
# Use bleach to sanitize HTML output (allows safe tags only)
|
||||
try:
|
||||
import bleach
|
||||
allowed_tags = ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ul', 'ol', 'li',
|
||||
'strong', 'em', 'code', 'pre', 'blockquote', 'a', 'br', 'hr']
|
||||
allowed_attrs = {'a': ['href', 'title']}
|
||||
# Strip javascript: URLs from href attributes
|
||||
def filter_href(tag, name, value):
|
||||
if name == 'href':
|
||||
if value.lower().startswith(('javascript:', 'data:', 'vbscript:')):
|
||||
return False
|
||||
return True
|
||||
html_output = bleach.clean(html_output, tags=allowed_tags, attributes=allowed_attrs,
|
||||
strip=True, protocols=['http', 'https', 'mailto'])
|
||||
except ImportError:
|
||||
# Fallback: escape the entire output if bleach not available
|
||||
html_output = str(escape(html_output))
|
||||
return f'<html><body>{html_output}</body></html>'
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -271,6 +271,23 @@ class URLReader:
|
||||
return []
|
||||
|
||||
try:
|
||||
# Validate URL to prevent SSRF
|
||||
from urllib.parse import urlparse
|
||||
parsed = urlparse(source)
|
||||
if parsed.scheme not in ("http", "https"):
|
||||
logger.error(f"Unsupported URL scheme: {parsed.scheme}")
|
||||
return []
|
||||
import ipaddress
|
||||
try:
|
||||
import socket
|
||||
hostname = parsed.hostname or ""
|
||||
ip = ipaddress.ip_address(socket.gethostbyname(hostname))
|
||||
if ip.is_private or ip.is_loopback or ip.is_reserved or ip.is_link_local:
|
||||
logger.error(f"URL resolves to private/reserved IP: {ip}")
|
||||
return []
|
||||
except (socket.gaierror, ValueError):
|
||||
pass # Let the request handle DNS resolution errors
|
||||
|
||||
response = httpx.get(source, timeout=30, follow_redirects=True)
|
||||
response.raise_for_status()
|
||||
content = response.text
|
||||
|
||||
@@ -312,13 +312,23 @@ class HybridWorkflowExecutor:
|
||||
return flags
|
||||
|
||||
def _eval_condition(self, condition: str, flags: Dict) -> bool:
|
||||
"""Evaluate a simple condition expression."""
|
||||
"""Evaluate a simple condition expression safely."""
|
||||
import ast
|
||||
|
||||
condition = condition.strip()
|
||||
if condition.startswith("{{") and condition.endswith("}}"):
|
||||
condition = condition[2:-2].strip()
|
||||
|
||||
context = {"flags": type("Flags", (), flags)(), "env": os.environ}
|
||||
# Handle simple attribute lookups like "flags.verbose" or "env.DEBUG"
|
||||
context = {"flags": flags, "env": dict(os.environ)}
|
||||
try:
|
||||
return bool(eval(condition, {"__builtins__": {}}, context))
|
||||
except Exception:
|
||||
# Only allow simple attribute access patterns: obj.attr
|
||||
parts = condition.split(".")
|
||||
if len(parts) == 2 and parts[0] in context:
|
||||
obj = context[parts[0]]
|
||||
if isinstance(obj, dict):
|
||||
return bool(obj.get(parts[1], False))
|
||||
# Try literal eval for simple boolean/numeric expressions
|
||||
return bool(ast.literal_eval(condition))
|
||||
except (ValueError, KeyError, TypeError):
|
||||
return False
|
||||
@@ -719,17 +719,26 @@ class JobWorkflowExecutor:
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def _eval_condition(self, condition: str, flags: Dict) -> bool:
|
||||
"""Evaluate a simple condition expression."""
|
||||
"""Evaluate a simple condition expression safely."""
|
||||
import ast
|
||||
|
||||
# Strip {{ }} if present
|
||||
condition = condition.strip()
|
||||
if condition.startswith("{{") and condition.endswith("}}"):
|
||||
condition = condition[2:-2].strip()
|
||||
|
||||
# Build evaluation context
|
||||
context = {"flags": type("Flags", (), flags)(), "env": os.environ}
|
||||
# Handle simple attribute lookups like "flags.verbose" or "env.DEBUG"
|
||||
context = {"flags": flags, "env": dict(os.environ)}
|
||||
try:
|
||||
return bool(eval(condition, {"__builtins__": {}}, context))
|
||||
except Exception:
|
||||
# Only allow simple attribute access patterns: obj.attr
|
||||
parts = condition.split(".")
|
||||
if len(parts) == 2 and parts[0] in context:
|
||||
obj = context[parts[0]]
|
||||
if isinstance(obj, dict):
|
||||
return bool(obj.get(parts[1], False))
|
||||
# Try literal eval for simple boolean/numeric expressions
|
||||
return bool(ast.literal_eval(condition))
|
||||
except (ValueError, KeyError, TypeError):
|
||||
return False
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
@@ -237,7 +237,7 @@ class SurrealDBConversationStore(ConversationStore):
|
||||
params["after"] = after
|
||||
|
||||
where = "WHERE " + " AND ".join(conditions)
|
||||
limit_clause = f"LIMIT {limit}" if limit else ""
|
||||
limit_clause = f"LIMIT {int(limit)}" if limit else ""
|
||||
|
||||
result = self._run_sync(
|
||||
self._client.query(
|
||||
|
||||
@@ -6,6 +6,7 @@ Install: pip install clickhouse-connect
|
||||
"""
|
||||
|
||||
import logging
|
||||
import re
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from .base import KnowledgeStore, KnowledgeDocument
|
||||
@@ -13,6 +14,13 @@ from .base import KnowledgeStore, KnowledgeDocument
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _sanitize_identifier(name: str) -> str:
|
||||
"""Sanitize a SQL identifier (table/database name) to prevent injection."""
|
||||
if not re.match(r'^[a-zA-Z_][a-zA-Z0-9_.]*$', name):
|
||||
raise ValueError(f"Invalid identifier: {name}")
|
||||
return name
|
||||
|
||||
|
||||
class ClickHouseKnowledgeStore(KnowledgeStore):
|
||||
"""
|
||||
ClickHouse-based knowledge store using vector search.
|
||||
@@ -52,11 +60,11 @@ class ClickHouseKnowledgeStore(KnowledgeStore):
|
||||
self.database = database
|
||||
|
||||
# Create database if not exists
|
||||
self._client.command(f"CREATE DATABASE IF NOT EXISTS {database}")
|
||||
self._client.command(f"CREATE DATABASE IF NOT EXISTS {_sanitize_identifier(database)}")
|
||||
logger.info(f"Connected to ClickHouse database: {database}")
|
||||
|
||||
def _table_name(self, collection: str) -> str:
|
||||
return f"{self.database}.praison_vec_{collection}"
|
||||
return _sanitize_identifier(f"{self.database}.praison_vec_{collection}")
|
||||
|
||||
def create_collection(
|
||||
self,
|
||||
@@ -98,10 +106,11 @@ class ClickHouseKnowledgeStore(KnowledgeStore):
|
||||
|
||||
def list_collections(self) -> List[str]:
|
||||
"""List all collections."""
|
||||
result = self._client.query(f"""
|
||||
SELECT name FROM system.tables
|
||||
WHERE database = '{self.database}' AND name LIKE 'praison_vec_%'
|
||||
""")
|
||||
db = _sanitize_identifier(self.database)
|
||||
result = self._client.query(
|
||||
"SELECT name FROM system.tables WHERE database = %(db)s AND name LIKE 'praison_vec_%%'",
|
||||
parameters={"db": db}
|
||||
)
|
||||
return [row[0].replace("praison_vec_", "") for row in result.result_rows]
|
||||
|
||||
def insert(
|
||||
@@ -156,19 +165,20 @@ class ClickHouseKnowledgeStore(KnowledgeStore):
|
||||
"""Search for similar documents using cosine distance."""
|
||||
table = self._table_name(collection)
|
||||
|
||||
# Convert embedding to string for query
|
||||
embedding_str = "[" + ",".join(map(str, query_embedding)) + "]"
|
||||
|
||||
# Convert embedding to validated float array for query
|
||||
embedding_str = "[" + ",".join(str(float(x)) for x in query_embedding) + "]"
|
||||
limit = int(limit)
|
||||
|
||||
query = f"""
|
||||
SELECT
|
||||
SELECT
|
||||
id, content, content_hash, created_at,
|
||||
1 - cosineDistance(embedding, {embedding_str}) as score
|
||||
FROM {table}
|
||||
"""
|
||||
|
||||
|
||||
if score_threshold:
|
||||
query += f" WHERE 1 - cosineDistance(embedding, {embedding_str}) >= {score_threshold}"
|
||||
|
||||
query += f" WHERE 1 - cosineDistance(embedding, {embedding_str}) >= {float(score_threshold)}"
|
||||
|
||||
query += f" ORDER BY score DESC LIMIT {limit}"
|
||||
|
||||
result = self._client.query(query)
|
||||
@@ -195,11 +205,12 @@ class ClickHouseKnowledgeStore(KnowledgeStore):
|
||||
"""Get documents by IDs."""
|
||||
table = self._table_name(collection)
|
||||
|
||||
id_list = ", ".join([f"'{i}'" for i in ids])
|
||||
result = self._client.query(f"""
|
||||
SELECT id, content, content_hash, created_at, embedding
|
||||
FROM {table} WHERE id IN ({id_list})
|
||||
""")
|
||||
placeholders = ", ".join(["%(id_{})s".format(idx) for idx in range(len(ids))])
|
||||
params = {"id_{}".format(idx): i for idx, i in enumerate(ids)}
|
||||
result = self._client.query(
|
||||
f"SELECT id, content, content_hash, created_at, embedding FROM {table} WHERE id IN ({placeholders})",
|
||||
parameters=params
|
||||
)
|
||||
|
||||
documents = []
|
||||
for row in result.result_rows:
|
||||
@@ -225,8 +236,12 @@ class ClickHouseKnowledgeStore(KnowledgeStore):
|
||||
table = self._table_name(collection)
|
||||
|
||||
if ids:
|
||||
id_list = ", ".join([f"'{i}'" for i in ids])
|
||||
self._client.command(f"ALTER TABLE {table} DELETE WHERE id IN ({id_list})")
|
||||
placeholders = ", ".join(["%(id_{})s".format(idx) for idx in range(len(ids))])
|
||||
params = {"id_{}".format(idx): i for idx, i in enumerate(ids)}
|
||||
self._client.command(
|
||||
f"ALTER TABLE {table} DELETE WHERE id IN ({placeholders})",
|
||||
parameters=params
|
||||
)
|
||||
return len(ids)
|
||||
return 0
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ Install: pip install lancedb
|
||||
"""
|
||||
|
||||
import logging
|
||||
import re
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from .base import KnowledgeStore, KnowledgeDocument
|
||||
@@ -13,6 +14,18 @@ from .base import KnowledgeStore, KnowledgeDocument
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _sanitize_identifier(name: str) -> str:
|
||||
"""Sanitize a SQL identifier to prevent injection."""
|
||||
if not re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', name):
|
||||
raise ValueError(f"Invalid identifier: {name}")
|
||||
return name
|
||||
|
||||
|
||||
def _escape_value(val: str) -> str:
|
||||
"""Escape a string value for use in LanceDB WHERE clauses."""
|
||||
return str(val).replace("'", "''").replace("\\", "\\\\")
|
||||
|
||||
|
||||
class LanceDBKnowledgeStore(KnowledgeStore):
|
||||
"""
|
||||
LanceDB-based knowledge store for vector search.
|
||||
@@ -140,9 +153,9 @@ class LanceDBKnowledgeStore(KnowledgeStore):
|
||||
query = table.search(query_embedding).limit(limit)
|
||||
|
||||
if filters:
|
||||
where_clauses = [f"{k} = '{v}'" for k, v in filters.items()]
|
||||
where_clauses = [f"{_sanitize_identifier(k)} = '{_escape_value(v)}'" for k, v in filters.items()]
|
||||
query = query.where(" AND ".join(where_clauses))
|
||||
|
||||
|
||||
results = query.to_pandas()
|
||||
|
||||
documents = []
|
||||
@@ -170,7 +183,7 @@ class LanceDBKnowledgeStore(KnowledgeStore):
|
||||
"""Get documents by IDs."""
|
||||
table = self._get_table(collection)
|
||||
|
||||
id_list = ", ".join([f"'{i}'" for i in ids])
|
||||
id_list = ", ".join([f"'{_escape_value(i)}'" for i in ids])
|
||||
results = table.search().where(f"id IN ({id_list})").to_pandas()
|
||||
|
||||
documents = []
|
||||
@@ -197,11 +210,11 @@ class LanceDBKnowledgeStore(KnowledgeStore):
|
||||
table = self._get_table(collection)
|
||||
|
||||
if ids:
|
||||
id_list = ", ".join([f"'{i}'" for i in ids])
|
||||
id_list = ", ".join([f"'{_escape_value(i)}'" for i in ids])
|
||||
table.delete(f"id IN ({id_list})")
|
||||
return len(ids)
|
||||
elif filters:
|
||||
where_clauses = [f"{k} = '{v}'" for k, v in filters.items()]
|
||||
where_clauses = [f"{_sanitize_identifier(k)} = '{_escape_value(v)}'" for k, v in filters.items()]
|
||||
table.delete(" AND ".join(where_clauses))
|
||||
return -1
|
||||
return 0
|
||||
|
||||
@@ -6,6 +6,7 @@ Install: pip install pymilvus
|
||||
"""
|
||||
|
||||
import logging
|
||||
import re
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from .base import KnowledgeStore, KnowledgeDocument
|
||||
@@ -13,6 +14,18 @@ from .base import KnowledgeStore, KnowledgeDocument
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _sanitize_identifier(name: str) -> str:
|
||||
"""Sanitize a Milvus field name to prevent injection."""
|
||||
if not re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', name):
|
||||
raise ValueError(f"Invalid field name: {name}")
|
||||
return name
|
||||
|
||||
|
||||
def _escape_value(val: str) -> str:
|
||||
"""Escape a string value for use in Milvus expressions."""
|
||||
return str(val).replace('\\', '\\\\').replace('"', '\\"')
|
||||
|
||||
|
||||
class MilvusKnowledgeStore(KnowledgeStore):
|
||||
"""
|
||||
Milvus-based knowledge store for vector search.
|
||||
@@ -180,9 +193,9 @@ class MilvusKnowledgeStore(KnowledgeStore):
|
||||
|
||||
expr = None
|
||||
if filters:
|
||||
conditions = [f'{k} == "{v}"' for k, v in filters.items()]
|
||||
conditions = [f'{_sanitize_identifier(k)} == "{_escape_value(v)}"' for k, v in filters.items()]
|
||||
expr = " and ".join(conditions)
|
||||
|
||||
|
||||
results = col.search(
|
||||
data=[query_embedding],
|
||||
anns_field="vector",
|
||||
@@ -220,9 +233,9 @@ class MilvusKnowledgeStore(KnowledgeStore):
|
||||
col = Collection(collection)
|
||||
col.load()
|
||||
|
||||
id_list = ", ".join([f'"{i}"' for i in ids])
|
||||
id_list = ", ".join([f'"{_escape_value(i)}"' for i in ids])
|
||||
expr = f"id in [{id_list}]"
|
||||
|
||||
|
||||
results = col.query(
|
||||
expr=expr,
|
||||
output_fields=["id", "content", "content_hash", "created_at", "vector"],
|
||||
@@ -254,12 +267,12 @@ class MilvusKnowledgeStore(KnowledgeStore):
|
||||
col.load()
|
||||
|
||||
if ids:
|
||||
id_list = ", ".join([f'"{i}"' for i in ids])
|
||||
id_list = ", ".join([f'"{_escape_value(i)}"' for i in ids])
|
||||
expr = f"id in [{id_list}]"
|
||||
col.delete(expr)
|
||||
return len(ids)
|
||||
elif filters:
|
||||
conditions = [f'{k} == "{v}"' for k, v in filters.items()]
|
||||
conditions = [f'{_sanitize_identifier(k)} == "{_escape_value(v)}"' for k, v in filters.items()]
|
||||
expr = " and ".join(conditions)
|
||||
col.delete(expr)
|
||||
return -1
|
||||
|
||||
@@ -5,6 +5,7 @@ Requires: singlestoredb
|
||||
Install: pip install singlestoredb
|
||||
"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
@@ -227,7 +228,7 @@ class SingleStoreVectorKnowledgeStore(KnowledgeStore):
|
||||
documents.append(KnowledgeDocument(
|
||||
id=row[0],
|
||||
content=row[1],
|
||||
metadata=eval(row[2]) if row[2] else None,
|
||||
metadata=json.loads(row[2]) if row[2] else None,
|
||||
content_hash=row[3],
|
||||
created_at=row[4]
|
||||
))
|
||||
@@ -256,7 +257,7 @@ class SingleStoreVectorKnowledgeStore(KnowledgeStore):
|
||||
KnowledgeDocument(
|
||||
id=row[0],
|
||||
content=row[1],
|
||||
metadata=eval(row[2]) if row[2] else None,
|
||||
metadata=json.loads(row[2]) if row[2] else None,
|
||||
content_hash=row[3],
|
||||
created_at=row[4]
|
||||
)
|
||||
|
||||
@@ -199,8 +199,9 @@ class SurrealDBVectorKnowledgeStore(KnowledgeStore):
|
||||
"""Search for similar documents using vector search."""
|
||||
self._init_client()
|
||||
|
||||
# SurrealDB vector search query
|
||||
embedding_str = str(query_embedding)
|
||||
# SurrealDB vector search query - validate numeric inputs
|
||||
embedding_str = "[" + ",".join(str(float(x)) for x in query_embedding) + "]"
|
||||
limit = int(limit)
|
||||
query = f"""
|
||||
SELECT *, vector::similarity::cosine(embedding, {embedding_str}) AS score
|
||||
FROM {collection}
|
||||
|
||||
@@ -56,7 +56,7 @@ class PlanApplier:
|
||||
# Try to use ruamel.yaml for comment preservation
|
||||
try:
|
||||
from ruamel.yaml import YAML
|
||||
self._yaml = YAML()
|
||||
self._yaml = YAML(typ='safe')
|
||||
self._yaml.preserve_quotes = True
|
||||
self._use_ruamel = True
|
||||
except ImportError:
|
||||
|
||||
Reference in new issue
Block a user