( parent context.Context, provider, model string, clock quartz.Clock, timeout time.Duration, openStream func(context.Context) (fantasy.StreamResponse, error), metrics *Metrics, )
| 874 | } |
| 875 | |
| 876 | func guardedStream( |
| 877 | parent context.Context, |
| 878 | provider, model string, |
| 879 | clock quartz.Clock, |
| 880 | timeout time.Duration, |
| 881 | openStream func(context.Context) (fantasy.StreamResponse, error), |
| 882 | metrics *Metrics, |
| 883 | ) (guardedAttempt, error) { |
| 884 | attemptCtx, cancelAttempt := context.WithCancelCause(parent) |
| 885 | guard := newStreamSilenceGuard(clock, timeout, cancelAttempt) |
| 886 | var releaseOnce sync.Once |
| 887 | release := func() { |
| 888 | releaseOnce.Do(func() { |
| 889 | guard.Disarm() |
| 890 | cancelAttempt(nil) |
| 891 | }) |
| 892 | } |
| 893 | |
| 894 | streamStart := clock.Now() |
| 895 | stream, err := openStream(attemptCtx) |
| 896 | if err != nil { |
| 897 | err = classifyStreamSilenceTimeout(attemptCtx, provider, err) |
| 898 | release() |
| 899 | return guardedAttempt{}, err |
| 900 | } |
| 901 | |
| 902 | recordTTFT := sync.OnceFunc(func() { |
| 903 | metrics.TTFTSeconds.WithLabelValues(provider, model).Observe( |
| 904 | clock.Since(streamStart).Seconds(), |
| 905 | ) |
| 906 | }) |
| 907 | return guardedAttempt{ |
| 908 | ctx: attemptCtx, |
| 909 | stream: fantasy.StreamResponse(func(yield func(fantasy.StreamPart) bool) { |
| 910 | for part := range stream { |
| 911 | guard.Reset() |
| 912 | recordTTFT() |
| 913 | if !yield(part) { |
| 914 | return |
| 915 | } |
| 916 | } |
| 917 | }), |
| 918 | release: release, |
| 919 | finish: func(err error) error { |
| 920 | return classifyStreamSilenceTimeout(attemptCtx, provider, err) |
| 921 | }, |
| 922 | }, nil |
| 923 | } |
| 924 | |
| 925 | // processStepStream consumes a fantasy StreamResponse and |
| 926 | // accumulates all content into a stepResult. Callbacks fire |
no test coverage detected