-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()).