Add user defined system message

This commit is contained in:
dwrz
2026-05-28 00:53:08 +00:00
parent a06833bb8b
commit 9ea4005e96
26 changed files with 652 additions and 222 deletions

View File

@@ -22,8 +22,8 @@ type Config struct {
Key string `yaml:"key"`
// Model is the model identifier.
Model string `yaml:"model"`
// SystemPrompt is prepended to all conversations.
SystemPrompt string `yaml:"system_prompt"`
// SystemMessage is prepended to all conversations.
SystemMessage string `yaml:"system_message"`
// Timeout is the maximum duration for a query (e.g., "5m").
// Defaults to 5 minutes if empty.
Timeout string `yaml:"timeout"`
@@ -49,13 +49,13 @@ func (cfg Config) Validate() error {
// Client wraps an OpenAI-compatible client with tool execution support.
type Client struct {
client *openai.Client
log *slog.Logger
model string
registry *tool.Registry
systemPrompt string
timeout time.Duration
tools []openai.Tool
client *openai.Client
log *slog.Logger
model string
registry *tool.Registry
systemMessage string
timeout time.Duration
tools []openai.Tool
}
// NewClient creates a new LLM client with the provided configuration.
@@ -70,10 +70,10 @@ func NewClient(
}
llm := &Client{
log: log,
model: cfg.Model,
systemPrompt: cfg.SystemPrompt,
registry: registry,
log: log,
model: cfg.Model,
systemMessage: cfg.SystemMessage,
registry: registry,
}
if cfg.Timeout == "" {
@@ -122,12 +122,14 @@ func (c *Client) DefaultModel() string {
// Query sends messages to the LLM using the specified model.
// If model is empty, uses the default configured model.
// If systemMessage is non-empty, it overrides the configured system message.
// Returns all messages generated during the query, including tool calls
// and tool results. The final message is the last element in the slice.
func (c *Client) Query(
ctx context.Context,
messages []openai.ChatCompletionMessage,
model string,
systemMessage string,
) ([]openai.ChatCompletionMessage, error) {
ctx, cancel := context.WithTimeout(ctx, c.timeout)
defer cancel()
@@ -137,13 +139,19 @@ func (c *Client) Query(
model = c.model
}
// Prepend system prompt, if configured and not already present.
if c.systemPrompt != "" && (len(messages) == 0 ||
// Use per-request system message if provided, otherwise fall back to config.
effectiveMessage := c.systemMessage
if systemMessage != "" {
effectiveMessage = systemMessage
}
// Prepend system message, if configured and not already present.
if effectiveMessage != "" && (len(messages) == 0 ||
messages[0].Role != openai.ChatMessageRoleSystem) {
messages = append(
[]openai.ChatCompletionMessage{{
Role: openai.ChatMessageRoleSystem,
Content: c.systemPrompt,
Content: effectiveMessage,
}},
messages...,
)
@@ -239,11 +247,13 @@ type StreamEvent struct {
// streams results. Each complete message (assistant reply, tool call,
// tool result) is sent to the events channel as it becomes available.
// The channel is closed before returning.
// If systemMessage is non-empty, it overrides the configured system message.
// Returns all messages generated during the query.
func (c *Client) QueryStream(
ctx context.Context,
messages []openai.ChatCompletionMessage,
model string,
systemMessage string,
events chan<- StreamEvent,
) error {
defer close(events)
@@ -256,13 +266,19 @@ func (c *Client) QueryStream(
model = c.model
}
// Prepend system prompt, if configured and not already present.
if c.systemPrompt != "" && (len(messages) == 0 ||
// Use per-request system message if provided, otherwise fall back to config.
effectiveMessage := c.systemMessage
if systemMessage != "" {
effectiveMessage = systemMessage
}
// Prepend system message, if configured and not already present.
if effectiveMessage != "" && (len(messages) == 0 ||
messages[0].Role != openai.ChatMessageRoleSystem) {
messages = append(
[]openai.ChatCompletionMessage{{
Role: openai.ChatMessageRoleSystem,
Content: c.systemPrompt,
Content: effectiveMessage,
}},
messages...,
)