@@ -1055,8 +1055,7 @@ def _evaluate(
1055
1055
# If provided, we don't need to create a new experiment.
1056
1056
runs = runs ,
1057
1057
# Create or resolve the experiment.
1058
- include_attachments = _include_attachments (target )
1059
- or _evaluators_include_attachments (evaluators ) > 0 ,
1058
+ include_attachments = _include_attachments (target , evaluators ),
1060
1059
upload_results = upload_results ,
1061
1060
).start ()
1062
1061
cache_dir = ls_utils .get_cache_dir (None )
@@ -1459,7 +1458,7 @@ def with_predictions(
1459
1458
self ._predict ,
1460
1459
target ,
1461
1460
max_concurrency = max_concurrency ,
1462
- include_attachments = _include_attachments (target ),
1461
+ include_attachments = _target_include_attachments (target ),
1463
1462
)
1464
1463
r1 , r2 = itertools .tee (_experiment_results , 2 )
1465
1464
return _ExperimentManager (
@@ -1901,15 +1900,10 @@ def _get_run(r: rt.RunTree) -> None:
1901
1900
client = client ,
1902
1901
)
1903
1902
try :
1904
- args = (
1905
- (example .inputs , example .attachments )
1906
- if include_attachments
1907
- else (example .inputs ,)
1908
- )
1909
- fn (
1910
- * args ,
1911
- langsmith_extra = langsmith_extra ,
1912
- )
1903
+ arg_names = _get_target_args (fn )
1904
+ args = [getattr (example , argn ) for argn in arg_names ]
1905
+ fn (* args , langsmith_extra = langsmith_extra )
1906
+ # Reset attachment readers if attachments were used.
1913
1907
if include_attachments and example .attachments is not None :
1914
1908
for attachment in example .attachments :
1915
1909
reader = example .attachments [attachment ]["reader" ]
@@ -1981,31 +1975,41 @@ def _ensure_traceable(
1981
1975
return fn
1982
1976
1983
1977
1984
- def _evaluators_include_attachments (
1985
- evaluators : Optional [Sequence [Union [EVALUATOR_T , AEVALUATOR_T ]]],
1986
- ) -> int :
1978
+ def _include_attachments (target : Any , evaluators : Optional [Sequence ]) -> bool :
1979
+ return _target_include_attachments (target ) or bool (
1980
+ _evaluators_include_attachments (evaluators )
1981
+ )
1982
+
1983
+
1984
+ def _evaluators_include_attachments (evaluators : Optional [Sequence ]) -> int :
1987
1985
if evaluators is None :
1988
1986
return 0
1989
1987
1990
- def evaluator_uses_attachments (evaluator : Any ) -> bool :
1991
- if not callable (evaluator ):
1992
- return False
1993
- sig = inspect .signature (evaluator )
1994
- params = list (sig .parameters .values ())
1995
- positional_params = [
1996
- p for p in params if p .kind in (p .POSITIONAL_ONLY , p .POSITIONAL_OR_KEYWORD )
1997
- ]
1998
- return any (p .name == "attachments" for p in positional_params )
1988
+ return sum (_evaluator_uses_attachments (e ) for e in evaluators )
1989
+
1990
+
1991
+ def _evaluator_uses_attachments (evaluator : Any ) -> bool :
1992
+ if not callable (evaluator ):
1993
+ return False
1994
+ sig = inspect .signature (evaluator )
1995
+ params = list (sig .parameters .values ())
1996
+ positional_params = [
1997
+ p for p in params if p .kind in (p .POSITIONAL_ONLY , p .POSITIONAL_OR_KEYWORD )
1998
+ ]
1999
+ return any (p .name == "attachments" for p in positional_params )
2000
+
1999
2001
2000
- return sum (evaluator_uses_attachments (e ) for e in evaluators )
2002
+ def _target_include_attachments (target : Any ) -> bool :
2003
+ """Whether the target function accepts attachments."""
2004
+ return "attachments" in _get_target_args (target )
2001
2005
2002
2006
2003
- def _include_attachments (
2004
- target : Any ,
2005
- ) -> bool :
2007
+ def _get_target_args (target : Any ) -> list [str ]:
2006
2008
"""Whether the target function accepts attachments."""
2007
- if _is_langchain_runnable (target ) or not callable (target ):
2008
- return False
2009
+ if not callable (target ):
2010
+ return []
2011
+ if _is_langchain_runnable (target ):
2012
+ return ["inputs" ]
2009
2013
# Check function signature
2010
2014
sig = inspect .signature (target )
2011
2015
params = list (sig .parameters .values ())
@@ -2018,21 +2022,27 @@ def _include_attachments(
2018
2022
raise ValueError (
2019
2023
"Target function must accept at least one positional argument (inputs)."
2020
2024
)
2021
- elif len (positional_no_default ) > 2 :
2025
+ elif len (positional_no_default ) > 3 :
2022
2026
raise ValueError (
2023
- "Target function must accept at most two "
2024
- "arguments without default values: (inputs, attachments)."
2027
+ "Target function must accept at most three "
2028
+ "arguments without default values: (inputs, attachments, metadata)."
2029
+ )
2030
+ elif len (positional_no_default ) > 1 and {
2031
+ p .name for p in positional_no_default
2032
+ }.difference (["inputs" , "attachments" , "metadata" ]):
2033
+ raise ValueError (
2034
+ "When passing multiple positional arguments without default values, they "
2035
+ "must be named 'inputs', 'attachments', or 'metadata'. Received: "
2036
+ f"{ [p .name for p in positional_no_default ]} "
2025
2037
)
2026
- elif len (positional_no_default ) == 2 :
2027
- if [p .name for p in positional_no_default ] != ["inputs" , "attachments" ]:
2028
- raise ValueError (
2029
- "When passing 2 positional arguments, they must be named "
2030
- "'inputs' and 'attachments', respectively. Received: "
2031
- f"{ [p .name for p in positional_no_default ]} "
2032
- )
2033
- return True
2034
2038
else :
2035
- return [p .name for p in positional_params [:2 ]] == ["inputs" , "attachments" ]
2039
+ args = []
2040
+ for p in positional_params [:3 ]:
2041
+ if p .name in {"inputs" , "attachments" , "metadata" }:
2042
+ args .append (p .name )
2043
+ else :
2044
+ break
2045
+ return args or ["inputs" ]
2036
2046
2037
2047
2038
2048
def _resolve_experiment (
0 commit comments