Skip to content

[Java] @CopilotTool ergonomics: Enforce non-blank @Param description at compile time #1836

Description

@edburns

Overview

Enforce a non-empty @Param.value() (description) at compile time in the CopilotToolProcessor annotation processor. Any @CopilotTool method that declares a parameter annotated with @Param and leaves value() blank must produce a build error, not a silent runtime degradation.

Parent Epic: #1682
Parent Follow-up: #1809

Motivation

The @Param.value() field carries the parameter description that is sent to the LLM as part of the tool schema. Without a meaningful description, the LLM cannot reliably select and invoke the right tool or supply the right argument.

The lambda-based ToolDefinition.from*(...) API (issue #1810) enforces non-blank descriptions at construction time via Param<T>. The annotation-based API must achieve the same guarantee, but at compile time via the existing JSR 269 annotation processor.

Currently, CopilotToolProcessor validates:

  • @Param(required=true) + non-empty defaultValue → compile error
  • @Param(defaultValue=...) + primitive without required=false → compile error

But it does not validate:

  • @Param(value = "") → no error today, silently sends empty description to the LLM

Deliverables

Files to modify

  1. java/src/main/java/com/github/copilot/tool/CopilotToolProcessor.java
  2. java/src/test/java/com/github/copilot/rpc/ToolDefinitionFromObjectTest.java or a dedicated processor test
  3. java/README.md if the annotation usage examples do not already show non-empty descriptions

Implementation specification

1. Compile-time validation in CopilotToolProcessor

In the parameter validation loop (the existing block that iterates over method.getParameters()), add a check after retrieving paramAnnotation:

if (paramAnnotation != null && paramAnnotation.value().isBlank()) {
    processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
            "@Param value (description) must not be blank — "
            + "descriptions are required so the LLM can correctly invoke this tool",
            param);
}

The check must apply to:

  • every method parameter annotated with @Param
  • both instance and static @CopilotTool methods
  • inherited methods discovered through the class hierarchy

2. Exception: record-component parameters

When a @CopilotTool method takes a single record as its parameter, the @Param annotation on that wrapper parameter is not currently used for per-field descriptions (record components carry their own @Param annotations). The existing processor already emits an error if name/value/required are set on a single-record wrapper parameter. The new blank-value check must not apply to the wrapper parameter itself, only to scalar/flat parameters.

Specifically: the blank-description check fires only when the parameter is not the single-record wrapper shortcut.

3. Error message quality

The error message must:

  • identify the parameter by name
  • identify the enclosing method and class
  • explain why a description is required

Example:

error: @Param on parameter 'query' in 'MyTools.searchItems' has a blank value (description).
Descriptions are required so the LLM can correctly select and invoke the tool.

4. @Param without any annotation

Parameters without @Param are currently accepted (they get no description in the schema). This issue does not require those to be flagged; it only enforces non-blank on parameters that do carry @Param.

Gating tests and criteria

All of the following must pass:

Compile-time error tests (annotation processor tests)

  1. Blank value: A method parameter annotated @Param(value = "") produces a compile error.
  2. Whitespace-only value: @Param(value = " ") also produces a compile error.
  3. Valid value: @Param(value = "Search query") compiles without error.
  4. No @Param at all: A parameter without @Param compiles without error (not in scope of this issue).
  5. Record wrapper exemption: @Param-annotated single-record wrapper parameter is exempt from the blank-value check.

No-regression tests

  1. All existing ToolDefinitionFromObjectTest tests continue to pass.
  2. mvn clean verify passes with no regressions.

Design decisions

  • Compile-time only. No runtime enforcement is added; the annotation processor is the right enforcement point.
  • value() only. This issue does not impose a description requirement on unannotated parameters.
  • Aligned with lambda API. The lambda API enforces blank descriptions at construction time; this makes the annotation API consistent.
  • Parity with @CopilotTool description. The tool-level description is already implicitly required (an empty tool description is poor practice); this extends the same expectation to parameter descriptions.

Example — before and after

Before (currently compiles silently):

@CopilotTool("Search for items")
public String searchItems(@Param("") String query) {
    return "results for " + query;
}

After (produces compile error):

error: @Param on parameter 'query' in 'MyTools.searchItems' has a blank value (description).
Descriptions are required so the LLM can correctly select and invoke the tool.

Correct usage:

@CopilotTool("Search for items")
public String searchItems(@Param("Search keyword") String query) {
    return "results for " + query;
}

Branch and PR conventions

  • Branch: create from main on upstream
  • PR target: main
  • Run cd java && mvn verify before merging
  • Run cd java && mvn spotless:apply before commit
  • Follow existing Javadoc conventions for any public API/documentation updates

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementjavaPull requests that update java code

    Type

    No fields configured for Task.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions