pg-vector-filter-expression

PG Vector Filter Expression 문제

배경

  • Filter expression을 사용해서 vector store에서 값을 지우려고 시도 했음
  • pg vector filter expression을 parsing을 못 한다고 함
    • [DELETE FROM public.vector_store WHERE metadata::jsonb @@ '($.ragDocumentId == [41])'::jsonpath]
  • in 연상자를 사용했는데 ==로 파싱된 것이 이상함
List<Long> ragDocumentIds = targets.stream().map(RagDocument::getId).toList();
FilterExpressionBuilder b = new FilterExpressionBuilder();
Filter.Expression expression = b.in("ragDocumentId", ragDocumentIds).build();
vectorStore.delete(expression);
2026-01-05T12:26:01.559+09:00 ERROR 27876 --- [personal-website] [   scheduling-1] o.s.s.s.TaskUtils$LoggingErrorHandler    : Unexpected error occurred in scheduled task

java.lang.IllegalStateException: Failed to delete documents by filter
	at org.springframework.ai.vectorstore.pgvector.PgVectorStore.doDelete(PgVectorStore.java:356) ~[spring-ai-pgvector-store-1.1.2.jar:1.1.2]
	at 
...
java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642) ~[na:na]
	at java.base/java.lang.Thread.run(Thread.java:1583) ~[na:na]
Caused by: org.springframework.jdbc.BadSqlGrammarException: StatementCallback; bad SQL grammar [DELETE FROM public.vector_store WHERE metadata::jsonb @@ '($.ragDocumentId == [41])'::jsonpath]
...
org.springframework.ai.vectorstore.pgvector.PgVectorStore.doDelete(PgVectorStore.java:353) ~[spring-ai-pgvector-store-1.1.2.jar:1.1.2]
	... 29 common frames omitted
Caused by: org.postgresql.util.PSQLException: ERROR: syntax error at or near "[" of jsonpath input
  Position: 58
	at 
...

문제 분석

Pg Vector를 위한 filter expression 를 SQL query로 바꿔주는 class

  • PG의 json에서는 in 연산자이 없어서 and, or를 사용한 query로 변환
  • 파싱 될 때, List<Long> ragDocumentIds가 list가 아닌 Object로 인식 되어서 [41]로 한번에 사용됨
// PgVectorFilterExpressionConverter
@Override
protected void doExpression(Expression expression, StringBuilder context) {
	if (expression.type() == Filter.ExpressionType.IN) {
		handleIn(expression, context);
	}
	...

private void handleIn(Expression expression, StringBuilder context) {
    context.append("(");
    convertToConditions(expression, context);
    context.append(")");
}

private void convertToConditions(Expression expression, StringBuilder context) {
    ...
    List<Object> values = (List) value;
    for (int i = 0; i < values.size(); i++) {
        this.convertOperand(expression.left(), context);
        context.append(" == ");
        this.doSingleValue(values.get(i), context);
        if (i < values.size() - 1) {
            context.append(" || ");
        }
    }
}
  • in 연산자 overloading에서 method가 잘못 mapping 된 것이 문제
  • List로 매핑이 되었어야했는데 Object..로 됨
    // FilterExpressionBuilder
    public Op in(String key, Object... values) {
        return this.in(key, List.of(values));
    }
    
    public Op in(String key, List<Object> values) {
        return new Op(new Filter.Expression(ExpressionType.IN, new Key(key), new Value(values)));
    }
    

    해결

    • Object로 인식되는 문제를 없애기 위해 array로 넘김
    Long[] ragDocumentIds = targets.stream().map(RagDocument::getId).toArray();