Skip to content

Commit

Permalink
Merge pull request #1052 from mariofusco/out_guard_rewrite_doc
Browse files Browse the repository at this point in the history
Document rewriting output guardrails
  • Loading branch information
geoand authored Nov 6, 2024
2 parents 237418a + e366a4d commit 1b1115e
Showing 1 changed file with 71 additions and 2 deletions.
73 changes: 71 additions & 2 deletions docs/modules/ROOT/pages/guardrails.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -299,9 +299,10 @@ The <<_detecting_hallucinations_in_the_rag_context>> section gives an example of

==== Output Guardrails Outcome

Output guardrails can have five outcomes:
Output guardrails can have six outcomes:

- _pass_ - The output is valid, the next guardrail is executed, and if the last guardrail passes, the output is returned to the caller.
- _pass with rewrite_ - The output isn't valid in its original form, but has been rewritten in order to make it valid, the next guardrail is executed against the rewritten output, and if the last guardrail passes, the output is returned to the caller.
- _fail_ - The output is invalid, but the next guardrail is executed the same, in order to accumulate all the possible validation problems.
- _fatal_ - The output is invalid, the next guardrail is **not** executed, and the error is rethrown.
- _fatal with retry_ - The output is invalid, the next guardrail is **not** executed, and the LLM is called again with the **same** prompt.
Expand Down Expand Up @@ -507,4 +508,72 @@ public class HallucinationGuard implements OutputGuardrail {
return 1.0 - cosineSimilarity;
}
}
----
----

=== Rewriting the LLM output
It may happen that the output generated by the LLM is not completely satisfying, but it can be programmatically adjusted instead of attempting a retry or a remprompt, both implying a costly, time consuming and less reliable new interaction with the LLM. For instance it is quite common that an LLM produces the json of the data object that it is required to extract from the user prompt, but appends to it some unwanted explanation of why it generated that result, making the json unparsable, something like

[source]
----
{"name":"Alex", age:18} Alex is 18 since he became an adult a few days ago.
----

In this situation it is better to try to programmatically trim the json part of the response and check if we can deserialize a valid Person object out of it, before trying to reprompt the LLM again. If the programmatic extraction of the json string from the partially hallucinated LLM output succeeds, it is possible to propagate the rewritten output through the `successWith` method as in the following example.

[source,java]
----
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import dev.langchain4j.data.message.AiMessage;
import io.quarkiverse.langchain4j.guardrails.OutputGuardrail;
import io.quarkiverse.langchain4j.guardrails.OutputGuardrailResult;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.jboss.logging.Logger;
@ApplicationScoped
public class ValidJsonOutputGuardrail implements OutputGuardrail {
private static final ObjectMapper MAPPER = new ObjectMapper();
@Inject
Logger logger;
@Override
public OutputGuardrailResult validate(AiMessage responseFromLLM) {
String llmResponse = responseFromLLM.text();
logger.infof("LLM output: %s", llmResponse);
if (validateJson(llmResponse, Person.class)) {
return success();
}
String json = trimNonJson(llmResponse);
if (json != null && validateJson(json, Person.class)) {
return successWith(json);
}
return reprompt("Invalid JSON",
"Make sure you return a valid JSON object following "
+ "the specified format");
}
private static String trimNonJson(String llmResponse) {
int jsonStart = llmResponse.indexOf("{");
int jsonEnd = llmResponse.indexOf("}");
if (jsonStart >= 0 && jsonEnd >= 0 && jsonStart < jsonEnd) {
return llmResponse.substring(jsonStart + 1, jsonEnd);
}
return null;
}
private static boolean validateJson(String json, Class<?> expectedOutputClass) {
try {
MAPPER.readValue(json, expectedOutputClass);
return true;
} catch (JsonProcessingException e) {
return false;
}
}
}
----

0 comments on commit 1b1115e

Please sign in to comment.