@@ -5,6 +5,7 @@ package com.langchain.smith.services.async
55import com.langchain.smith.client.AutoBatchIngestLimits
66import com.langchain.smith.client.AutoBatchQueue
77import com.langchain.smith.client.toAutoBatchIngestLimits
8+ import com.langchain.smith.client.toRunMultipartFormData
89import com.langchain.smith.core.ClientOptions
910import com.langchain.smith.core.RequestOptions
1011import com.langchain.smith.core.checkRequired
@@ -19,6 +20,7 @@ import com.langchain.smith.core.http.HttpResponseFor
1920import com.langchain.smith.core.http.json
2021import com.langchain.smith.core.http.parseable
2122import com.langchain.smith.core.prepareAsync
23+ import com.langchain.smith.errors.NotFoundException
2224import com.langchain.smith.models.runs.RunCreateParams
2325import com.langchain.smith.models.runs.RunCreateResponse
2426import com.langchain.smith.models.runs.RunIngestBatchParams
@@ -37,35 +39,77 @@ import com.langchain.smith.services.async.annotationqueues.InfoServiceAsyncImpl
3739import com.langchain.smith.services.async.runs.RuleServiceAsync
3840import com.langchain.smith.services.async.runs.RuleServiceAsyncImpl
3941import java.util.concurrent.CompletableFuture
42+ import java.util.concurrent.CompletionException
43+ import java.util.concurrent.atomic.AtomicBoolean
4044import java.util.function.Consumer
4145import kotlin.jvm.optionals.getOrNull
4246import org.slf4j.LoggerFactory
4347
4448class RunServiceAsyncImpl internal constructor(private val clientOptions : ClientOptions ) :
4549 RunServiceAsync {
4650
47- private val withRawResponse: RunServiceAsync .WithRawResponse by lazy {
48- WithRawResponseImpl (clientOptions)
49- }
51+ private val withRawResponse: WithRawResponseImpl by lazy { WithRawResponseImpl (clientOptions) }
5052
5153 private val rules: RuleServiceAsync by lazy { RuleServiceAsyncImpl (clientOptions) }
54+ private val multipartDisabled = AtomicBoolean (false )
5255
5356 private val batchQueue: AutoBatchQueue by lazy {
5457 val limits = fetchAutoBatchIngestLimits()
5558 AutoBatchQueue (
56- sendBatch = { params, requestOptions ->
57- withRawResponse().ingestBatch(params, requestOptions).thenApply {
58- it.parse()
59- null
60- }
61- },
59+ sendBatch = { params, requestOptions -> sendAutoBatch(params, requestOptions, limits) },
6260 batchSizeLimit = limits.batchSizeLimit,
6361 batchSizeLimitBytes = limits.batchSizeLimitBytes,
6462 )
6563 }
6664
6765 override fun withRawResponse (): RunServiceAsync .WithRawResponse = withRawResponse
6866
67+ private fun sendAutoBatch (
68+ params : RunIngestBatchParams ,
69+ requestOptions : RequestOptions ,
70+ limits : AutoBatchIngestLimits ,
71+ ): CompletableFuture <Void ?> {
72+ if (! limits.useMultipartEndpoint || multipartDisabled.get()) {
73+ return sendJsonBatch(params, requestOptions)
74+ }
75+
76+ val multipartFuture =
77+ try {
78+ withRawResponse.ingestMultipartBatch(params, requestOptions)
79+ } catch (e: Exception ) {
80+ return failedFuture(e)
81+ }
82+
83+ return multipartFuture
84+ .handle { sentMultipart, throwable ->
85+ if (throwable == null ) {
86+ if (sentMultipart) {
87+ CompletableFuture .completedFuture<Void ?>(null )
88+ } else {
89+ sendJsonBatch(params, requestOptions)
90+ }
91+ } else {
92+ val cause = (throwable as ? CompletionException )?.cause ? : throwable
93+ if (cause is NotFoundException ) {
94+ multipartDisabled.set(true )
95+ sendJsonBatch(params, requestOptions)
96+ } else {
97+ failedFuture(cause)
98+ }
99+ }
100+ }
101+ .thenCompose { it }
102+ }
103+
104+ private fun sendJsonBatch (
105+ params : RunIngestBatchParams ,
106+ requestOptions : RequestOptions ,
107+ ): CompletableFuture <Void ?> =
108+ withRawResponse().ingestBatch(params, requestOptions).thenApply {
109+ it.parse()
110+ null
111+ }
112+
69113 private fun fetchAutoBatchIngestLimits () =
70114 try {
71115 InfoServiceAsyncImpl (clientOptions)
@@ -177,6 +221,9 @@ class RunServiceAsyncImpl internal constructor(private val clientOptions: Client
177221
178222 private companion object {
179223 private val logger = LoggerFactory .getLogger(RunServiceAsyncImpl ::class .java)
224+
225+ private fun <T > failedFuture (throwable : Throwable ): CompletableFuture <T > =
226+ CompletableFuture <T >().also { it.completeExceptionally(throwable) }
180227 }
181228
182229 class WithRawResponseImpl internal constructor(private val clientOptions : ClientOptions ) :
@@ -327,6 +374,33 @@ class RunServiceAsyncImpl internal constructor(private val clientOptions: Client
327374 }
328375 }
329376
377+ internal fun ingestMultipartBatch (
378+ params : RunIngestBatchParams ,
379+ requestOptions : RequestOptions ,
380+ ): CompletableFuture <Boolean > {
381+ val body = params.toRunMultipartFormData(clientOptions.jsonMapper)
382+ if (body == null ) {
383+ // Some queued runs do not have the fields required by multipart ingest; fall
384+ // back to legacy JSON batch ingest for this batch only.
385+ return CompletableFuture .completedFuture(false )
386+ }
387+ val request =
388+ HttpRequest .builder()
389+ .method(HttpMethod .POST )
390+ .baseUrl(clientOptions.baseUrl())
391+ .addPathSegments(" runs" , " multipart" )
392+ .body(body)
393+ .build()
394+ .prepareAsync(clientOptions, params)
395+ val requestOptions = requestOptions.applyDefaults(RequestOptions .from(clientOptions))
396+ return request
397+ .thenComposeAsync { clientOptions.httpClient.executeAsync(it, requestOptions) }
398+ .thenApply { response ->
399+ errorHandler.handle(response).use {}
400+ true
401+ }
402+ }
403+
330404 private val queryHandler: Handler <RunQueryResponse > =
331405 jsonHandler<RunQueryResponse >(clientOptions.jsonMapper)
332406
0 commit comments