Skip to content

Commit

Permalink
configure agent tools
Browse files Browse the repository at this point in the history
  • Loading branch information
alexcheng1982 committed May 7, 2024
1 parent 3da71f0 commit efd90e8
Show file tree
Hide file tree
Showing 19 changed files with 149 additions and 61 deletions.
4 changes: 4 additions & 0 deletions core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-kotlin</artifactId>
</dependency>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-observation</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ data class AgentExecutor(
result.add(performAgentAction(nameToToolMap, it))
}
} catch (e: OutputParserException) {
logger.error("Output parsing error for input {}", inputs, e)
logger.error("Output parsing error for input: {}", inputs, e)
val text = e.llmOutput()
var observation = parsingErrorHandler?.apply(e) ?: e.observation()
val output = AgentAction("_Exception", observation, text)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ object JsonParser {
try {
return parseJson(parseJsonMarkdown(json))
} catch (e: Exception) {
logger.warn("Failed to parse json {}", json)
logger.warn("Failed to parse json: {}", json)
return null
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,39 @@
package io.github.llmagentbuilder.core.tool

import java.lang.reflect.Method
import org.apache.commons.beanutils.BeanUtils
import java.util.function.Supplier

/**
* Factory to create agent tools
*
* Agent tool factories are loaded using [java.util.ServiceLoader].
*/
interface AgentToolFactory<out T : AgentTool<*, *>> {
/**
* @param T Agent tool type
* @return Agent tool
*/
fun create(): T
}

/**
* Factory to create agent tools with configuration objects
*/
interface ConfigurableAgentToolFactory<CONFIG, out T : ConfigurableAgentTool<*, *, CONFIG>> :
AgentToolFactory<T> {
/**
* @param T Agent tool type
* @param config Tool configuration object
* @return Agent tool
*/
fun create(config: CONFIG): T

/**
* Name of configuration key
*
* @return Config name
*/
fun configName(): String
}

abstract class BaseConfigurableAgentToolFactory<out T : ConfigurableAgentTool<*, *, CONFIG>, CONFIG>(
Expand Down Expand Up @@ -41,46 +62,17 @@ open class EnvironmentVariableConfigProvider<C>(
Supplier<C> {
override fun get(): C {
val instance = configClass.getDeclaredConstructor().newInstance()
val setters = getPropertySetters()
getEnvironmentVariables().forEach { (key, value) ->
setters[key]?.run {
invokeMethod(instance, this, value)
}
}
BeanUtils.populate(instance, getEnvironmentVariables())
return instance
}

private fun invokeMethod(instance: C, method: Method, value: String) {
if (method.parameterCount != 1) {
return
}
val parameter = method.parameters[0]
val parameterValue = when (parameter.type) {
Long::class.java -> value.toLongOrNull()
Int::class.java -> value.toIntOrNull()
Double::class.java -> value.toDoubleOrNull()
Float::class.java -> value.toFloatOrNull()
else -> value
}
method.invoke(instance, parameterValue)
}

private fun getPropertySetters(): Map<String, Method> {
return configClass.methods.filter {
it.name.startsWith("set")
}.associateBy {
it.name.removePrefix("set").lowercase()
}
}

private fun getEnvironmentVariables(): Map<String, String> {
val prefix = environmentVariablePrefix.lowercase()
val prefix = environmentVariablePrefix
return System.getenv()
.filterKeys {
it.lowercase().startsWith(prefix)
it.startsWith(prefix)
}.mapKeys { entry ->
entry.key.lowercase()
.removePrefix(prefix)
entry.key.removePrefix(prefix)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import io.github.llmagentbuilder.core.observation.AgentToolExecutionObservationC
import io.github.llmagentbuilder.core.observation.AgentToolExecutionObservationDocumentation
import io.github.llmagentbuilder.core.observation.DefaultAgentToolExecutionObservationConvention
import io.micrometer.observation.ObservationRegistry
import org.apache.commons.beanutils.BeanUtils
import org.slf4j.LoggerFactory
import org.springframework.ai.model.function.FunctionCallback
import org.springframework.ai.model.function.FunctionCallbackWrapper
Expand Down Expand Up @@ -109,6 +110,43 @@ class CompositeAgentToolsProvider(private val providers: List<AgentToolsProvider
}
}

object AgentToolsProviderFactory {
private val logger = LoggerFactory.getLogger(javaClass)

fun create(config: Map<String, Map<String, Any>>?): AgentToolsProvider {
val (toConfig, noConfig) = ServiceLoader.load(AgentToolFactory::class.java)
.stream()
.map { it.get() }
.asSequence()
.partition { it is ConfigurableAgentToolFactory<*, *> }
val noConfigTools = noConfig.map { it.create() }
val toConfigTools = toConfig.map {
val configName =
(it as ConfigurableAgentToolFactory<*, *>).configName()
val types =
GenericTypeResolver.resolveTypeArguments(
it.javaClass,
ConfigurableAgentToolFactory::class.java
)
val configType =
types?.get(0) ?: throw IllegalArgumentException("Invalid type")
val instance = configType.getDeclaredConstructor().newInstance()
BeanUtils.populate(instance, config?.get(configName) ?: mapOf())
val method = it.javaClass.getDeclaredMethod("create", configType)
method.invoke(it, instance) as AgentTool<*, *>
}
val agentTools = (noConfigTools + toConfigTools)
.associateBy { it.name() }.also {
logger.info("Found agent tools {}", it.keys)
};
return object : AgentToolsProvider {
override fun get(): Map<String, AgentTool<*, *>> {
return agentTools
}
}
}
}

object AutoDiscoveredAgentToolsProvider : AgentToolsProvider {
private val logger = LoggerFactory.getLogger(javaClass)
private val agentTools: Map<String, AgentTool<*, *>> by lazy {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package io.github.alexcheng1982.llmagentbuilder.core.chatmemory
package io.github.llmagentbuilder.core.chatmemory

import io.github.llmagentbuilder.core.chatmemory.InMemoryChatMemoryStore
import io.github.llmagentbuilder.core.chatmemory.MessageWindowChatMemory
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package io.github.alexcheng1982.llmagentbuilder.core.planner
package io.github.llmagentbuilder.core.planner

import io.github.llmagentbuilder.core.planner.JsonParser
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.Test
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package io.github.alexcheng1982.llmagentbuilder.core.planner.react
package io.github.llmagentbuilder.core.planner.react

import io.github.llmagentbuilder.core.AgentAction
import io.github.llmagentbuilder.core.AgentFinish
import io.github.llmagentbuilder.core.planner.react.ReActOutputParser
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package io.github.alexcheng1982.llmagentbuilder.core.planner.reactjson
package io.github.llmagentbuilder.core.planner.reactjson

import io.github.llmagentbuilder.core.planner.reactjson.ReActJsonOutputParser
import org.junit.jupiter.api.Test
import kotlin.test.assertNotNull

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package io.github.alexcheng1982.llmagentbuilder.core.tool
package io.github.llmagentbuilder.core.tool

import io.github.llmagentbuilder.core.tool.EnvironmentVariableConfigProvider
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import uk.org.webcompere.systemstubs.SystemStubs.withEnvironmentVariable
Expand Down
2 changes: 1 addition & 1 deletion llm/dashscope/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
<dependency>
<groupId>io.github.alexcheng1982</groupId>
<artifactId>spring-ai-dashscope-client</artifactId>
<version>0.8.0</version>
<version>1.1.1</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
Expand Down
5 changes: 5 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,11 @@
<artifactId>jsoup</artifactId>
<version>1.17.2</version>
</dependency>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.4</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
Expand Down
5 changes: 5 additions & 0 deletions spring/spring-boot-autoconfigure/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>io.github.alexcheng1982</groupId>
<artifactId>spring-ai-dashscope-spring-boot-autoconfigure</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-actuator-autoconfigure</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.github.llmagentbuilder.spring.autoconfigure.chatagent.chatagent;
package io.github.llmagentbuilder.spring.autoconfigure.chatagent;

import io.github.alexcheng1982.springai.dashscope.autoconfigure.DashscopeAutoConfiguration;
import io.github.llmagentbuilder.core.Agent;
import io.github.llmagentbuilder.core.AgentFactory;
import io.github.llmagentbuilder.core.ChatAgent;
Expand All @@ -9,7 +10,7 @@
import io.github.llmagentbuilder.core.planner.reactjson.ReActJsonPlannerFactory;
import io.github.llmagentbuilder.core.tool.AgentToolFunctionCallbackContext;
import io.github.llmagentbuilder.core.tool.AgentToolsProvider;
import io.github.llmagentbuilder.core.tool.AutoDiscoveredAgentToolsProvider;
import io.github.llmagentbuilder.core.tool.AgentToolsProviderFactory;
import io.github.llmagentbuilder.core.tool.CompositeAgentToolsProvider;
import io.github.llmagentbuilder.spring.spring.SpringAgentToolsProvider;
import io.github.llmagentbuilder.spring.spring.chatagent.ChatAgentService;
Expand All @@ -34,11 +35,13 @@
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

@AutoConfiguration(before = WebMvcAutoConfiguration.class, after = {
OllamaAutoConfiguration.class, OpenAiAutoConfiguration.class,
DashscopeAutoConfiguration.class,
ObservationAutoConfiguration.class})
@ConditionalOnProperty(prefix = "io.github.llmagentbuilder.chatagent", name = "enabled", matchIfMissing = true)
@ConditionalOnProperty(prefix = ChatAgentProperties.CONFIG_PREFIX, name = "enabled", matchIfMissing = true)
public class ChatAgentAutoConfiguration {

@Configuration(proxyBeanMethods = false)
Expand All @@ -55,7 +58,8 @@ public static class ChatAgentConfiguration {
}

@Bean
@ConditionalOnProperty(prefix = "io.github.llmagentbuilder.chatagent.memory", name = "enabled", matchIfMissing = true)
@ConditionalOnProperty(prefix = ChatAgentProperties.CONFIG_PREFIX
+ ".memory", name = "enabled", matchIfMissing = true)
@ConditionalOnMissingBean
public ChatMemoryStore chatMemoryStore() {
return new InMemoryChatMemoryStore();
Expand Down Expand Up @@ -83,15 +87,17 @@ public Planner planner(ChatClient chatClient,
}

@Bean
@ConditionalOnProperty(prefix = "io.github.llmagentbuilder.chatagent.tracing", name = "enabled", matchIfMissing = true)
@ConditionalOnProperty(prefix = ChatAgentProperties.CONFIG_PREFIX
+ ".tracing", name = "enabled", matchIfMissing = true)
@ConditionalOnBean(Planner.class)
@ConditionalOnMissingBean
public ObservationRegistry observationRegistry() {
return ObservationRegistry.create();
}

@Bean
@ConditionalOnProperty(prefix = "io.github.llmagentbuilder.chatagent.metrics", name = "enabled", matchIfMissing = true)
@ConditionalOnProperty(prefix = ChatAgentProperties.CONFIG_PREFIX
+ ".metrics", name = "enabled", matchIfMissing = true)
@ConditionalOnBean(Planner.class)
@ConditionalOnMissingBean
public MeterRegistry meterRegistry() {
Expand Down Expand Up @@ -121,8 +127,8 @@ public ChatAgentService chatAgentService(ChatAgent chatAgent) {
}

@Bean
@ConditionalOnMissingBean
public FunctionCallbackContext springAiFunctionManager(
@Primary
public FunctionCallbackContext agentToolFunctionCallbackContext(
AgentToolsProvider agentToolsProvider,
Optional<ObservationRegistry> observationRegistry,
ApplicationContext context) {
Expand All @@ -139,7 +145,8 @@ public AgentToolsProvider agentToolsProvider(ApplicationContext context) {
var springAgentToolsProvider = new SpringAgentToolsProvider();
springAgentToolsProvider.setApplicationContext(context);
return new CompositeAgentToolsProvider(List.of(
AutoDiscoveredAgentToolsProvider.INSTANCE,
AgentToolsProviderFactory.INSTANCE.create(
properties.getTools().getConfig()),
springAgentToolsProvider
));
}
Expand Down
Loading

0 comments on commit efd90e8

Please sign in to comment.