|
31 | 31 | package org.testar.action.priorization.llm; |
32 | 32 |
|
33 | 33 | import com.google.gson.Gson; |
34 | | -import com.google.gson.JsonParseException; |
35 | 34 | import org.apache.logging.log4j.Level; |
36 | 35 | import org.apache.logging.log4j.LogManager; |
37 | 36 | import org.apache.logging.log4j.Logger; |
|
46 | 45 | import org.testar.monkey.Main; |
47 | 46 | import org.testar.monkey.alayer.*; |
48 | 47 | import org.testar.monkey.alayer.actions.*; |
49 | | -import org.testar.monkey.alayer.webdriver.enums.WdTags; |
50 | 48 | import org.testar.settings.Settings; |
51 | 49 |
|
52 | 50 | import java.io.*; |
@@ -175,7 +173,8 @@ private Action selectActionWithLlm(State state, Set<Action> actions) { |
175 | 173 |
|
176 | 174 | String conversationJson = gson.toJson(conversation); |
177 | 175 | String llmResponse = getResponseFromLlm(conversationJson); |
178 | | - LlmParseActionResult llmParseResult = parseLlmResponse(actions, llmResponse); |
| 176 | + LlmParseActionResponse llmParseResponse = new LlmParseActionResponse(gson); |
| 177 | + LlmParseActionResult llmParseResult = llmParseResponse.parseLlmResponse(actions, llmResponse); |
179 | 178 |
|
180 | 179 | switch(llmParseResult.getParseResult()) { |
181 | 180 | case SUCCESS: { |
@@ -327,128 +326,6 @@ private String getResponseFromLlm(String requestBody) { |
327 | 326 | } |
328 | 327 | } |
329 | 328 |
|
330 | | - /** |
331 | | - * Parses the response sent by the LLM and selects an action if the response was valid. |
332 | | - * @param actions Set of actions in the current state. |
333 | | - * @param responseContent The response of the LLM in plaintext. |
334 | | - * @return LlmParseActionResult containing the result of the parse and the action to execute if parsing was successful. |
335 | | - */ |
336 | | - private LlmParseActionResult parseLlmResponse(Set<Action> actions, String responseContent) { |
337 | | - try { |
338 | | - LlmSelectedAction llmSelectedAction = gson.fromJson(sanitizeJsonResponse(responseContent), LlmSelectedAction.class); |
339 | | - |
340 | | - Action selectedAction = getActionByIdentifier(actions, llmSelectedAction.getActionId()); |
341 | | - |
342 | | - // If the selectedAction is a NOP action at this stage, parsing has likely failed. |
343 | | - // Observed to happen when the LLM selects an actionId that does not exist. |
344 | | - if(selectedAction instanceof NOP) { |
345 | | - logger.log(Level.ERROR, "Action AbstractID not found, parsing LLM response has likely failed!: " + responseContent); |
346 | | - return new LlmParseActionResult(null, LlmParseActionResult.ParseResult.INVALID_ACTION); |
347 | | - } |
348 | | - |
349 | | - String input = llmSelectedAction.getInput(); |
350 | | - Widget widget = selectedAction.get(Tags.OriginWidget); |
351 | | - |
352 | | - // For interacting with select combobox web widgets |
353 | | - // A WdSelectListAction is created to change the active value of the combobox |
354 | | - if(Objects.equals(widget.get(WdTags.WebTagName, ""), "select")) { |
355 | | - if(Objects.equals(input, "")) { |
356 | | - return new LlmParseActionResult(null, LlmParseActionResult.ParseResult.SL_MISSING_INPUT); |
357 | | - } |
358 | | - |
359 | | - String target = widget.get(WdTags.WebId, ""); |
360 | | - WdSelectListAction.JsTargetMethod method; |
361 | | - if (target.isEmpty()) { |
362 | | - logger.warn("elementId is empty for select widget! Using name target method."); |
363 | | - target = widget.get(WdTags.WebName, ""); |
364 | | - method = WdSelectListAction.JsTargetMethod.NAME; |
365 | | - } else { |
366 | | - method = WdSelectListAction.JsTargetMethod.ID; |
367 | | - } |
368 | | - |
369 | | - return new LlmParseActionResult(new WdSelectListAction(target, input, widget, method), |
370 | | - LlmParseActionResult.ParseResult.SUCCESS); |
371 | | - } |
372 | | - |
373 | | - setCompoundActionInputText(selectedAction, input); |
374 | | - return new LlmParseActionResult(selectedAction, LlmParseActionResult.ParseResult.SUCCESS); |
375 | | - |
376 | | - } catch(JsonParseException e) { |
377 | | - logger.log(Level.ERROR, "Unable to parse response from LLM to JSON: " + responseContent); |
378 | | - return new LlmParseActionResult(null, LlmParseActionResult.ParseResult.PARSE_FAILED); |
379 | | - } catch(NullPointerException e) { |
380 | | - logger.log(Level.ERROR, "Null response due to LLM parse response error"); |
381 | | - return new LlmParseActionResult(null, LlmParseActionResult.ParseResult.COMMUNICATION_FAILURE); |
382 | | - } catch(Exception e) { |
383 | | - logger.log(Level.ERROR, "Exception parsing LLM response"); |
384 | | - return new LlmParseActionResult(null, LlmParseActionResult.ParseResult.COMMUNICATION_FAILURE); |
385 | | - } |
386 | | - } |
387 | | - |
388 | | - /** |
389 | | - * Try to sanitize markdown-style code fences sometimes generated by LLMs |
390 | | - * @param responseContent |
391 | | - * @return The sanitized LLM response |
392 | | - */ |
393 | | - private String sanitizeJsonResponse(String responseContent) { |
394 | | - if (responseContent == null) return null; |
395 | | - |
396 | | - if (responseContent.startsWith("```")) { |
397 | | - logger.log(Level.INFO, String.format("Sanitizing Response: [%s]", responseContent)); |
398 | | - // Remove enclosing ```...``` or ```json...``` |
399 | | - responseContent = responseContent.replaceAll("(?s)^```(?:json)?\\s*", ""); |
400 | | - responseContent = responseContent.replaceAll("(?s)\\s*```$", ""); |
401 | | - logger.log(Level.INFO, String.format("Sanitized Response: [%s]", responseContent)); |
402 | | - } |
403 | | - |
404 | | - return responseContent; |
405 | | - } |
406 | | - |
407 | | - /** |
408 | | - * Retrieves an action with given actionId. |
409 | | - * @param actions Set of actions to search. |
410 | | - * @param actionId ActionId to search for. |
411 | | - * @return Requested action if found, NOP Action if not found. |
412 | | - */ |
413 | | - private Action getActionByIdentifier(Set<Action> actions, String actionId) { |
414 | | - for(Action action : actions) { |
415 | | - if(action.get(Tags.AbstractID, "").equalsIgnoreCase(actionId)) { |
416 | | - return action; |
417 | | - } |
418 | | - } |
419 | | - return new NOP(); |
420 | | - } |
421 | | - |
422 | | - /** |
423 | | - * Sets TESTAR input text of compound Type and PasteText actions. |
424 | | - * @param action CompoundAction to change. |
425 | | - * @param inputText The characters to enter into the input field. Can be left empty if not applicable. |
426 | | - * @return if input text changed. |
427 | | - */ |
428 | | - private boolean setCompoundActionInputText(Action action, String inputText) { |
429 | | - //TODO: Create single actions in protocol so this is not necessary? |
430 | | - if(action instanceof CompoundAction) { |
431 | | - for(Action innerAction : ((CompoundAction)action).getActions()) { |
432 | | - |
433 | | - if(innerAction instanceof Type) { |
434 | | - ((Type)innerAction).set(Tags.InputText, inputText); |
435 | | - action.set(Tags.Desc, "Type '" + ((Type)innerAction).get(Tags.InputText) |
436 | | - + "' into '" + action.get(Tags.OriginWidget).get(Tags.Desc, "<no description>" + "'")); |
437 | | - return true; |
438 | | - } |
439 | | - |
440 | | - if(innerAction instanceof PasteText) { |
441 | | - ((PasteText)innerAction).set(Tags.InputText, inputText); |
442 | | - action.set(Tags.Desc, "PasteText '" + ((PasteText)innerAction).get(Tags.InputText) |
443 | | - + "' into '" + action.get(Tags.OriginWidget).get(Tags.Desc, "<no description>" + "'")); |
444 | | - return true; |
445 | | - } |
446 | | - } |
447 | | - } |
448 | | - |
449 | | - return false; |
450 | | - } |
451 | | - |
452 | 329 | /** |
453 | 330 | * Returns the amount of invalid actions (incorrect actionId, unable to parse llm response, etc.) |
454 | 331 | * @return Amount of invalid actions. |
|
0 commit comments