From fc8a06bde5f8bb5ac3e6e5b121134352b3926843 Mon Sep 17 00:00:00 2001 From: Zacharias Date: Sun, 19 Jul 2026 21:47:18 +0200 Subject: [PATCH] Add LLMSystemPrompt as a unified system-prompt builder for OllamaObject Replaces ad-hoc system-prompt string construction with a structured builder (Identity, Context, Capabilities, Behavior, Output), plus OllamaObject#setSystemMessage/setSystemPrompt for replacing the system message post-construction instead of only at build time. --- Core/build.gradle | 2 +- .../me/neurodock/core/LLMSystemPrompt.java | 593 ++++++++++++++++++ .../neurodock/ollama/OllamaFunctionTool.java | 9 + .../me/neurodock/ollama/OllamaObject.java | 79 ++- 4 files changed, 672 insertions(+), 11 deletions(-) create mode 100644 Core/src/main/java/me/neurodock/core/LLMSystemPrompt.java diff --git a/Core/build.gradle b/Core/build.gradle index 23b11dc..f950aa5 100644 --- a/Core/build.gradle +++ b/Core/build.gradle @@ -2,7 +2,7 @@ plugins { id 'java-library' } -version = '1.7' +version = '1.8.13' dependencies { implementation project(":Plugin-API") diff --git a/Core/src/main/java/me/neurodock/core/LLMSystemPrompt.java b/Core/src/main/java/me/neurodock/core/LLMSystemPrompt.java new file mode 100644 index 0000000..fa29ba4 --- /dev/null +++ b/Core/src/main/java/me/neurodock/core/LLMSystemPrompt.java @@ -0,0 +1,593 @@ +package me.neurodock.core; + +import com.sun.jdi.connect.spi.TransportService; +import me.neurodock.ollama.*; +import org.json.JSONObject; + +import java.io.*; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.Map; + +//# Identity +//You are [name], [role/purpose]. +// +//# Context +//[Static facts it needs — what NeuroDock is, what world/game state means, etc.] +// +//# Capabilities / Tools +//[What tools it has access to and when to use them] +// +//# Behavior rules +//- [Constraints: tone, length, what not to do] +//- [Formatting requirements] +// +//# Output format +//[Exact schema if you need structured output — JSON shape, etc.] + +/** + * Compiles a structured system prompt for an LLM out of discrete, reusable pieces — + * identity, static context, tool-usage capabilities, behavior rules, and output format — + * instead of hand-writing and concatenating prompt strings inline. + *

