13
13
from sqlalchemy .orm .util import AliasedClass
14
14
from sqlalchemy .sql .expression import Select
15
15
from typing_extensions import TypeAlias , TypeGuard , assert_never
16
+ from sqlalchemy import case , literal
16
17
17
18
import phoenix .trace .v1 as pb
18
19
from phoenix .db import models
31
32
r"""\b((annotations|evals)\[(".*?"|'.*?')\][.](label|score))\b"""
32
33
)
33
34
35
+ EVAL_NAME_PATTERN = re .compile (
36
+ r"""\b((annotations|evals)\[(\".*?\"|'.*?')\])\b"""
37
+ )
38
+
34
39
35
40
@dataclass (frozen = True )
36
41
class AliasedAnnotationRelation :
@@ -46,16 +51,19 @@ class AliasedAnnotationRelation:
46
51
table : AliasedClass [models .SpanAnnotation ] = field (init = False , repr = False )
47
52
_label_attribute_alias : str = field (init = False , repr = False )
48
53
_score_attribute_alias : str = field (init = False , repr = False )
54
+ _exists_attribute_alias : str = field (init = False , repr = False )
49
55
50
56
def __post_init__ (self ) -> None :
51
57
table_alias = f"span_annotation_{ self .index } "
52
58
alias_id = uuid4 ().hex
53
59
label_attribute_alias = f"{ table_alias } _label_{ alias_id } "
54
60
score_attribute_alias = f"{ table_alias } _score_{ alias_id } "
61
+ exists_attribute_alias = f"{ table_alias } _exists_{ alias_id } "
55
62
56
63
table = aliased (models .SpanAnnotation , name = table_alias )
57
64
object .__setattr__ (self , "_label_attribute_alias" , label_attribute_alias )
58
65
object .__setattr__ (self , "_score_attribute_alias" , score_attribute_alias )
66
+ object .__setattr__ (self , "_exists_attribute_alias" , exists_attribute_alias )
59
67
object .__setattr__ (self , "table" , table )
60
68
61
69
@property
@@ -66,6 +74,9 @@ def attributes(self) -> typing.Iterator[tuple[str, Mapped[typing.Any]]]:
66
74
"""
67
75
yield self ._label_attribute_alias , self .table .label
68
76
yield self ._score_attribute_alias , self .table .score
77
+ yield self ._exists_attribute_alias , case (
78
+ (self .table .id .is_not (None ), literal (True ))
79
+ )
69
80
70
81
def attribute_alias (self , attribute : AnnotationAttribute ) -> str :
71
82
"""
@@ -555,6 +566,7 @@ def _validate_expression(
555
566
isinstance (node , (ast .BoolOp , ast .Compare ))
556
567
or isinstance (node , ast .UnaryOp )
557
568
and isinstance (node .op , ast .Not )
569
+ or _is_annotation (node )
558
570
):
559
571
continue
560
572
elif (
@@ -792,6 +804,15 @@ def _apply_eval_aliasing(
792
804
eval_aliases [annotation_name ] = eval_alias
793
805
alias_name = eval_alias .attribute_alias (annotation_attribute )
794
806
source = source .replace (annotation_expression , alias_name )
807
+
808
+ for match in EVAL_NAME_PATTERN .finditer (source ):
809
+ annotation_expression , annotation_type , quoted_eval_name = match .groups ()
810
+ annotation_name = quoted_eval_name [1 :- 1 ]
811
+ if (eval_alias := eval_aliases .get (annotation_name )) is None :
812
+ eval_alias = AliasedAnnotationRelation (index = len (eval_aliases ), name = annotation_name )
813
+ eval_aliases [annotation_name ] = eval_alias
814
+ alias_name = eval_alias ._exists_attribute_alias
815
+ source = source .replace (annotation_expression , alias_name )
795
816
return source , tuple (eval_aliases .values ())
796
817
797
818
0 commit comments