Skip to content

Commit 8eab38c

Browse files
Apply PR feedback
1 parent 4e5772e commit 8eab38c

File tree

3 files changed

+102
-18
lines changed

3 files changed

+102
-18
lines changed

dd-java-agent/agent-aiguard/build.gradle

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,6 @@ jar {
3131
archiveClassifier = 'unbundled'
3232
}
3333

34-
ext {
35-
minimumBranchCoverage = 0.6
36-
minimumInstructionCoverage = 0.8
37-
excludedClassesCoverage = []
38-
excludedClassesBranchCoverage = []
39-
excludedClassesInstructionCoverage = []
40-
}
41-
4234
spotless {
4335
java {
4436
target 'src/**/*.java'

dd-java-agent/agent-aiguard/src/main/java/com/datadog/aiguard/AIGuardInternal.java

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import datadog.trace.bootstrap.instrumentation.api.AgentTracer;
2222
import java.io.IOException;
2323
import java.util.Collection;
24+
import java.util.Collections;
2425
import java.util.HashMap;
2526
import java.util.List;
2627
import java.util.Map;
@@ -37,6 +38,12 @@
3738

3839
public class AIGuardInternal implements Evaluator {
3940

41+
public static class BadConfigurationException extends RuntimeException {
42+
public BadConfigurationException(final String message) {
43+
super(message);
44+
}
45+
}
46+
4047
static final String SPAN_NAME = "ai_guard";
4148
static final String TARGET_TAG = "ai_guard.target";
4249
static final String TOOL_TAG = "ai_guard.tool";
@@ -51,7 +58,7 @@ public static void install() {
5158
final String apiKey = config.getApiKey();
5259
final String appKey = config.getApplicationKey();
5360
if (isEmpty(apiKey) || isEmpty(appKey)) {
54-
throw new RuntimeException(
61+
throw new BadConfigurationException(
5562
"AI Guard: Missing api and/or application key, use DD_API_KEY and DD_APP_KEY");
5663
}
5764
String endpoint = config.getAiGuardEndpoint();
@@ -98,11 +105,12 @@ private static List<Message> truncate(List<Message> messages) {
98105
final int maxContent = config.getAiGuardMaxContentSize();
99106
for (int i = 0; i < messages.size(); i++) {
100107
Message source = messages.get(i);
101-
if (source.getContent() != null && source.getContent().length() > maxContent) {
108+
final String content = source.getContent();
109+
if (content != null && content.length() > maxContent) {
102110
source =
103111
new Message(
104112
source.getRole(),
105-
source.getContent().substring(0, maxContent),
113+
content.substring(0, maxContent),
106114
source.getToolCalls(),
107115
source.getToolCallId());
108116
messages.set(i, source);
@@ -146,7 +154,7 @@ private boolean isBlockingEnabled(final Object isBlockingEnabled) {
146154
@Override
147155
public Evaluation evaluate(final List<Message> messages, final Options options) {
148156
if (messages == null || messages.isEmpty()) {
149-
throw new IllegalArgumentException("messages must not be empty");
157+
throw new IllegalArgumentException("Messages must not be empty");
150158
}
151159
final AgentTracer.TracerAPI tracer = AgentTracer.get();
152160
final AgentSpan span = tracer.buildSpan(SPAN_NAME, SPAN_NAME).start();
@@ -161,8 +169,8 @@ public Evaluation evaluate(final List<Message> messages, final Options options)
161169
} else {
162170
span.setTag(TARGET_TAG, "prompt");
163171
}
164-
final Map<String, Object> metaStruct = new HashMap<>(1);
165-
metaStruct.put(META_STRUCT_KEY, truncate(messages));
172+
final Map<String, Object> metaStruct =
173+
Collections.singletonMap(META_STRUCT_KEY, truncate(messages));
166174
span.setMetaStruct(META_STRUCT_TAG, metaStruct);
167175
final Request.Builder request =
168176
new Request.Builder()
@@ -173,7 +181,7 @@ public Evaluation evaluate(final List<Message> messages, final Options options)
173181
final Map<String, Object> result = parseResponseBody(response);
174182
final String actionStr = (String) result.get("action");
175183
if (actionStr == null) {
176-
throw new IllegalArgumentException("action field is missing in the response");
184+
throw new IllegalArgumentException("Action field is missing in the response");
177185
}
178186
final Action action = Action.valueOf(actionStr);
179187
final String reason = (String) result.get("reason");
@@ -214,7 +222,7 @@ private Map<String, Object> parseResponseBody(final Response response) throws IO
214222
}
215223

216224
private AIGuardClientError fail(final int statusCode, final Object errors) {
217-
return new AIGuardClientError("AI Guard service call failed, status" + statusCode, errors);
225+
return new AIGuardClientError("AI Guard service call failed, status: " + statusCode, errors);
218226
}
219227

220228
private static OkHttpClient buildClient(final HttpUrl url, final long timeout) {

dd-java-agent/agent-aiguard/src/test/groovy/com/datadog/aiguard/AIGuardInternalTests.groovy

Lines changed: 86 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,28 @@ class AIGuardInternalTests extends DDSpecification {
7777
}
7878

7979
void 'test missing api/app keys'() {
80+
given:
81+
if (apiKey) {
82+
injectEnvConfig('API_KEY', apiKey)
83+
}
84+
if (appKey) {
85+
injectEnvConfig('APP_KEY', appKey)
86+
}
87+
8088
when:
8189
AIGuardInternal.install()
8290

8391
then:
84-
thrown(RuntimeException)
92+
thrown(AIGuardInternal.BadConfigurationException)
93+
94+
where:
95+
apiKey | appKey
96+
'apiKey' | null
97+
'apiKey' | ''
98+
null | 'appKey'
99+
'' | 'appKey'
100+
null | null
101+
'' | ''
85102
}
86103

87104
void 'test endpoint discovery'() {
@@ -269,6 +286,30 @@ class AIGuardInternalTests extends DDSpecification {
269286
1 * span.addThrowable(_ as AIGuard.AIGuardClientError)
270287
}
271288

289+
void 'test evaluate with empty response'() {
290+
given:
291+
Request request = null
292+
final call = Mock(Call) {
293+
execute() >> {
294+
return mockResponse(request, 200, null)
295+
}
296+
}
297+
final client = Mock(OkHttpClient) {
298+
newCall(_ as Request) >> {
299+
request = (Request) it[0]
300+
return call
301+
}
302+
}
303+
final aiguard = new AIGuardInternal(URL, HEADERS, client)
304+
305+
when:
306+
aiguard.evaluate(TOOL_CALL, AIGuard.Options.DEFAULT)
307+
308+
then:
309+
thrown(AIGuard.AIGuardClientError)
310+
1 * span.addThrowable(_ as AIGuard.AIGuardClientError)
311+
}
312+
272313
void 'test message length truncation'() {
273314
given:
274315
final maxMessages = Config.get().getAiGuardMaxMessagesLength()
@@ -331,6 +372,49 @@ class AIGuardInternalTests extends DDSpecification {
331372
}
332373
}
333374

375+
void 'test no messages'() {
376+
given:
377+
final aiguard = new AIGuardInternal(URL, HEADERS, Stub(OkHttpClient))
378+
379+
when:
380+
aiguard.evaluate(messages, AIGuard.Options.DEFAULT)
381+
382+
then:
383+
thrown(IllegalArgumentException)
384+
385+
386+
where:
387+
messages << [[], null]
388+
}
389+
390+
void 'test missing tool name'() {
391+
given:
392+
def request
393+
final call = Mock(Call) {
394+
execute() >> {
395+
return mockResponse(
396+
request,
397+
200,
398+
[data: [attributes: [action: 'ALLOW', reason: 'Just do it']]]
399+
)
400+
}
401+
}
402+
final client = Mock(OkHttpClient) {
403+
newCall(_ as Request) >> {
404+
request = (Request) it[0]
405+
return call
406+
}
407+
}
408+
final aiguard = new AIGuardInternal(URL, HEADERS, client)
409+
410+
when:
411+
aiguard.evaluate([AIGuard.Message.tool('call_1', 'Content')], AIGuard.Options.DEFAULT)
412+
413+
then:
414+
1 * span.setTag(AIGuardInternal.TARGET_TAG, 'tool')
415+
0 * span.setTag(AIGuardInternal.TOOL_TAG, _)
416+
}
417+
334418
private static assertRequest(final Request request, final List<AIGuard.Message> messages) {
335419
assert request.url() == URL
336420
assert request.method() == 'POST'
@@ -358,7 +442,7 @@ class AIGuardInternalTests extends DDSpecification {
358442
.message('ok')
359443
.request(request)
360444
.code(status)
361-
.body(ResponseBody.create(MediaType.parse('application/json'), MOSHI.adapter(Object).toJson(body)))
445+
.body(body == null ? null : ResponseBody.create(MediaType.parse('application/json'), MOSHI.adapter(Object).toJson(body)))
362446
.build()
363447
}
364448

0 commit comments

Comments
 (0)