-module(langfuse_client@metrics). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/langfuse_client/metrics.gleam"). -export([scorer_names/1, score_count_query/4, decode/1, list_score_counts/2, score_value_query/3, list_score_values/2, decode_score_values/1]). -export_type([score_view/0, score_count_row/0, filter/0, string_options_operator/0, score_count_query/0, score_value_row/0, score_value_query/0]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. ?MODULEDOC( " `GET /api/public/v2/metrics` — server-side aggregations over Langfuse\n" " score data. Build a query with `score_count_query` /\n" " `score_value_query` and pass it to the matching `list_*` function.\n" "\n" " The Langfuse v2 metrics endpoint is BETA. This module exposes score\n" " count + avg-value queries grouped by `(name, dataType, source)`, with\n" " optional server-side filters. Broader surface (other measures, views,\n" " dimensions) will follow the same shape once the endpoint stabilises.\n" ). -type score_view() :: scores_numeric | scores_categorical. -type score_count_row() :: {score_count_row, binary(), binary(), binary(), integer()}. -type filter() :: {string_options, binary(), string_options_operator(), list(binary())}. -type string_options_operator() :: any_of | none_of. -type score_count_query() :: {score_count_query, score_view(), binary(), binary(), list(filter())}. -type score_value_row() :: {score_value_row, binary(), binary(), binary(), float()}. -type score_value_query() :: {score_value_query, binary(), binary(), list(filter())}. -file("src/langfuse_client/metrics.gleam", 57). ?DOC( " Convenience: filter to scores whose `name` (scorer) is in the given\n" " list. Saves bytes on the wire and downstream work — server-side filter\n" " always preferred over client-side.\n" ). -spec scorer_names(list(binary())) -> filter(). scorer_names(Names) -> {string_options, <<"name"/utf8>>, any_of, Names}. -file("src/langfuse_client/metrics.gleam", 74). ?DOC( " Build a query for counts of scores grouped by `(name, data_type,\n" " source)` in the given window, with optional server-side filters.\n" ). -spec score_count_query(score_view(), binary(), binary(), list(filter())) -> score_count_query(). score_count_query(View, From_timestamp, To_timestamp, Filters) -> {score_count_query, View, From_timestamp, To_timestamp, Filters}. -file("src/langfuse_client/metrics.gleam", 103). ?DOC( " Parse a `GET /api/public/v2/metrics` response body for a score-count\n" " query. Useful if you already have the raw body in hand (e.g. from a\n" " cached/recorded response).\n" ). -spec decode(binary()) -> {ok, list(score_count_row())} | {error, gleam@json:decode_error()}. decode(Body) -> gleam@json:parse(Body, rows_decoder()). -file("src/langfuse_client/metrics.gleam", 208). -spec row_decoder() -> gleam@dynamic@decode:decoder(score_count_row()). row_decoder() -> gleam@dynamic@decode:field( <<"name"/utf8>>, {decoder, fun gleam@dynamic@decode:decode_string/1}, fun(Name) -> gleam@dynamic@decode:field( <<"dataType"/utf8>>, {decoder, fun gleam@dynamic@decode:decode_string/1}, fun(Data_type) -> gleam@dynamic@decode:field( <<"source"/utf8>>, {decoder, fun gleam@dynamic@decode:decode_string/1}, fun(Source) -> gleam@dynamic@decode:field( <<"sum_count"/utf8>>, {decoder, fun gleam@dynamic@decode:decode_string/1}, fun(Sum_count_str) -> Count = gleam@result:unwrap( gleam_stdlib:parse_int(Sum_count_str), 0 ), gleam@dynamic@decode:success( {score_count_row, Name, Data_type, Source, Count} ) end ) end ) end ) end ). -file("src/langfuse_client/metrics.gleam", 203). -spec rows_decoder() -> gleam@dynamic@decode:decoder(list(score_count_row())). rows_decoder() -> gleam@dynamic@decode:field( <<"data"/utf8>>, gleam@dynamic@decode:list(row_decoder()), fun(Rows) -> gleam@dynamic@decode:success(Rows) end ). -file("src/langfuse_client/metrics.gleam", 274). -spec string_options_operator_string(string_options_operator()) -> binary(). string_options_operator_string(Op) -> case Op of any_of -> <<"any of"/utf8>>; none_of -> <<"none of"/utf8>> end. -file("src/langfuse_client/metrics.gleam", 262). -spec filter_to_json(filter()) -> gleam@json:json(). filter_to_json(F) -> case F of {string_options, Column, Operator, Values} -> gleam@json:object( [{<<"type"/utf8>>, gleam@json:string(<<"stringOptions"/utf8>>)}, {<<"column"/utf8>>, gleam@json:string(Column)}, {<<"operator"/utf8>>, gleam@json:string( string_options_operator_string(Operator) )}, {<<"value"/utf8>>, gleam@json:array(Values, fun gleam@json:string/1)}] ) end. -file("src/langfuse_client/metrics.gleam", 258). -spec filters_to_json(list(filter())) -> gleam@json:json(). filters_to_json(Filters) -> gleam@json:preprocessed_array(gleam@list:map(Filters, fun filter_to_json/1)). -file("src/langfuse_client/metrics.gleam", 187). -spec score_count_metrics() -> list(gleam@json:json()). score_count_metrics() -> [gleam@json:object( [{<<"measure"/utf8>>, gleam@json:string(<<"count"/utf8>>)}, {<<"aggregation"/utf8>>, gleam@json:string(<<"sum"/utf8>>)}] )]. -file("src/langfuse_client/metrics.gleam", 179). -spec score_count_dimensions() -> list(gleam@json:json()). score_count_dimensions() -> [gleam@json:object([{<<"field"/utf8>>, gleam@json:string(<<"name"/utf8>>)}]), gleam@json:object( [{<<"field"/utf8>>, gleam@json:string(<<"dataType"/utf8>>)}] ), gleam@json:object( [{<<"field"/utf8>>, gleam@json:string(<<"source"/utf8>>)}] )]. -file("src/langfuse_client/metrics.gleam", 196). -spec view_string(score_view()) -> binary(). view_string(V) -> case V of scores_numeric -> <<"scores-numeric"/utf8>>; scores_categorical -> <<"scores-categorical"/utf8>> end. -file("src/langfuse_client/metrics.gleam", 167). -spec query_body(score_count_query()) -> binary(). query_body(Q) -> _pipe = gleam@json:object( [{<<"view"/utf8>>, gleam@json:string(view_string(erlang:element(2, Q)))}, {<<"fromTimestamp"/utf8>>, gleam@json:string(erlang:element(3, Q))}, {<<"toTimestamp"/utf8>>, gleam@json:string(erlang:element(4, Q))}, {<<"dimensions"/utf8>>, gleam@json:preprocessed_array(score_count_dimensions())}, {<<"metrics"/utf8>>, gleam@json:preprocessed_array(score_count_metrics())}, {<<"filters"/utf8>>, filters_to_json(erlang:element(5, Q))}] ), gleam@json:to_string(_pipe). -file("src/langfuse_client/metrics.gleam", 88). ?DOC( " Aggregate score counts for the query. Returns one row per distinct\n" " `(name, data_type, source)` combination present in the window after\n" " filters are applied. Erlang-only — see `langfuse_client/client` module\n" " docs.\n" ). -spec list_score_counts(langfuse_client@client:client(), score_count_query()) -> {ok, list(score_count_row())} | {error, langfuse_client@client:error()}. list_score_counts(C, Q) -> langfuse_client@client:send_get( C, <<"/api/public/v2/metrics"/utf8>>, [{<<"query"/utf8>>, query_body(Q)}], rows_decoder() ). -file("src/langfuse_client/metrics.gleam", 133). ?DOC( " Build a query for avg score values grouped by `(name, data_type,\n" " source)` in the given window, with optional server-side filters. Only\n" " numeric and boolean scores are returned.\n" ). -spec score_value_query(binary(), binary(), list(filter())) -> score_value_query(). score_value_query(From_timestamp, To_timestamp, Filters) -> {score_value_query, From_timestamp, To_timestamp, Filters}. -file("src/langfuse_client/metrics.gleam", 254). -spec lenient_float() -> gleam@dynamic@decode:decoder(float()). lenient_float() -> gleam@dynamic@decode:one_of( {decoder, fun gleam@dynamic@decode:decode_float/1}, [begin _pipe = {decoder, fun gleam@dynamic@decode:decode_int/1}, gleam@dynamic@decode:map(_pipe, fun erlang:float/1) end] ). -file("src/langfuse_client/metrics.gleam", 244). -spec value_row_decoder() -> gleam@dynamic@decode:decoder(score_value_row()). value_row_decoder() -> gleam@dynamic@decode:field( <<"name"/utf8>>, {decoder, fun gleam@dynamic@decode:decode_string/1}, fun(Name) -> gleam@dynamic@decode:field( <<"dataType"/utf8>>, {decoder, fun gleam@dynamic@decode:decode_string/1}, fun(Data_type) -> gleam@dynamic@decode:field( <<"source"/utf8>>, {decoder, fun gleam@dynamic@decode:decode_string/1}, fun(Source) -> gleam@dynamic@decode:field( <<"avg_value"/utf8>>, lenient_float(), fun(Avg_value) -> gleam@dynamic@decode:success( {score_value_row, Name, Data_type, Source, Avg_value} ) end ) end ) end ) end ). -file("src/langfuse_client/metrics.gleam", 239). -spec value_rows_decoder() -> gleam@dynamic@decode:decoder(list(score_value_row())). value_rows_decoder() -> gleam@dynamic@decode:field( <<"data"/utf8>>, gleam@dynamic@decode:list(value_row_decoder()), fun(Rows) -> gleam@dynamic@decode:success(Rows) end ). -file("src/langfuse_client/metrics.gleam", 230). -spec score_value_metrics() -> list(gleam@json:json()). score_value_metrics() -> [gleam@json:object( [{<<"measure"/utf8>>, gleam@json:string(<<"value"/utf8>>)}, {<<"aggregation"/utf8>>, gleam@json:string(<<"avg"/utf8>>)}] )]. -file("src/langfuse_client/metrics.gleam", 218). -spec value_query_body(score_value_query()) -> binary(). value_query_body(Q) -> _pipe = gleam@json:object( [{<<"view"/utf8>>, gleam@json:string(<<"scores-numeric"/utf8>>)}, {<<"fromTimestamp"/utf8>>, gleam@json:string(erlang:element(2, Q))}, {<<"toTimestamp"/utf8>>, gleam@json:string(erlang:element(3, Q))}, {<<"dimensions"/utf8>>, gleam@json:preprocessed_array(score_count_dimensions())}, {<<"metrics"/utf8>>, gleam@json:preprocessed_array(score_value_metrics())}, {<<"filters"/utf8>>, filters_to_json(erlang:element(4, Q))}] ), gleam@json:to_string(_pipe). -file("src/langfuse_client/metrics.gleam", 145). ?DOC( " Aggregate avg score values for the query. One row per `(name,\n" " data_type, source)` combination over the window after filters are\n" " applied. Erlang-only — see `langfuse_client/client` module docs.\n" ). -spec list_score_values(langfuse_client@client:client(), score_value_query()) -> {ok, list(score_value_row())} | {error, langfuse_client@client:error()}. list_score_values(C, Q) -> langfuse_client@client:send_get( C, <<"/api/public/v2/metrics"/utf8>>, [{<<"query"/utf8>>, value_query_body(Q)}], value_rows_decoder() ). -file("src/langfuse_client/metrics.gleam", 159). ?DOC( " Parse a `GET /api/public/v2/metrics` response body for a score-value\n" " query.\n" ). -spec decode_score_values(binary()) -> {ok, list(score_value_row())} | {error, gleam@json:decode_error()}. decode_score_values(Body) -> gleam@json:parse(Body, value_rows_decoder()).