2026-09-05 00:43:25 -07:00
|
|
|
package action
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
|
|
"github.com/rockliang/poimen/workflows/action/llm"
|
|
|
|
|
"github.com/rockliang/poimen/workflows/statemachine"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// LLMInferenceInput is input for LLMInferenceActivity
|
|
|
|
|
type LLMInferenceInput struct {
|
|
|
|
|
Model string `json:"model"` // Model ID (reasoning, ornith:35b, etc)
|
|
|
|
|
SystemPrompt string `json:"system_prompt"` // System instruction
|
|
|
|
|
UserPrompt string `json:"user_prompt"` // User message
|
|
|
|
|
Temperature float64 `json:"temperature,omitempty"` // LLM temperature (0-1)
|
|
|
|
|
MaxTokens int `json:"max_tokens,omitempty"` // Max output tokens
|
2026-09-05 00:46:40 -07:00
|
|
|
AuthToken string `json:"auth_token,omitempty"` // JWT token for authenticated endpoints
|
2026-09-05 00:43:25 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// LLMInferenceOutput is output from LLMInferenceActivity
|
|
|
|
|
type LLMInferenceOutput struct {
|
|
|
|
|
Response string `json:"response"` // LLM response text
|
|
|
|
|
Model string `json:"model"` // Model used
|
|
|
|
|
StopReason string `json:"stop_reason"` // How inference stopped (stop_sequence, length, etc)
|
|
|
|
|
TokensUsed int `json:"tokens_used"` // Total tokens consumed
|
|
|
|
|
ErrorMessage string `json:"error,omitempty"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// LLMInferenceActivity calls LLM API with given prompt and returns response
|
|
|
|
|
func LLMInferenceActivity(ctx context.Context, in LLMInferenceInput) (LLMInferenceOutput, error) {
|
|
|
|
|
logger := newActivityLogger(ctx)
|
|
|
|
|
|
|
|
|
|
output := LLMInferenceOutput{
|
|
|
|
|
Model: in.Model,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Validate input
|
|
|
|
|
if in.Model == "" {
|
|
|
|
|
return output, fmt.Errorf("model not specified")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if in.UserPrompt == "" {
|
|
|
|
|
return output, fmt.Errorf("user_prompt not specified")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
logger.logf("info", "Starting LLM inference with model: %s", in.Model)
|
|
|
|
|
|
|
|
|
|
// Create LLM client
|
|
|
|
|
client, err := llm.NewClient()
|
|
|
|
|
if err != nil {
|
|
|
|
|
output.ErrorMessage = err.Error()
|
|
|
|
|
return output, fmt.Errorf("failed to create LLM client: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Call LLM
|
2026-09-05 00:46:40 -07:00
|
|
|
logger.logf("info", "Calling LLM API (model=%s, prompt_len=%d, auth=%v)", in.Model, len(in.UserPrompt), in.AuthToken != "")
|
2026-09-05 00:43:25 -07:00
|
|
|
|
|
|
|
|
response, err := client.CreateMessage(ctx, llm.MessageInput{
|
|
|
|
|
Model: statemachine.ModelSpec{
|
|
|
|
|
ModelID: in.Model,
|
|
|
|
|
},
|
|
|
|
|
SystemPrompt: in.SystemPrompt,
|
|
|
|
|
Messages: []llm.MessageParam{
|
|
|
|
|
{
|
|
|
|
|
Role: "user",
|
|
|
|
|
Content: in.UserPrompt,
|
|
|
|
|
},
|
|
|
|
|
},
|
2026-09-05 00:46:40 -07:00
|
|
|
AuthToken: in.AuthToken,
|
2026-09-05 00:43:25 -07:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
output.ErrorMessage = err.Error()
|
|
|
|
|
logger.logf("error", "LLM API call failed: %v", err)
|
|
|
|
|
return output, fmt.Errorf("LLM inference failed: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
output.Response = response
|
|
|
|
|
output.StopReason = "stop_sequence"
|
|
|
|
|
|
|
|
|
|
logger.logf("info", "LLM inference completed (response_len=%d)", len(response))
|
|
|
|
|
|
|
|
|
|
return output, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// LLMBatchInferenceInput is input for batch inference
|
|
|
|
|
type LLMBatchInferenceInput struct {
|
|
|
|
|
Model string `json:"model"`
|
|
|
|
|
SystemPrompt string `json:"system_prompt"`
|
|
|
|
|
Prompts []string `json:"prompts"` // List of user prompts
|
|
|
|
|
Temperature float64 `json:"temperature,omitempty"`
|
2026-09-05 00:46:40 -07:00
|
|
|
AuthToken string `json:"auth_token,omitempty"` // JWT token for authenticated endpoints
|
2026-09-05 00:43:25 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// LLMBatchInferenceOutput is output from batch inference
|
|
|
|
|
type LLMBatchInferenceOutput struct {
|
|
|
|
|
Responses []string `json:"responses"` // LLM responses (parallel to input Prompts)
|
|
|
|
|
Model string `json:"model"`
|
|
|
|
|
Errors []string `json:"errors,omitempty"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// LLMBatchInferenceActivity calls LLM multiple times in sequence
|
|
|
|
|
func LLMBatchInferenceActivity(ctx context.Context, in LLMBatchInferenceInput) (LLMBatchInferenceOutput, error) {
|
|
|
|
|
logger := newActivityLogger(ctx)
|
|
|
|
|
|
|
|
|
|
output := LLMBatchInferenceOutput{
|
|
|
|
|
Model: in.Model,
|
|
|
|
|
Responses: []string{},
|
|
|
|
|
Errors: []string{},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if in.Model == "" {
|
|
|
|
|
return output, fmt.Errorf("model not specified")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(in.Prompts) == 0 {
|
|
|
|
|
return output, fmt.Errorf("no prompts provided")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
logger.logf("info", "Starting batch LLM inference (model=%s, count=%d)", in.Model, len(in.Prompts))
|
|
|
|
|
|
|
|
|
|
// Create LLM client
|
|
|
|
|
client, err := llm.NewClient()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return output, fmt.Errorf("failed to create LLM client: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Process each prompt
|
|
|
|
|
for i, prompt := range in.Prompts {
|
|
|
|
|
logger.logf("info", "Processing prompt %d/%d", i+1, len(in.Prompts))
|
|
|
|
|
|
|
|
|
|
response, err := client.CreateMessage(ctx, llm.MessageInput{
|
|
|
|
|
Model: statemachine.ModelSpec{
|
|
|
|
|
ModelID: in.Model,
|
|
|
|
|
},
|
|
|
|
|
SystemPrompt: in.SystemPrompt,
|
|
|
|
|
Messages: []llm.MessageParam{
|
|
|
|
|
{
|
|
|
|
|
Role: "user",
|
|
|
|
|
Content: prompt,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
output.Errors = append(output.Errors, fmt.Sprintf("prompt %d: %v", i, err))
|
|
|
|
|
output.Responses = append(output.Responses, "")
|
|
|
|
|
logger.logf("warn", "Failed to process prompt %d: %v", i, err)
|
|
|
|
|
} else {
|
|
|
|
|
output.Responses = append(output.Responses, response)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
logger.logf("info", "Batch inference completed (responses=%d, errors=%d)",
|
|
|
|
|
len(output.Responses), len(output.Errors))
|
|
|
|
|
|
|
|
|
|
return output, nil
|
|
|
|
|
}
|