Process a request to completion while reporting each model call to `observer`.
(
self: Arc<Self>,
ctx: Context,
request: Request,
observer: Option<RunObserver>,
)
| 717 | |
| 718 | /// Process a request to completion while reporting each model call to `observer`. |
| 719 | async fn run_observed( |
| 720 | self: Arc<Self>, |
| 721 | ctx: Context, |
| 722 | request: Request, |
| 723 | observer: Option<RunObserver>, |
| 724 | ) -> Result<(Vec<Arc<dyn Decision>>, Response)> { |
| 725 | // Serve one offloaded call with its target's default client. A failed *model* |
| 726 | // call is forwarded to the algorithm via `respond`; this errors only on an |
| 727 | // infrastructure failure (no default client, or the promise was dropped). |
| 728 | // `serve` makes the one API call libsy itself performs, so it gets its |
| 729 | // own `libsy.client_call` span. |
| 730 | #[tracing::instrument( |
| 731 | target = "libsy", |
| 732 | name = "libsy.client_call", |
| 733 | skip_all, |
| 734 | fields( |
| 735 | algorithm = observability::algorithm_label(&call.get_routed().ctx), |
| 736 | switchyard.algorithm = observability::algorithm_label(&call.get_routed().ctx), |
| 737 | switchyard.routing.tier = tracing::field::Empty, |
| 738 | selected_model = call.get_decision().selected_model(), |
| 739 | otel.kind = "client", |
| 740 | otel.name = %format_args!("chat {}", call.get_decision().selected_model()), |
| 741 | openinference.span.kind = "LLM", |
| 742 | gen_ai.operation.name = "chat", |
| 743 | gen_ai.request.model = call.get_decision().selected_model(), |
| 744 | gen_ai.request.stream = tracing::field::Empty, |
| 745 | gen_ai.request.temperature = tracing::field::Empty, |
| 746 | gen_ai.request.top_p = tracing::field::Empty, |
| 747 | gen_ai.request.top_k = tracing::field::Empty, |
| 748 | gen_ai.request.max_tokens = tracing::field::Empty, |
| 749 | gen_ai.request.reasoning.level = tracing::field::Empty, |
| 750 | gen_ai.output.type = tracing::field::Empty, |
| 751 | gen_ai.conversation.id = tracing::field::Empty, |
| 752 | server.address = tracing::field::Empty, |
| 753 | server.port = tracing::field::Empty, |
| 754 | gen_ai.response.id = tracing::field::Empty, |
| 755 | gen_ai.response.model = tracing::field::Empty, |
| 756 | gen_ai.usage.input_tokens = tracing::field::Empty, |
| 757 | gen_ai.usage.output_tokens = tracing::field::Empty, |
| 758 | gen_ai.usage.cache_read.input_tokens = tracing::field::Empty, |
| 759 | gen_ai.usage.cache_creation.input_tokens = tracing::field::Empty, |
| 760 | gen_ai.usage.reasoning.output_tokens = tracing::field::Empty, |
| 761 | outcome = tracing::field::Empty, |
| 762 | otel.status_code = tracing::field::Empty, |
| 763 | error.type = tracing::field::Empty, |
| 764 | error = tracing::field::Empty, |
| 765 | ) |
| 766 | )] |
| 767 | async fn serve(call: CallLlmRequest) -> Result<()> { |
| 768 | let span = tracing::Span::current(); |
| 769 | observability::record_gen_ai_request(&span, &call.get_routed().request.llm_request); |
| 770 | if let Some(tier) = call.get_decision().routing_tier() { |
| 771 | span.record("switchyard.routing.tier", tier); |
| 772 | } |
| 773 | if let Some(session_id) = call |
| 774 | .get_routed() |
| 775 | .request |
| 776 | .metadata |
no test coverage detected