diff --git a/bootstrap/pom.xml b/bootstrap/pom.xml index 30c7d9b..2a65804 100644 --- a/bootstrap/pom.xml +++ b/bootstrap/pom.xml @@ -43,6 +43,21 @@ launcher-ktor ${project.version} + + io.github.llmagentbuilder + open-telemetry + ${project.version} + + + io.github.llmagentbuilder + llm-openai + ${project.version} + + + io.github.llmagentbuilder + agent-planner-react-json + ${project.version} + org.slf4j slf4j-api diff --git a/bootstrap/src/main/kotlin/io/github/llmagentbuilder/bootstrap/AgentBootstrap.kt b/bootstrap/src/main/kotlin/io/github/llmagentbuilder/bootstrap/AgentBootstrap.kt index 5dce988..2bab5b7 100644 --- a/bootstrap/src/main/kotlin/io/github/llmagentbuilder/bootstrap/AgentBootstrap.kt +++ b/bootstrap/src/main/kotlin/io/github/llmagentbuilder/bootstrap/AgentBootstrap.kt @@ -6,6 +6,8 @@ import io.github.llmagentbuilder.core.* import io.github.llmagentbuilder.core.tool.AgentToolFunctionCallbackContext import io.github.llmagentbuilder.core.tool.AgentToolsProviderFactory import io.github.llmagentbuilder.launcher.ktor.server.KtorLauncher +import io.github.llmagentbuilder.plugin.observation.opentelemetry.OpenTelemetryPlugin +import io.micrometer.observation.ObservationRegistry import org.slf4j.LoggerFactory import org.springframework.ai.chat.client.ChatClient import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor @@ -47,6 +49,8 @@ object AgentBootstrap { AgentToolFunctionCallbackContext( agentToolsProvider, ) + val observationRegistry = ObservationRegistry.create() + OpenTelemetryPlugin().install(agentConfig, observationRegistry) val llmConfigs = agentConfig.llm val chatModel = ServiceLoader.load(ChatModelProvider::class.java) .stream() @@ -60,8 +64,9 @@ object AgentBootstrap { .firstOrNull()?.also { logger.info("Loaded ChatModel $it") } ?: throw RuntimeException("No ChatModel found") - val chatClientBuilder = ChatClient.builder(chatModel) - .defaultAdvisors(advisors) + val chatClientBuilder = + ChatClient.builder(chatModel, observationRegistry, null) + .defaultAdvisors(advisors) val plannerConfigs = agentConfig.planner val planner = ServiceLoader.load(PlannerProvider::class.java) .stream() @@ -82,9 +87,10 @@ object AgentBootstrap { metadata?.description, metadata?.usageInstruction, agentToolsProvider, + UUID.randomUUID().toString(), + observationRegistry, ) KtorLauncher.launch(chatAgent) -// JdkHttpSyncLauncher().launch(chatAgent, agentToolsProvider) } private fun profileAdvisor(agentConfig: AgentConfig): Advisor? { diff --git a/bootstrap/src/main/resources/agent.yaml b/bootstrap/src/main/resources/agent.yaml index a3fe081..d18b9f0 100644 --- a/bootstrap/src/main/resources/agent.yaml +++ b/bootstrap/src/main/resources/agent.yaml @@ -1,5 +1,5 @@ metadata: - name: Test agent + name: TestAgent llm: openai: enabled: true @@ -11,6 +11,12 @@ memory: planner: reActJson: enabled: true +observation: + tracing: + exporter: + endpoint: "https://api.honeycomb.io/v1/traces" + headers: + "x-honeycomb-team": "{{env.HONEYCOMB_API_KEY}}" tools: - id: writeLocalFile config: diff --git a/cli/pom.xml b/cli/pom.xml index eb40384..2048dfa 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -28,7 +28,6 @@ com.github.jknack handlebars - 4.4.0 org.yaml diff --git a/core/pom.xml b/core/pom.xml index 3de287d..d4f7851 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -64,6 +64,10 @@ org.yaml snakeyaml + + com.github.jknack + handlebars + uk.org.webcompere diff --git a/core/src/main/kotlin/io/github/llmagentbuilder/core/AgentConfig.kt b/core/src/main/kotlin/io/github/llmagentbuilder/core/AgentConfig.kt index 5478c67..ebf9b51 100644 --- a/core/src/main/kotlin/io/github/llmagentbuilder/core/AgentConfig.kt +++ b/core/src/main/kotlin/io/github/llmagentbuilder/core/AgentConfig.kt @@ -1,5 +1,6 @@ package io.github.llmagentbuilder.core +import com.github.jknack.handlebars.Handlebars import org.yaml.snakeyaml.LoaderOptions import org.yaml.snakeyaml.Yaml import org.yaml.snakeyaml.constructor.Constructor @@ -36,6 +37,21 @@ class AgentMetadata { var usageInstruction: String? = null } +class TracingExporterConfig { + lateinit var endpoint: String + var headers: Map? = null +} + +class TracingConfig { + var enabled: Boolean? = false + var exporter: TracingExporterConfig? = null +} + +class ObservationConfig { + var enabled: Boolean? = false + var tracing: TracingConfig? = null +} + class AgentConfig { var metadata: AgentMetadata? = null var profile: ProfileConfig? = null @@ -43,6 +59,7 @@ class AgentConfig { var llm: Map? = null var planner: Map? = null var tools: List? = null + var observation: ObservationConfig? = null } object AgentConfigLoader { @@ -62,4 +79,16 @@ object AgentConfigLoader { ) ).load(reader) } +} + +object EvaluationHelper { + private val handlebars = Handlebars() + + fun evaluate(input: String): String { + return handlebars.compileInline(input).apply( + mapOf( + "env" to System.getenv(), + ) + ) + } } \ No newline at end of file diff --git a/core/src/main/kotlin/io/github/llmagentbuilder/core/ObservationPlugin.kt b/core/src/main/kotlin/io/github/llmagentbuilder/core/ObservationPlugin.kt new file mode 100644 index 0000000..5da560b --- /dev/null +++ b/core/src/main/kotlin/io/github/llmagentbuilder/core/ObservationPlugin.kt @@ -0,0 +1,10 @@ +package io.github.llmagentbuilder.core + +import io.micrometer.observation.ObservationRegistry + +interface ObservationPlugin { + fun install( + agentConfig: AgentConfig, + observationRegistry: ObservationRegistry + ) +} \ No newline at end of file diff --git a/launchers/ktor/src/main/kotlin/io/github/llmagentbuilder/launcher/ktor/server/apis/AgentApi.kt b/launchers/ktor/src/main/kotlin/io/github/llmagentbuilder/launcher/ktor/server/apis/AgentApi.kt index 42db23d..fd2314a 100644 --- a/launchers/ktor/src/main/kotlin/io/github/llmagentbuilder/launcher/ktor/server/apis/AgentApi.kt +++ b/launchers/ktor/src/main/kotlin/io/github/llmagentbuilder/launcher/ktor/server/apis/AgentApi.kt @@ -1,14 +1,3 @@ -/** - * Agent Protocol - * Specification of the API protocol for communication with an agent. - * - * The version of the OpenAPI document: v1 - * - * - * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). - * https://openapi-generator.tech - * Do not edit the class manually. - */ package io.github.llmagentbuilder.launcher.ktor.server.apis import io.github.llmagentbuilder.core.ChatAgent @@ -28,6 +17,11 @@ import kotlin.collections.set fun Route.AgentApi(chatAgent: ChatAgent) { val tasks = ConcurrentHashMap() + post("/chat") { + val request = call.receive(ChatAgentRequest::class) + call.respond(chatAgent.call(request)) + } + post("/ap/v1/agent/tasks") { val taskId = UUID.randomUUID().toString() val request = call.receive(TaskRequestBody::class) diff --git a/observation/open-telemetry/pom.xml b/observation/open-telemetry/pom.xml new file mode 100644 index 0000000..d611b3f --- /dev/null +++ b/observation/open-telemetry/pom.xml @@ -0,0 +1,47 @@ + + + 4.0.0 + + io.github.llmagentbuilder + observation + 0.2.1 + + + open-telemetry + + + 21 + 21 + UTF-8 + 1.42.1 + + + + + io.github.llmagentbuilder + core + ${project.version} + + + io.opentelemetry + opentelemetry-sdk + ${opentelemetry.version} + + + io.opentelemetry + opentelemetry-exporter-otlp + ${opentelemetry.version} + + + io.micrometer + micrometer-registry-otlp + + + io.micrometer + micrometer-tracing-bridge-otel + + + + \ No newline at end of file diff --git a/observation/open-telemetry/src/main/kotlin/io/github/llmagentbuilder/plugin/observation/opentelemetry/OpenTelemetryPlugin.kt b/observation/open-telemetry/src/main/kotlin/io/github/llmagentbuilder/plugin/observation/opentelemetry/OpenTelemetryPlugin.kt new file mode 100644 index 0000000..09a16c5 --- /dev/null +++ b/observation/open-telemetry/src/main/kotlin/io/github/llmagentbuilder/plugin/observation/opentelemetry/OpenTelemetryPlugin.kt @@ -0,0 +1,60 @@ +package io.github.llmagentbuilder.plugin.observation.opentelemetry + +import io.github.llmagentbuilder.core.AgentConfig +import io.github.llmagentbuilder.core.EvaluationHelper +import io.github.llmagentbuilder.core.ObservationPlugin +import io.micrometer.observation.ObservationRegistry +import io.micrometer.tracing.handler.DefaultTracingObservationHandler +import io.micrometer.tracing.otel.bridge.EventListener +import io.micrometer.tracing.otel.bridge.OtelCurrentTraceContext +import io.micrometer.tracing.otel.bridge.OtelTracer +import io.micrometer.tracing.otel.bridge.OtelTracer.EventPublisher +import io.micrometer.tracing.otel.bridge.Slf4JEventListener +import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter +import io.opentelemetry.sdk.resources.Resource +import io.opentelemetry.sdk.trace.SdkTracerProvider +import io.opentelemetry.sdk.trace.export.BatchSpanProcessor + +class OpenTelemetryPlugin : ObservationPlugin { + override fun install( + agentConfig: AgentConfig, + observationRegistry: ObservationRegistry + ) { + val builder = Resource.builder() + builder.put("service.name", agentConfig.metadata?.name) + val exporterBuilder = OtlpHttpSpanExporter.builder() + agentConfig.observation?.tracing?.exporter?.let { + exporterBuilder.setEndpoint(it.endpoint) + it.headers?.entries?.forEach { (k, v) -> + exporterBuilder.addHeader(k, EvaluationHelper.evaluate(v)) + } + } + val resource = builder.build() + val tracer = SdkTracerProvider.builder() + .setResource(resource) + .addSpanProcessor( + BatchSpanProcessor.builder( + exporterBuilder.build() + ).build() + ) + .build() + .get("llm-agent") + val context = OtelCurrentTraceContext() + val otelTracer = OtelTracer( + tracer, context, OTelEventPublisher( + listOf( + Slf4JEventListener() + ) + ) + ) + val handler = DefaultTracingObservationHandler(otelTracer) + observationRegistry.observationConfig().observationHandler(handler) + } + + private class OTelEventPublisher(private val listeners: List) : + EventPublisher { + override fun publishEvent(event: Any?) { + listeners.forEach { it.onEvent(event) } + } + } +} \ No newline at end of file diff --git a/observation/pom.xml b/observation/pom.xml new file mode 100644 index 0000000..a0c019f --- /dev/null +++ b/observation/pom.xml @@ -0,0 +1,25 @@ + + + 4.0.0 + + io.github.llmagentbuilder + llm-agent-builder + 0.2.1 + + + observation + pom + LLM Observation + + open-telemetry + + + + 21 + 21 + UTF-8 + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index 06c22b5..4fd7aca 100644 --- a/pom.xml +++ b/pom.xml @@ -17,6 +17,7 @@ bootstrap llm cli + observation LLM Agent Builder @@ -57,6 +58,7 @@ 2.16.1 1.12.4 1.2.4 + 4.4.0 @@ -290,6 +292,11 @@ pom import + + com.github.jknack + handlebars + ${handlebars.version} +