在Clojure REPL中创建带注解的Java接口,无需AOT编译
Clojure宏实现Langchain4j提示接口的问题与修复
需求
- 在Clojure中创建Java接口
- 接口可直接在REPL中使用(无需AOT/编译步骤,完全不用Java编写)
- 接口包含方法级和参数级Java注解
- 方法级注解需正确传入字符串数组而非单个字符串
背景
使用Langchain4j时,希望在REPL(CIDER)中内联定义LLM提示词,避免将提示词存储在资源文件或Java文件中,减少上下文切换的认知负担。期望的宏调用方式如下:
(defprompt foo "You are a {{role}} and need to solve this: {{problem-statement}}.")
原有的非内联实现需要依赖资源文件,无法直接查看提示词:
(definterface Foo (^{UserMessage {:fromResource "foo-prompt.txt"}} ;; 无法直接查看提示词 ^String foo [^{V "role"} role ^{V "problem-statement"} problem-statement]))
现有尝试的宏代码(存在问题)
(defmacro defprompt "Define a Langchain4j prompt interface for AiServices. Arguments: name - the name for the interface (will be capitalized) prompt - a string prompt template with {{variable}} placeholders method-name - the name of the single method Example: (defprompt brainstorming \"ACT as a professional {{role}} and brainstorm on this problem statement: {{problemStatement}} Give different possible solutions that make sense.\") Returns the interface symbol that can be passed to AiServices/create." [name prompt] (let [interface-name (symbol (str name)) ;; Extract variables from the prompt pattern #"\{\{([^}]+)\}\}" ;; 修正了多余的右括号 param-names (->> (re-seq pattern prompt) (map second) (distinct)) ;; Create parameter declarations with annotations param-decls (mapv (fn [param] (let [param-sym (symbol param)] (with-meta param-sym {V {:value param} :tag String}))) param-names)] `(definterface ~interface-name (~(with-meta 'prompt {:tag String UserMessage {:value (into-array java.lang.String (list prompt))}}) [~@param-decls]))))
问题现象
- 执行
(->> Foo .getClass .getDeclaredMethods pprint)无方法输出 - 目标Java接口示例:
interface Foo { @UserMessage("The {{role}} says {{sound}}.") String foo(@V("role") String role, @V("sound") String sound); }
- 宏展开结果看似正确,但Langchain4j提示找不到
@V参数注解 - 用
javap -v -p查看接口class文件,类型正确但无注解内容
问题根源
definterface的元数据处理规则:Clojure的definterface对方法和参数的Java注解元数据有严格格式要求,普通with-meta添加的注解不会被正确编译到生成的Java接口中。- 参数注解格式错误:参数级Java注解需要使用
^@前缀标记,而非普通的^,否则Clojure不会将其识别为Java参数注解。 - 方法注解数组参数的解析问题:
UserMessage注解的value是字符串数组,宏中直接使用(into-array java.lang.String (list prompt))会导致宏展开后代码无法被Clojure正确解析为注解参数,需要调整为更直接的数组构造方式。
修复后的宏代码
(ns your.namespace.here (:import [dev.langchain4j.model.input.annotation UserMessage V])) (defmacro defprompt "Define a Langchain4j prompt interface for AiServices. Arguments: name - the name for the interface (will be capitalized) prompt - a string prompt template with {{variable}} placeholders Example: (defprompt brainstorming \"ACT as a professional {{role}} and brainstorm on this problem statement: {{problemStatement}} Give different possible solutions that make sense.\") Returns the interface symbol that can be passed to AiServices/create." [name prompt] (let [interface-name (symbol (str (clojure.string/capitalize (name name)))) method-name (symbol (name name)) ;; Extract variables from the prompt pattern #"\{\{([^}]+)\}\}" param-names (->> (re-seq pattern prompt) (map second) (distinct)) ;; Create parameter declarations with correct Java parameter annotations param-decls (mapv (fn [param] (let [param-sym (symbol param)] ;; Use ^@ for Java parameter annotations, ^ for type hints (with-meta param-sym {:tag String `^@V {:value param}}))) param-names)] `(definterface ~interface-name ;; Method-level annotation: use correct array syntax for UserMessage's value (~(with-meta method-name {:tag String `UserMessage {:value (into-array String [~prompt])}}) [~@param-decls]))))
关键修改说明
- 参数注解格式:使用
^@V标记参数级Java注解,确保Clojure将其编译为Java接口的参数注解。 - 方法注解数组构造:将
(into-array java.lang.String (list prompt))改为(into-array String [~prompt]),简化数组构造的同时确保宏展开后代码正确。 - 接口名与方法名统一:将接口名改为首字母大写(符合Java接口命名规范),方法名使用传入的
name的小写形式,保持命名一致性。 - 修正正则表达式:移除了原代码中多余的右括号,确保变量提取正常工作。
测试验证
- 在REPL中加载宏后,调用
(defprompt foo "You are a {{role}} and need to solve this: {{problem-statement}}.") - 检查方法注解:
(->> Foo .getDeclaredMethods first .getAnnotations pprint) - 检查参数注解:
(->> Foo .getDeclaredMethods first .getParameterAnnotations pprint) - 验证Langchain4j兼容性:使用
(AiServices/create Foo model)创建服务并测试调用
内容的提问来源于stack exchange,提问作者MonkeyWithDarts
相关产品推荐
相关产品推荐

