diff --git a/java/langchain4j-editor/.gitignore b/java/langchain4j-editor/.gitignore new file mode 100644 index 0000000..5ff6309 --- /dev/null +++ b/java/langchain4j-editor/.gitignore @@ -0,0 +1,38 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/java/langchain4j-editor/pom.xml b/java/langchain4j-editor/pom.xml new file mode 100644 index 0000000..7f0bb67 --- /dev/null +++ b/java/langchain4j-editor/pom.xml @@ -0,0 +1,69 @@ + + + 4.0.0 + + org.example + langchain4j-examples + 1.0-SNAPSHOT + + + + + junit + junit + 4.12 + test + + + + + org.projectlombok + lombok + 1.18.30 + provided + + + + com.fasterxml.jackson.core + jackson-annotations + 2.15.3 + + + + com.fasterxml.jackson.core + jackson-databind + 2.15.3 + + + + dev.langchain4j + langchain4j + 0.23.0 + + + dev.langchain4j + langchain4j-open-ai + 0.23.0 + + + dev.langchain4j + langchain4j-embeddings-all-minilm-l6-v2 + 0.23.0 + + + org.projectlombok + lombok + 1.18.28 + compile + + + + + 18 + 18 + UTF-8 + + + \ No newline at end of file diff --git a/java/langchain4j-editor/src/main/java/com/johnsosoka/Main.java b/java/langchain4j-editor/src/main/java/com/johnsosoka/Main.java new file mode 100644 index 0000000..510e652 --- /dev/null +++ b/java/langchain4j-editor/src/main/java/com/johnsosoka/Main.java @@ -0,0 +1,90 @@ +package com.johnsosoka; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.johnsosoka.agent.BlogEditor; +import com.johnsosoka.model.FeedbackItem; +import dev.langchain4j.model.openai.OpenAiChatModel; +import dev.langchain4j.service.AiServices; +import lombok.SneakyThrows; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.Duration; +import java.util.List; + +public class Main { + + + public static void main(String[] args) { + String API_KEY = System.getenv("API_KEY"); + + if (API_KEY == null || API_KEY.isEmpty()) { + System.out.println("API_KEY not set. Exiting..."); + return; + } + + BlogEditor requirementAssistant = AiServices.builder(BlogEditor.class) + .chatLanguageModel(OpenAiChatModel.builder() + .apiKey(API_KEY) + .modelName("gpt-4") + .timeout(Duration.ofSeconds(380)) + .maxRetries(1) + .build()) + //.chatMemory(MessageWindowChatMemory.withMaxMessages(5)) + .build(); + + String blogPost = readMarkdownFile("/Users/john/code/johnsosoka-com/jscom-blog/website/_posts/blog/2023-10-31-llm-app-patterns.md"); + + List technicalFeedback = getTechnicalFeedback(blogPost, requirementAssistant); + List contentFeedback = getContentEditorFeedback(blogPost, requirementAssistant); + + printFeedback(technicalFeedback); + printFeedback(contentFeedback); + + System.out.println("done."); + } + + public static List getTechnicalFeedback(String blogPost, BlogEditor assistant) { + String technicalFeedback = assistant.provideTechnicalFeedback(blogPost); + List technicalFeedbackItems = null; + try { + technicalFeedbackItems = new ObjectMapper().readValue(technicalFeedback, new TypeReference>(){}); + } catch (JsonProcessingException e) { + e.printStackTrace(); + } + System.out.println("Technical Feedback Items: " + technicalFeedbackItems.size()); + return technicalFeedbackItems; + } + + public static List getContentEditorFeedback(String blogPost, BlogEditor assistant) { + String contentEditorFeedback = assistant.provideContentEditorFeedback(blogPost); + List contentEditorFeedbackItems = null; + try { + contentEditorFeedbackItems = new ObjectMapper().readValue(contentEditorFeedback, new TypeReference>(){}); + } catch (JsonProcessingException e) { + e.printStackTrace(); + } + System.out.println("Content Editor Feedback Items: " + contentEditorFeedbackItems.size()); + return contentEditorFeedbackItems; + } + + public static void printFeedback(List feedbackItems) { + // iterate through both feedback items printing fields + for (FeedbackItem feedbackItem : feedbackItems) { + System.out.println("Feedback Type: " + feedbackItem.getFeedbackType()); + System.out.println("Feedback: " + feedbackItem.getFeedback()); + System.out.println("Why the LLM cares: " + feedbackItem.getExplanation()); + System.out.println(feedbackItem.getFeedback()); + } + } + + @SneakyThrows + public static String readMarkdownFile(String filePath) { + Path path = Paths.get(filePath); + List lines = Files.readAllLines(path); + return String.join("\n", lines); + } +} diff --git a/java/langchain4j-editor/src/main/java/com/johnsosoka/agent/BlogEditor.java b/java/langchain4j-editor/src/main/java/com/johnsosoka/agent/BlogEditor.java new file mode 100644 index 0000000..ddf8a7e --- /dev/null +++ b/java/langchain4j-editor/src/main/java/com/johnsosoka/agent/BlogEditor.java @@ -0,0 +1,90 @@ +package com.johnsosoka.agent; + +import dev.langchain4j.service.SystemMessage; +import dev.langchain4j.service.UserMessage; + +public interface BlogEditor { + + String BLOG_AUDIENCE_CLAUSE = "Remember, this blog is meant for a technical audience. Keep in mind, this blog does have a personal touch, so it's not meant to be overly formal."; + String DETAIL_CLAUSE = "When providing feedback, be as detailed as possible. " + + "Consider industry best practices, and provide specific examples. " + + "Consider both the content and technical accuracy of the blog post."; + + String JSON_LIST_RESPONSE_CLAUSE = "Your feedback must adhere to the following format: " + + "[{\n" + + " \"feedback_type\": \"CONTENT | STRUCTURE | TECHNICAL_ACCURACY | READABILITY | REFERENCES\",\n" + + " \"original_quote\": \"The original quote from the blog post.\",\n" + + " \"feedback\": \"The critical feedback provided to improve the blog post.\",\n" + + " \"explanation\": \"Explain your reasoning for providing this feedback.\",\n" + + " \"follow_up\": {\n" + + " \"action_required\": \"NONE | REVISE | CLARIFY | ADD_REFERENCE | VERIFY_TECHNICAL_VALIDITY\",\n" + + " \"additional_info\": \"Any extra information or resources required that will assist in carrying out the required action.\"\n" + + " }\n" + + "}]"; + + String GENERAL_ROLE = "You are a world-class editor assisting in the evaluation of a technical blog post."; + + @SystemMessage({"You are a world-class editor helping a friend edit their personal tech blog.", + "Your goal is to carefully analyze the blog post and provide feedback to your friend.", + BLOG_AUDIENCE_CLAUSE, + DETAIL_CLAUSE, + JSON_LIST_RESPONSE_CLAUSE + }) + @UserMessage("Please provide feedback on the blog post: {{it}}") + String provideFeedback(String blogPost); + + @SystemMessage({ + // General Role + GENERAL_ROLE, + // Detailed Role - STRUCTURE, CLARITY, READABILITY + "Your primary task is to meticulously scrutinize the blog post for overall structure, clarity, and readability.", + "Hierarchy: Ensure that the headers and sub-headers create a coherent hierarchy that logically guides the reader through the content.", + "Flow: Check the sequencing of paragraphs and sections for natural progression.", + "Consistency: Verify that the formatting is consistently applied throughout.", + "Transitions: Review the transitions between sections and paragraphs for seamlessness.", + "Precision: Review each sentence for precision of language and eliminate ambiguous phrases or undefined jargon.", + "Conciseness: Look for verbose or redundant expressions and suggest more concise alternatives.", + "Purpose: Ensure each paragraph and section serves a clear purpose and contributes to the blog post's main objective or arguments.", + "Technical Concepts: Validate that technical ideas are clearly introduced and easy to understand.", + "Language Level: Gauge the language complexity to match the intended audience.", + "Sentence Structure: Assess sentence structure for variety and rhythm.", + "Punctuation: Verify the correct usage of all punctuation marks.", + "Visual Aids: Confirm that visual elements like bullet points and block quotes are used appropriately to improve readability.", + "Your goal is to provide actionable feedback, specifying what needs to be revised, removed, or added. Each feedback item should be accompanied by your reasoning for these changes.", + "Your expertise will be instrumental in elevating this blog post to a level of excellence that sets it apart in the tech community.", + // Clauses + BLOG_AUDIENCE_CLAUSE, + DETAIL_CLAUSE, + JSON_LIST_RESPONSE_CLAUSE + + }) + @UserMessage("Please provide feedback on the blog post: {{it}}") + String provideContentEditorFeedback(String blogPost); + + @SystemMessage({ + // General Role + GENERAL_ROLE, + // Detailed Role - TECHNICAL / ACCURACY + "You are a world-class technical editor with a keen eye for detail, tasked with evaluating the technical aspects of this blog post.", + "Your primary objective is to ensure that the blog post is not only factually accurate but also adheres to industry best practices.", + "Accuracy: Verify the correctness of all technical statements, data, and code snippets. Make sure all claims are backed by reliable sources or empirical evidence.", + "Best Practices: Check if the blog post follows current industry best practices. If newer or better approaches exist, suggest them as revisions.", + "Libraries and Frameworks: Confirm that the correct versions of any libraries or frameworks are referenced. Make sure that deprecated or unsafe methods are not used.", + "Security: Evaluate the post for any security red flags, such as insecure code samples, and suggest safer alternatives.", + "Performance: Assess if performance best practices are followed in code samples and technical advice.", + "Compatibility: Ensure that the solutions offered are compatible across different environments, such as various operating systems or browser versions.", + "Up-to-Date Information: Determine whether the blog post refers to the most recent research, data, or technology. Suggest updates if necessary.", + "Citations: Verify that all technical claims are properly cited and that the references are reliable and up-to-date.", + "Potential Pitfalls: Point out any potential issues or common misunderstandings related to the topic that the reader should be aware of.", + "Future Research: If applicable, propose follow-up questions that could lead to further exploration or clarify areas where the tech community has yet to reach a consensus.", + "Your goal is to provide detailed, actionable feedback, specifying what needs to be revised, added, or removed. Each feedback item should include your reasoning, along with any necessary resources or citations.", + "Your technical expertise will be crucial in ensuring that this blog post is both accurate and enlightening, standing out as a reliable resource in the tech community.", + // Clauses + BLOG_AUDIENCE_CLAUSE, + DETAIL_CLAUSE, + JSON_LIST_RESPONSE_CLAUSE + }) + @UserMessage("Please provide technical feedback on the blog post: {{it}}") + String provideTechnicalFeedback(String blogPost); + +} diff --git a/java/langchain4j-editor/src/main/java/com/johnsosoka/model/FeedbackItem.java b/java/langchain4j-editor/src/main/java/com/johnsosoka/model/FeedbackItem.java new file mode 100644 index 0000000..99159be --- /dev/null +++ b/java/langchain4j-editor/src/main/java/com/johnsosoka/model/FeedbackItem.java @@ -0,0 +1,34 @@ +package com.johnsosoka.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +@Data +public class FeedbackItem { + + @JsonProperty("feedback_type") + private String feedbackType; + + @JsonProperty("original_quote") + private String originalQuote; + + @JsonProperty("feedback") + private String feedback; + + @JsonProperty("explanation") + private String explanation; + + @JsonProperty("follow_up") + private FollowUp followUp; + + @Data + public static class FollowUp { + + @JsonProperty("action_required") + private String actionRequired; + + @JsonProperty("additional_info") + private String additionalInfo; + } +} + diff --git a/java/langchain4j-editor/src/main/java/com/johnsosoka/prompt/DynamicPrompt.java b/java/langchain4j-editor/src/main/java/com/johnsosoka/prompt/DynamicPrompt.java new file mode 100644 index 0000000..9c867a4 --- /dev/null +++ b/java/langchain4j-editor/src/main/java/com/johnsosoka/prompt/DynamicPrompt.java @@ -0,0 +1,19 @@ +package com.johnsosoka.prompt; + +import dev.langchain4j.model.input.structured.StructuredPrompt; +import lombok.Builder; + + + +@Builder +@StructuredPrompt({"You are a world-class editor helping a friend edit their personal tech blog.", + "Your goal is to carefully analyze the blog post and provide feedback to your friend.", + "Notes from Author: {{authorNotes}}", + "Blog Post Content: {{blogPost}}", +}) +public class DynamicPrompt { + + private String authorNotes; + private String blogPost; + +}