@@ -233,6 +233,27 @@ class TraceConfig(
233233 val executor : ExecutorService ? = null ,
234234 /* * Whether tracing is active. Inherited by children. */
235235 val tracingEnabled : Boolean? = null ,
236+ /* *
237+ * Optional typed processors for transforming inputs/outputs before they are recorded on a run.
238+ *
239+ * When set, the processors in [TraceProcessIO] replace the default input/output serialization.
240+ * The generic type parameters on [TraceProcessIO] control what the processor callbacks receive
241+ * — see [TraceProcessIO] for details.
242+ *
243+ * ```java
244+ * TraceProcessIO process = TraceProcessIO.<String, MyResponse>builder()
245+ * .processInputs(input -> Map.of("query", input))
246+ * .processOutputs(resp -> Map.of("answer", resp.getText()))
247+ * .build();
248+ * TraceConfig config = TraceConfig.builder()
249+ * .name("my-run")
250+ * .processTracedIO(process)
251+ * .build();
252+ * ```
253+ *
254+ * @see TraceProcessIO
255+ */
256+ val processTracedIO : TraceProcessIO <* , * >? = null ,
236257) {
237258 companion object {
238259 /* * Creates a new [Builder] for constructing a [TraceConfig]. */
@@ -250,9 +271,6 @@ class TraceConfig(
250271 * .name("my-run")
251272 * .client(LangsmithOkHttpClient.fromEnv())
252273 * .runType(RunType.LLM)
253- * .metadata(Map.of("version", "1.0"))
254- * .tags(List.of("prod"))
255- * .projectName("my-project")
256274 * .build();
257275 * ```
258276 */
@@ -265,6 +283,7 @@ class TraceConfig(
265283 private var projectName: String? = null
266284 private var executor: ExecutorService ? = null
267285 private var tracingEnabled: Boolean? = null
286+ private var processTracedIO: TraceProcessIO <* , * >? = null
268287
269288 @JvmSynthetic
270289 internal fun from (config : TraceConfig ) = apply {
@@ -276,6 +295,7 @@ class TraceConfig(
276295 projectName = config.projectName
277296 executor = config.executor
278297 tracingEnabled = config.tracingEnabled
298+ processTracedIO = config.processTracedIO
279299 }
280300
281301 /* * The name of the run, displayed in LangSmith. */
@@ -302,6 +322,16 @@ class TraceConfig(
302322 /* * Whether tracing is active. Inherited by children if not set. */
303323 fun tracingEnabled (tracingEnabled : Boolean ) = apply { this .tracingEnabled = tracingEnabled }
304324
325+ /* *
326+ * Typed processors for transforming inputs/outputs before they are recorded.
327+ *
328+ * @see TraceConfig.processTracedIO
329+ * @see TraceProcessIO
330+ */
331+ fun processTracedIO (processTracedIO : TraceProcessIO <* , * >) = apply {
332+ this .processTracedIO = processTracedIO
333+ }
334+
305335 /* * Builds the [TraceConfig]. */
306336 fun build () =
307337 TraceConfig (
@@ -313,12 +343,21 @@ class TraceConfig(
313343 projectName = projectName,
314344 executor = executor,
315345 tracingEnabled = tracingEnabled,
346+ processTracedIO = processTracedIO,
316347 )
317348 }
318349}
319350
320351private const val DEFAULT_RUN_NAME = " <lambda>"
321352
353+ /* * Applies processInputs from the config, or returns null if not set. */
354+ private fun applyProcessInputs (config : TraceConfig , value : Any? ): Map <String , Any ?>? =
355+ config.processTracedIO?.inputsFn?.apply (value)
356+
357+ /* * Applies processOutputs from the config, or returns null if not set. */
358+ private fun applyProcessOutputs (config : TraceConfig , value : Any? ): Map <String , Any ?>? =
359+ config.processTracedIO?.outputsFn?.apply (value)
360+
322361/* * Resolves the run name from the config and the function being wrapped. */
323362private fun resolveName (config : TraceConfig , block : Any? ): String {
324363 config.name?.let {
@@ -328,6 +367,12 @@ private fun resolveName(config: TraceConfig, block: Any?): String {
328367 return DEFAULT_RUN_NAME
329368}
330369
370+ /* * Creates a copy of [config] with the resolved name set. */
371+ private fun resolveConfig (config : TraceConfig , block : Any? ): TraceConfig {
372+ val name = resolveName(config, block)
373+ return if (name == config.name) config else config.toBuilder().name(name).build()
374+ }
375+
331376/* *
332377 * Wraps a no-arg function with LangSmith tracing (Kotlin).
333378 *
@@ -339,14 +384,22 @@ private fun resolveName(config: TraceConfig, block: Any?): String {
339384 */
340385@JvmSynthetic
341386fun <O > traceable (block : () -> O , config : TraceConfig ): () -> O {
342- val resolvedConfig = config.toBuilder().name(resolveName(config, block)).build()
343- return { executeTraced(resolvedConfig, emptyMap()) { block() } }
387+ val resolvedConfig = resolveConfig(config, block)
388+ return {
389+ val packed = emptyMap<String , Any ?>()
390+ val serializedInputs = applyProcessInputs(resolvedConfig, packed) ? : packed
391+ executeTraced(resolvedConfig, serializedInputs) { block() }
392+ }
344393}
345394
346395/* * Wraps a no-arg function with LangSmith tracing (Java [Supplier]). */
347396fun <O > traceable (block : Supplier <O >, config : TraceConfig ): Supplier <O > {
348- val resolvedConfig = config.toBuilder().name(resolveName(config, block)).build()
349- return Supplier { executeTraced(resolvedConfig, emptyMap()) { block.get() } }
397+ val resolvedConfig = resolveConfig(config, block)
398+ return Supplier {
399+ val packed = emptyMap<String , Any ?>()
400+ val serializedInputs = applyProcessInputs(resolvedConfig, packed) ? : packed
401+ executeTraced(resolvedConfig, serializedInputs) { block.get() }
402+ }
350403}
351404
352405/* *
@@ -393,10 +446,10 @@ fun <O> traceable(block: Supplier<O>, config: TraceConfig): Supplier<O> {
393446 */
394447@JvmSynthetic
395448fun <I , O > traceable (block : (I ) -> O , config : TraceConfig ): (I ) -> O {
396- val resolvedConfig = config.toBuilder().name(resolveName( config, block)).build( )
449+ val resolvedConfig = resolveConfig( config, block)
397450 return { input ->
398- val inputs = toInputMap(input)
399- executeTraced(resolvedConfig, inputs ) { block(input) }
451+ val serializedInputs = applyProcessInputs(resolvedConfig, input) ? : toInputMap(input)
452+ executeTraced(resolvedConfig, serializedInputs ) { block(input) }
400453 }
401454}
402455
@@ -409,10 +462,11 @@ fun <I, O> traceable(block: Function<I, O>, config: TraceConfig): Function<I, O>
409462/* * Wraps a 2-arg function with LangSmith tracing (Kotlin). */
410463@JvmSynthetic
411464fun <I1 , I2 , O > traceable (block : (I1 , I2 ) -> O , config : TraceConfig ): (I1 , I2 ) -> O {
412- val resolvedConfig = config.toBuilder().name(resolveName( config, block)).build( )
465+ val resolvedConfig = resolveConfig( config, block)
413466 return { i1, i2 ->
414- val inputs = mapOf (" args" to listOf (i1, i2))
415- executeTraced(resolvedConfig, inputs) { block(i1, i2) }
467+ val packed = mapOf<String , Any ?>(" args" to listOf (i1, i2))
468+ val serializedInputs = applyProcessInputs(resolvedConfig, packed) ? : packed
469+ executeTraced(resolvedConfig, serializedInputs) { block(i1, i2) }
416470 }
417471}
418472
@@ -428,10 +482,11 @@ fun <I1, I2, O> traceable(
428482/* * Wraps a 3-arg function with LangSmith tracing (Kotlin). */
429483@JvmSynthetic
430484fun <I1 , I2 , I3 , O > traceable (block : (I1 , I2 , I3 ) -> O , config : TraceConfig ): (I1 , I2 , I3 ) -> O {
431- val resolvedConfig = config.toBuilder().name(resolveName( config, block)).build( )
485+ val resolvedConfig = resolveConfig( config, block)
432486 return { i1, i2, i3 ->
433- val inputs = mapOf (" args" to listOf (i1, i2, i3))
434- executeTraced(resolvedConfig, inputs) { block(i1, i2, i3) }
487+ val packed = mapOf<String , Any ?>(" args" to listOf (i1, i2, i3))
488+ val serializedInputs = applyProcessInputs(resolvedConfig, packed) ? : packed
489+ executeTraced(resolvedConfig, serializedInputs) { block(i1, i2, i3) }
435490 }
436491}
437492
@@ -540,7 +595,7 @@ fun <I1, I2, I3, O> traceTriFunction(
540595 *
541596 * For best tracing results, pass [Map] inputs/outputs to [traceable]. Typed SDK objects (e.g.
542597 * `ChatCompletionCreateParams`) should be converted to maps by the caller — use
543- * `processInputs`/`processOutputs` callbacks (when available) or manual conversion.
598+ * [TraceConfig.processTracedIO] callbacks or manual conversion.
544599 */
545600private fun serializeValue (value : Any? ): Any? =
546601 when (value) {
@@ -630,7 +685,7 @@ private fun <T> executeTraced(config: TraceConfig, inputs: Map<String, Any?>?, b
630685 try {
631686 val result = block()
632687 run.endTime = nowIso()
633- run.outputs = toOutputMap(result)
688+ run.outputs = applyProcessOutputs(config, result) ? : toOutputMap(result)
634689 run.patchRun()
635690 result
636691 } catch (e: Throwable ) {
0 commit comments