You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在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]))))

问题现象

  1. 执行(->> Foo .getClass .getDeclaredMethods pprint)无方法输出
  2. 目标Java接口示例:
interface Foo {
  @UserMessage("The {{role}} says {{sound}}.")
  String foo(@V("role") String role, @V("sound") String sound);
}
  1. 宏展开结果看似正确,但Langchain4j提示找不到@V参数注解
  2. 用javap -v -p查看接口class文件,类型正确但无注解内容

问题根源

  1. definterface的元数据处理规则:Clojure的definterface对方法和参数的Java注解元数据有严格格式要求,普通with-meta添加的注解不会被正确编译到生成的Java接口中。
  2. 参数注解格式错误:参数级Java注解需要使用^@前缀标记,而非普通的^,否则Clojure不会将其识别为Java参数注解。
  3. 方法注解数组参数的解析问题: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]))))

关键修改说明

  1. 参数注解格式:使用^@V标记参数级Java注解,确保Clojure将其编译为Java接口的参数注解。
  2. 方法注解数组构造:将(into-array java.lang.String (list prompt))改为(into-array String [~prompt]),简化数组构造的同时确保宏展开后代码正确。
  3. 接口名与方法名统一:将接口名改为首字母大写(符合Java接口命名规范),方法名使用传入的name的小写形式,保持命名一致性。
  4. 修正正则表达式:移除了原代码中多余的右括号,确保变量提取正常工作。

测试验证

  1. 在REPL中加载宏后,调用(defprompt foo "You are a {{role}} and need to solve this: {{problem-statement}}.")
  2. 检查方法注解:(->> Foo .getDeclaredMethods first .getAnnotations pprint)
  3. 检查参数注解:(->> Foo .getDeclaredMethods first .getParameterAnnotations pprint)
  4. 验证Langchain4j兼容性:使用(AiServices/create Foo model)创建服务并测试调用

内容的提问来源于stack exchange,提问作者MonkeyWithDarts

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 11:10:02