+ * Each populated section is wrapped in its own XML-style tag + * ({@code }, {@code }, {@code }, {@code }, + * {@code }) and only included in the final prompt if it was actually set — see + * {@link #generateSystemPrompt()}. + *

+ * Instances are assembled via {@link #builder()}. Callers who need capability routing + * hints compiled from a live set of registered tools must call + * {@link #generateCapabilities(OllamaObject)} before {@link #generateSystemPrompt()}; + * see that method's docs for why this is a separate step rather than being folded into + * construction. + * + * @see Builder + * @see Capabilities + */ +public class LLMSystemPrompt { + /** + * The persona/role block for this prompt, or {@code null} if not set. See {@link Identity}. + */ + private Identity identity; + /** + * The raw, pre-built {@code } body for this prompt (static facts the model + * needs), or {@code null} if not set. Typically produced via {@link Context#build()}. + */ + private String context; + /** + * Usage-hint definitions for registered tools, or {@code null} if this prompt doesn't + * compile a capabilities section. This holds the *rules* for what to say about each + * tool; the actual compiled text lives in {@link #compiledCapabilities} and is only + * populated once {@link #generateCapabilities(OllamaObject)} has been called. + */ + private Capabilities capabilities; + /** + * The compiled, ready-to-render {@code } body, built by + * {@link #generateCapabilities(OllamaObject)} from {@link #capabilities} and a live + * set of registered tools. {@code null} until that method has been called at least + * once, or if {@link #capabilities} was never set. + */ + private String compiledCapabilities; + /** + * The raw, pre-built {@code } body for this prompt (tone, constraints, + * formatting rules), or {@code null} if not set. Usually populated via + * {@link Builder#loadBehaviorFromFile(String)}. + */ + private String behavior; + /** + * The output-format specification for this prompt (e.g. a JSON schema the model + * should reply in), or {@code null} if the model should respond in free text. + */ + private OutputFormat outputFormat; + + /** + * Constructs a system prompt from its component parts. Prefer {@link #builder()} + * over calling this directly. + * + * @param identity the persona/role block, or {@code null} to omit it + * @param context the pre-built static-context body, or {@code null} to omit it + * @param capabilities the tool usage-hint definitions, or {@code null} to omit the + * capabilities section entirely + * @param behavior the pre-built behavior/constraints body, or {@code null} to omit it + * @param outputFormat the output-format specification, or {@code null} for free-text output + */ + public LLMSystemPrompt(Identity identity, String context, Capabilities capabilities, String behavior, OutputFormat outputFormat) { + this.identity = identity; + this.context = context; + this.capabilities = capabilities; + this.behavior = behavior; + this.outputFormat = outputFormat; + } + + /** + * Compiles the {@code } section from the tools currently registered on + * the given {@link OllamaObject}, using the usage hints defined in {@link #capabilities}. + *

+ * This is intentionally a separate step from construction rather than something the + * constructor does automatically: which tools are registered on an {@link OllamaObject} + * can change after this prompt is built (tools added/removed at runtime), so the + * compiled capabilities text needs to be regenerated on demand against whatever the + * current tool set actually is, not frozen at construction time. + *

+ * For each registered {@link OllamaFunctionTool}, this looks up a usage hint by the + * tool's {@link Class} via {@link Capabilities#getUsageHints()}. If no hint was + * registered for that class, the tool is silently omitted from the compiled output — + * there is no fallback to {@link OllamaFunctionTool#description()}, since that text + * is written to help the model choose a tool mid-conversation, not to explain routing + * up front in a system prompt; conflating the two would blur what each is for. + *

+ * If {@link #capabilities} is {@code null} (this prompt doesn't use a capabilities + * section at all), this clears {@link #compiledCapabilities} to {@code null} and + * returns without inspecting {@code ollamaObject}. + * + * @param ollamaObject the object whose currently-registered tools should be inspected + */ + public void generateCapabilities(OllamaObject ollamaObject) { + if(capabilities == null) { + compiledCapabilities = null; + return; + } + + StringBuilder builder = new StringBuilder(); + for (Pair tool : ollamaObject.getTools()) { + if(tool.getKey() instanceof OllamaFunctionTool funcTool) { + String llmName = funcTool.name() + "_" + (funcTool.getSource() != null ? funcTool.getSource() : ""); + String hint = capabilities.getUsageHints().get(tool.getClass()); + + // fall back to description() if no explicit routing hint was set + String line = hint != null ? hint : funcTool.description(); + if (line == null) continue; // nothing usable to say about this tool, skip it + + builder.append(llmName).append(": ").append(line).append(System.lineSeparator()); + } + } + compiledCapabilities = builder.toString(); + } + + /** + * Renders this prompt's currently-set sections into a single system-role + * {@link OllamaMessage}, ready to be sent to Ollama. + *

+ * Each section — {@link #identity}, {@link #context}, {@link #compiledCapabilities}, + * {@link #behavior}, {@link #outputFormat} — is wrapped in its own XML-style tag and + * appended in that fixed order. A section is included only if it is non-null (and, for + * capabilities, non-blank); sections that were never set or never compiled are simply + * skipped rather than emitted as empty tags. + *

+ * Note that {@link #compiledCapabilities} reflects whatever tool set was live the last + * time {@link #generateCapabilities(OllamaObject)} was called — call that first if the + * registered tools may have changed since. + * + * @return a new {@link OllamaMessage} with role {@link OllamaMessageRole#SYSTEM} + * containing the compiled prompt text + */ + public OllamaMessage generateSystemPrompt() { + + StringBuilder builder = new StringBuilder(); + + if(identity != null) { + builder.append("").append(System.lineSeparator()).append(identity.build()).append(System.lineSeparator()).append("").append(System.lineSeparator()); + } + if(context != null) { + builder.append("").append(System.lineSeparator()).append(context).append(System.lineSeparator()).append("").append(System.lineSeparator()); + } + if (compiledCapabilities != null && !compiledCapabilities.isBlank()) { + builder.append("").append(System.lineSeparator()) + .append(compiledCapabilities) + .append(System.lineSeparator()).append("").append(System.lineSeparator()); + } + if(behavior != null) { + builder.append("").append(System.lineSeparator()).append(behavior).append(System.lineSeparator()).append("").append(System.lineSeparator()); + } + if(outputFormat != null) { + builder.append("").append(System.lineSeparator()).append(outputFormat.build()).append(System.lineSeparator()).append("").append(System.lineSeparator()); + } + + return new OllamaMessage(OllamaMessageRole.SYSTEM, builder.toString()); + } + + /** + * Creates a new {@link Builder} for assembling an {@link LLMSystemPrompt}. + * + * @return a fresh, empty {@link Builder} + */ + public static Builder builder() + { + return new Builder(); + } + + /** + * The persona/role block of a system prompt — who the model is and what it's for. + *

+ * At least one of {@code name}, {@code role}, or {@code purpose} must be non-blank; + * {@link #build()} throws if all three are absent, since an empty identity block is + * almost certainly a mistake rather than an intentional choice. + * + * @param name the model's given name (e.g. {@code "Alice"}), or {@code null}/blank to omit + * @param role the model's role (e.g. {@code "Personal assistant"}), or {@code null}/blank to omit + * @param purpose the model's purpose/goal, or {@code null}/blank to omit + */ + public record Identity(String name, String role, String purpose) { + /** + * Builds the identity sentence from whichever of {@link #name}, {@link #role}, and + * {@link #purpose} are present, joining them with commas. + * + * @return the composed identity sentence, e.g. + * {@code "You are Alice, Your role is Personal assistant"} + * @throws IllegalArgumentException if {@link #name}, {@link #role}, and + * {@link #purpose} are all {@code null} or blank + */ + public String build() + { + StringBuilder builder = new StringBuilder(); + if(name != null && !name.isBlank()) + { + builder.append("You are ").append(name); + } + if(role != null && !role.isBlank()) + { + if(!builder.isEmpty()) + { + builder.append(", "); + } + builder.append("Your role is ").append(role); + } + if(purpose != null && !purpose.isBlank()) + { + if(!builder.isEmpty()) + { + builder.append(", "); + } + builder.append("Your purpose is ").append(purpose); + } + if(!builder.isEmpty()) + return builder.toString(); + throw new IllegalArgumentException("Invalid identity"); + } + } + + /** + * A builder for the static-facts {@code } section of a system prompt — + * things the model should always know (e.g. "Always answer in English"). + *

+ * Facts are appended in the order they're added and joined with newlines by + * {@link #build()}. The resulting string is what gets passed to + * {@link Builder#context(String)}. + */ + public static class Context { + /** + * The ordered list of context facts added so far. + */ + ArrayList facts = new ArrayList<>(); + + /** + * Appends a single fact to this context block. + * + * @param fact the fact text to add + * @return this {@link Context}, for chaining + */ + public Context addFact(String fact) { + facts.add(fact); + return this; + } + + /** + * Returns the facts added to this context block so far, in insertion order. + * + * @return the live list of facts backing this {@link Context} + */ + public ArrayList getFacts() { + return facts; + } + + /** + * Joins all added facts into a single newline-separated string. + * + * @return the compiled context body, or an empty string if no facts were added + */ + public String build() { + StringBuilder builder = new StringBuilder(); + for(String fact: facts) { + if(!builder.isEmpty()) builder.append("\n"); + builder.append(fact); + } + return builder.toString(); + } + } + + /** + * Defines per-tool usage-routing hints for the {@code } section of a + * system prompt — i.e. "use this tool only in these cases" guidance, distinct from a + * tool's own {@link OllamaFunctionTool#description()}. + *

+ * Hints are keyed by the tool's {@link Class} rather than by a registered instance or + * its LLM-facing name, since NeuroDock's convention is one instance per tool class — + * see {@link #use(Class, String)}. This class only holds the hint definitions; the + * actual compiled text is produced by + * {@link LLMSystemPrompt#generateCapabilities(OllamaObject)}. + */ + public static class Capabilities { + /** + * Usage hints keyed by tool class, in the order they were registered. + */ + private final LinkedHashMap, String> usageHints = new LinkedHashMap<>(); + + /** + * Registers a usage-routing hint for a tool class — guidance on when the model + * should reach for this tool, e.g. {@code "This should only be used when needing + * to run CLI tools"}. + *

+ * Keyed by class rather than instance because a tool's LLM-facing name (which + * depends on its registration source) isn't known until it's actually registered + * on an {@link OllamaObject}; class identity is stable and known up front. + * + * @param toolClass the tool class this hint applies to + * @param usageHint the routing guidance text for this tool + * @return this {@link Capabilities}, for chaining + */ + public Capabilities use(Class toolClass, String usageHint) { + usageHints.put(toolClass, usageHint); + return this; + } + + /** + * Returns the usage hints registered so far, keyed by tool class. + * + * @return the live map backing this {@link Capabilities} + */ + Map, String> getUsageHints() { + return usageHints; + } + } + + /** + * The behavior/constraints section of a system prompt — tone, formatting rules, and + * other "how to behave" instructions. + *

+ * Not yet implemented — currently unused; {@link LLMSystemPrompt#behavior} is + * populated directly as a {@link String}, typically via + * {@link Builder#loadBehaviorFromFile(String)}, rather than through this class. + */ + public static class Behavior { + /** + * Structured behavioral constraints. Not yet implemented. + */ + Constraints constraints; + /** + * Output formatting rules. Not yet implemented. + */ + String formating; + + // TODO: Implement this + + /** + * Structured constraints for a {@link Behavior} block (tone, length limits, things + * to avoid, etc.). + *

+ * Not yet implemented. + */ + public static class Constraints + { + // TODO: Implement this + } + } + + /** + * Specifies the expected output format for a system prompt's {@code } section + * — e.g. a JSON schema the model should structure its replies as. + */ + public static class OutputFormat { + /** + * The raw format specification, rendered verbatim in the {@code } section. + */ + String format; + + /** + * Creates an output format from a raw, pre-formatted string. + * + * @param format the format specification text + */ + public OutputFormat(String format) { + this.format = format; + } + + /** + * Creates an output format from a JSON schema. + * + * @param format the JSON schema to use as the format specification + */ + public OutputFormat(JSONObject format) { + this.format = format.toString(); + } + + /** + * Returns the format specification text for rendering into the prompt. + * + * @return the raw format string + */ + public String build () { + return format; + } + } + + /** + * Builder for assembling an {@link LLMSystemPrompt} from its component sections. + * Obtain an instance via {@link LLMSystemPrompt#builder()}. + */ + public static class Builder { + /** + * The identity block to use, or {@code null} to omit it. Set via {@link #identity(Identity)}. + */ + private Identity identity; + /** + * The pre-built context body to use, or {@code null} to omit it. Set via {@link #context(String)}. + */ + private String context; + /** + * The capabilities/usage-hint definitions to use, or {@code null} to omit the + * capabilities section. Set via {@link #capabilities(Capabilities)}. + */ + private Capabilities capabilities; + /** + * The pre-built behavior body to use, or {@code null} to omit it. Set via + * {@link #behavior(String)} or loaded from disk via {@link #loadBehaviorFromFile(String)}. + */ + private String behavior; + /** + * The output format specification to use, or {@code null} for free-text output. + * Set via {@link #outputFormat(OutputFormat)}. + */ + private OutputFormat outputFormat; + + /** + * Loads {@link #behavior} from a text file, resolved in order against: + *

    + *
  1. the classpath
  2. + *
  3. {@link Core#DATA_DIR}
  4. + *
  5. the current runtime/working directory
  6. + *
  7. an exact file path match
  8. + *
+ * + * @param path the file to look for, resolved as described above + * @return this {@link Builder}, for chaining + * @throws IllegalStateException if no file could be found at any of the candidate + * locations, or if it exists but cannot be read + */ + public Builder loadBehaviorFromFile(String path) { + behavior = BehaviorLoader.load(path); + return this; + } + + /** + * Sets the identity/persona block for the prompt being built. + * + * @param identity the identity to use + * @return this {@link Builder}, for chaining + */ + public Builder identity(Identity identity) { + this.identity = identity; + return this; + } + + /** + * Sets the pre-built static-context body for the prompt being built. + * + * @param context the context body, typically produced via {@link Context#build()} + * @return this {@link Builder}, for chaining + */ + public Builder context(String context) { + this.context = context; + return this; + } + + /** + * Sets the capability usage-hint definitions for the prompt being built. These are + * only compiled into prompt text once + * {@link LLMSystemPrompt#generateCapabilities(OllamaObject)} is called on the built + * instance. + * + * @param capabilities the usage-hint definitions to use + * @return this {@link Builder}, for chaining + */ + public Builder capabilities(Capabilities capabilities) { + this.capabilities = capabilities; + return this; + } + + /** + * Sets the pre-built behavior body for the prompt being built directly, bypassing + * {@link #loadBehaviorFromFile(String)}. + * + * @param behavior the behavior/constraints text to use + * @return this {@link Builder}, for chaining + */ + public Builder behavior(String behavior) { + this.behavior = behavior; + return this; + } + + /** + * Sets the output format specification for the prompt being built. + * + * @param outputFormat the output format to use + * @return this {@link Builder}, for chaining + */ + public Builder outputFormat(OutputFormat outputFormat) { + this.outputFormat = outputFormat; + return this; + } + + /** + * Builds the {@link LLMSystemPrompt} from the sections configured on this builder. + * + * @return a new {@link LLMSystemPrompt} + */ + public LLMSystemPrompt build() { + return new LLMSystemPrompt(identity, context, capabilities, behavior, outputFormat); + } + } + + /** + * Internal helper for resolving and reading a behavior file from one of several + * candidate locations. See {@link Builder#loadBehaviorFromFile(String)}. + */ + private static class BehaviorLoader { + /** + * Checks whether a classpath resource exists at the given path without leaving it open. + * + * @param path the classpath-relative resource path to check + * @return {@code true} if a resource stream could be opened at that path + */ + private static boolean classpathResourceExists(String path) { + try (InputStream is = BehaviorLoader.class.getResourceAsStream(path)) { + return is != null; + } catch (IOException e) { + return false; + } + } + + /** + * Resolves and reads a behavior file as UTF-8 text, checking the classpath, + * {@link Core#DATA_DIR}, the working directory, and an exact path match, in that order. + * + * @param path the file to look for + * @return the full file contents, with each line terminated by {@code \n} + * @throws IllegalStateException if the resource cannot be found at any candidate + * location, or if it exists but cannot be read + */ + public static String load(String path) { + try { + InputStream in; + if (classpathResourceExists("/"+path)) { + in = BehaviorLoader.class.getResourceAsStream("/"+path); + } else { + Path dataDirPath = Path.of(Core.DATA_DIR.getPath(), path); + Path relativePath = Path.of(path); + if (Files.exists(dataDirPath)) { + in = new FileInputStream(dataDirPath.toFile()); + } else if (Files.exists(relativePath)) { + in = new FileInputStream(relativePath.toFile()); + } else { + throw new IllegalStateException("Could not find resource " + path); + } + } + + try (BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) { + StringBuilder sb = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + sb.append(line).append('\n'); // see note below + } + return sb.toString(); + } + } catch (IOException e) { + throw new IllegalStateException("Could not load resource " + path, e); + } + } + } +} diff --git a/Core/src/main/java/me/neurodock/ollama/OllamaFunctionTool.java b/Core/src/main/java/me/neurodock/ollama/OllamaFunctionTool.java index 2027740..411ffb1 100644 --- a/Core/src/main/java/me/neurodock/ollama/OllamaFunctionTool.java +++ b/Core/src/main/java/me/neurodock/ollama/OllamaFunctionTool.java @@ -19,6 +19,15 @@ public abstract class OllamaFunctionTool implements OllamaTool { */ protected String source = ""; + /** + * returns the source of this tool + * @return String source + */ + public final String getSource() + { + return source; + } + @Override public String toString() { JSONObject ret = new JSONObject(); diff --git a/Core/src/main/java/me/neurodock/ollama/OllamaObject.java b/Core/src/main/java/me/neurodock/ollama/OllamaObject.java index 4001d47..303ee18 100644 --- a/Core/src/main/java/me/neurodock/ollama/OllamaObject.java +++ b/Core/src/main/java/me/neurodock/ollama/OllamaObject.java @@ -1,6 +1,7 @@ package me.neurodock.ollama; import me.neurodock.core.Core; +import me.neurodock.core.LLMSystemPrompt; import me.neurodock.core.LaunchOptions; import me.neurodock.core.Pair; import me.neurodock.core.files.FileHandlerLocation; @@ -66,7 +67,7 @@ public class OllamaObject { * @param stream If the Ollama Object is streamed. see {@link OllamaObject#stream} * @param keep_alive The keep alive of the Ollama Object. see {@link OllamaObject#keep_alive} */ - private OllamaObject(String model, ArrayList messages, ArrayList> tools, JSONObject format, Map options, boolean stream, String keep_alive) { + private OllamaObject(String model, ArrayList messages, ArrayList> tools, JSONObject format, Map options, boolean stream, String keep_alive, LLMSystemPrompt prompt) { this.model = model; this.messages = messages; @@ -121,7 +122,7 @@ public class OllamaObject { } else { message = new OllamaMessageToolCall(OllamaMessageRole.fromRole(obj.getString("role")), obj.getString("content"), obj.getJSONArray("tool_calls")); } - messages.add(message); + this.messages.add(message); } }catch (Exception e) { System.out.println("Error loading old data"); @@ -133,6 +134,36 @@ public class OllamaObject { System.out.println("No old data found, skipping loading old data."); } } + + if(prompt == null) { + // Do nothing + } + else if(!this.messages.isEmpty()) + { + OllamaMessage systemPrompt = this.messages.getFirst(); + if(systemPrompt.getRole() != OllamaMessageRole.SYSTEM) + { + System.out.println("FIRST MESSAGE ISN'T A SYSTEM PROMPT!. Pushing all messages forward and injecting the system prompt"); + Core.writeLog("FIRST MESSAGE ISN'T A SYSTEM PROMPT!. Pushing all messages forward and injecting the system prompt"); + ArrayList newMessages = new ArrayList<>(); + newMessages.add(prompt.generateSystemPrompt()); + newMessages.addAll(messages); + this.messages = newMessages; + } + else + { + System.out.println("Replacing System prompt..."); + Core.writeLog("Replacing System prompt..."); + systemPrompt = prompt.generateSystemPrompt(); + this.messages.set(0, systemPrompt); + } + } + else + { + System.out.println("No old prompts. Inserting system prompt..."); + Core.writeLog("No old prompts. Inserting system prompt..."); + this.messages.add(prompt.generateSystemPrompt()); + } } /** @@ -253,13 +284,31 @@ public class OllamaObject { } /** - * Adds a system message to the messages. - *

- * Equvalent to {@code OllamaObject#addMessage(new OllamaMessage(OllamaMessageRole.SYSTEM, ))} - * @param message + * Sets the system message, replacing any existing one at the start of {@link #messages}, + * or inserting one at the start if none exists yet. + * @param message the system message text */ - public void addSystemMessage(String message) { - messages.add(new OllamaMessage(OllamaMessageRole.SYSTEM, message)); + public void setSystemMessage(String message) { + applySystemPrompt(new OllamaMessage(OllamaMessageRole.SYSTEM, message)); + } + + /** + * Replaces the current system prompt if one exists at the start of {@link #messages}, + * or inserts one at the start if none exists yet. + */ + public void setSystemPrompt(LLMSystemPrompt prompt) { + applySystemPrompt(prompt.generateSystemPrompt()); // shared with the constructor + } + + private void applySystemPrompt(OllamaMessage message) { + if (message == null) return; + if (messages.isEmpty()) { + messages.add(message); + } else if (messages.getFirst().getRole() != OllamaMessageRole.SYSTEM) { + messages.addFirst(message); + } else { + messages.set(0, message); + } } @Override @@ -330,6 +379,10 @@ public class OllamaObject { * The keep alive of the Ollama Object. */ String keep_alive; + /** + * This represents the System Prompt for the LLM + */ + LLMSystemPrompt systemPrompt; /** * Creates a new instance of {@link OllamaObjectBuilder}. @@ -521,13 +574,19 @@ public class OllamaObject { //throw new IllegalArgumentException("FileHandler is not supported yet!"); return addTools(FileHandler.getTools()); } - + + + public OllamaObjectBuilder setSystemPrompt(LLMSystemPrompt systemPrompt) { + this.systemPrompt = systemPrompt; + return this; + } + /** * Builds the {@link OllamaObject} * @return The {@link OllamaObject} */ public OllamaObject build() { - return new OllamaObject(model, messages, tools, format, options, stream, keep_alive); + return new OllamaObject(model, messages, tools, format, options, stream, keep_alive, systemPrompt); } } }