feat: add history messages, reasoning content, and adaptive language support
- Add optional history parameter to Run/RunStream for conversation context - Add Reasoning/ReasoningDelta fields to completion and stream types - Update llama.cpp adapter to propagate reasoning content from responses - Default persona language now adapts to the user's language dynamically
This commit is contained in:
parent
cd13f79bb3
commit
a38f63683b
8 changed files with 46 additions and 31 deletions
|
|
@ -206,8 +206,8 @@ Bucle iterativo entre LLM y ejecución de tools. Es el "cerebro" que orquesta to
|
|||
|
||||
```go
|
||||
type Loop interface {
|
||||
Run(ctx context.Context, input string) (Response, error)
|
||||
RunStream(ctx context.Context, input string) iter.Seq2[Chunk, error]
|
||||
Run(ctx context.Context, input string, history ...Message) (Response, error)
|
||||
RunStream(ctx context.Context, input string, history ...Message) iter.Seq2[Chunk, error]
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
|
|
|
|||
|
|
@ -206,8 +206,8 @@ Iterative loop between LLM and tool execution. It's the "brain" that orchestrate
|
|||
|
||||
```go
|
||||
type Loop interface {
|
||||
Run(ctx context.Context, input string) (Response, error)
|
||||
RunStream(ctx context.Context, input string) iter.Seq2[Chunk, error]
|
||||
Run(ctx context.Context, input string, history ...Message) (Response, error)
|
||||
RunStream(ctx context.Context, input string, history ...Message) iter.Seq2[Chunk, error]
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
|
|
|
|||
|
|
@ -23,8 +23,8 @@ while iteration < MaxIterations:
|
|||
|
||||
```go
|
||||
type Loop interface {
|
||||
Run(ctx context.Context, input string) (Response, error)
|
||||
RunStream(ctx context.Context, input string) iter.Seq2[Chunk, error]
|
||||
Run(ctx context.Context, input string, history ...llm.Message) (Response, error)
|
||||
RunStream(ctx context.Context, input string, history ...llm.Message) iter.Seq2[Chunk, error]
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@ while iteration < MaxIterations:
|
|||
|
||||
```go
|
||||
type Loop interface {
|
||||
Run(ctx context.Context, input string) (Response, error)
|
||||
RunStream(ctx context.Context, input string) iter.Seq2[Chunk, error]
|
||||
Run(ctx context.Context, input string, history ...llm.Message) (Response, error)
|
||||
RunStream(ctx context.Context, input string, history ...llm.Message) iter.Seq2[Chunk, error]
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
|
|
|
|||
|
|
@ -75,10 +75,12 @@ func New(cfg Config) *Loop {
|
|||
}
|
||||
|
||||
// Run executes the agent loop and returns the final response.
|
||||
func (l *Loop) Run(ctx context.Context, input string) (Response, error) {
|
||||
// Optional history messages are appended after the system prompt and before
|
||||
// the new user input.
|
||||
func (l *Loop) Run(ctx context.Context, input string, history ...llm.Message) (Response, error) {
|
||||
start := time.Now()
|
||||
|
||||
messages := l.buildInitialMessages(input)
|
||||
messages := l.buildInitialMessages(input, history)
|
||||
var finalContent string
|
||||
var allToolCalls []llm.ToolCall
|
||||
var totalUsage llm.TokenUsage
|
||||
|
|
@ -137,9 +139,11 @@ func (l *Loop) Run(ctx context.Context, input string) (Response, error) {
|
|||
}
|
||||
|
||||
// RunStream executes the agent loop with streaming output.
|
||||
func (l *Loop) RunStream(ctx context.Context, input string) iter.Seq2[llm.StreamChunk, error] {
|
||||
// Optional history messages are appended after the system prompt and before
|
||||
// the new user input.
|
||||
func (l *Loop) RunStream(ctx context.Context, input string, history ...llm.Message) iter.Seq2[llm.StreamChunk, error] {
|
||||
return func(yield func(llm.StreamChunk, error) bool) {
|
||||
messages := l.buildInitialMessages(input)
|
||||
messages := l.buildInitialMessages(input, history)
|
||||
iterations := 0
|
||||
|
||||
for iterations < l.cfg.MaxIters {
|
||||
|
|
@ -177,7 +181,7 @@ func (l *Loop) RunStream(ctx context.Context, input string) iter.Seq2[llm.Stream
|
|||
}
|
||||
}
|
||||
|
||||
if !hasToolCalls && chunk.Delta != "" {
|
||||
if !hasToolCalls && (chunk.Delta != "" || chunk.ReasoningDelta != "") {
|
||||
responseBuilder.WriteString(chunk.Delta)
|
||||
if !yield(chunk, nil) {
|
||||
return
|
||||
|
|
@ -194,12 +198,13 @@ func (l *Loop) RunStream(ctx context.Context, input string) iter.Seq2[llm.Stream
|
|||
}
|
||||
}
|
||||
|
||||
func (l *Loop) buildInitialMessages(input string) []llm.Message {
|
||||
func (l *Loop) buildInitialMessages(input string, history []llm.Message) []llm.Message {
|
||||
systemPrompt := persona.AssembleSystemPrompt(l.cfg.Persona, "")
|
||||
return []llm.Message{
|
||||
{Role: llm.RoleSystem, Content: systemPrompt},
|
||||
{Role: llm.RoleUser, Content: input},
|
||||
}
|
||||
messages := make([]llm.Message, 0, len(history)+2)
|
||||
messages = append(messages, llm.Message{Role: llm.RoleSystem, Content: systemPrompt})
|
||||
messages = append(messages, history...)
|
||||
messages = append(messages, llm.Message{Role: llm.RoleUser, Content: input})
|
||||
return messages
|
||||
}
|
||||
|
||||
func (l *Loop) getToolSchemas() []json.RawMessage {
|
||||
|
|
|
|||
|
|
@ -125,6 +125,7 @@ func (c *Client) Stream(ctx context.Context, req llm.CompletionRequest) iter.Seq
|
|||
for _, choice := range event.Choices {
|
||||
chunk := llm.StreamChunk{
|
||||
Delta: choice.Delta.Content,
|
||||
ReasoningDelta: choice.Delta.ReasoningContent,
|
||||
}
|
||||
if choice.FinishReason != "" {
|
||||
chunk.FinishReason = choice.FinishReason
|
||||
|
|
@ -210,6 +211,7 @@ func (c *Client) toResponse(resp llamaChatResponse) llm.CompletionResponse {
|
|||
ID: resp.ID,
|
||||
Model: resp.Model,
|
||||
Content: choice.Message.Content,
|
||||
Reasoning: choice.Message.ReasoningContent,
|
||||
StopReason: choice.FinishReason,
|
||||
}
|
||||
|
||||
|
|
@ -272,6 +274,7 @@ type llamaChoice struct {
|
|||
type llamaMessageResult struct {
|
||||
Role string `json:"role"`
|
||||
Content string `json:"content"`
|
||||
ReasoningContent string `json:"reasoning_content"`
|
||||
ToolCalls []llamaToolCall `json:"tool_calls"`
|
||||
}
|
||||
|
||||
|
|
@ -307,6 +310,7 @@ type llamaStreamChoice struct {
|
|||
|
||||
type llamaStreamDelta struct {
|
||||
Content string `json:"content"`
|
||||
ReasoningContent string `json:"reasoning_content"`
|
||||
Role string `json:"role"`
|
||||
ToolCalls []llamaStreamToolCall `json:"tool_calls"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@ type CompletionResponse struct {
|
|||
ID string `json:"id"`
|
||||
Model string `json:"model"`
|
||||
Content string `json:"content"`
|
||||
Reasoning string `json:"reasoning,omitempty"`
|
||||
ToolCalls []ToolCall `json:"tool_calls,omitempty"`
|
||||
StopReason string `json:"stop_reason"`
|
||||
Usage TokenUsage `json:"usage,omitempty"`
|
||||
|
|
@ -106,6 +107,7 @@ type ToolCall struct {
|
|||
// StreamChunk is emitted by the iterator returned from Stream().
|
||||
type StreamChunk struct {
|
||||
Delta string `json:"delta"`
|
||||
ReasoningDelta string `json:"reasoning_delta,omitempty"`
|
||||
ToolCalls []ToolCall `json:"tool_calls,omitempty"`
|
||||
FinishReason string `json:"finish_reason,omitempty"`
|
||||
Usage TokenUsage `json:"usage,omitempty"` // only present on final chunk
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ func DefaultPersona() Persona {
|
|||
Name: "Rony",
|
||||
Tone: "professional and helpful",
|
||||
Style: "clear and concise",
|
||||
Language: "en",
|
||||
Language: "the user's language",
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -59,8 +59,12 @@ func AssembleSystemPrompt(p Persona, agentsMD string) string {
|
|||
parts = append(parts, fmt.Sprintf("Write in a %s style.", p.Style))
|
||||
}
|
||||
if p.Language != "" {
|
||||
if strings.EqualFold(p.Language, "the user's language") {
|
||||
parts = append(parts, "Respond in the same language as the user's messages.")
|
||||
} else {
|
||||
parts = append(parts, fmt.Sprintf("Respond in %s.", p.Language))
|
||||
}
|
||||
}
|
||||
for _, c := range p.Constraints {
|
||||
parts = append(parts, fmt.Sprintf("CONSTRAINT: %s", c))
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue