| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162 |
- package main
- import (
- "bytes"
- "encoding/json"
- "fmt"
- "io"
- "net/http"
- "strings"
- "time"
- "github.com/maximhq/bifrost/core/schemas"
- )
- // Config that can be set in Bifrost's config.json
- type PluginConfig struct {
- SmallModelEndpoint string `json:"small_model_endpoint"` // e.g. "http://localhost:8081/v1/chat/completions"
- SmallModelName string `json:"small_model_name"` // e.g. "qwen3-4b-instruct"
- RewriteSystemPrompt string `json:"rewrite_system_prompt"`
- EnableLogging bool `json:"enable_logging"`
- TimeoutSeconds int `json:"timeout_seconds"`
- }
- var config PluginConfig
- func Init(cfg any) error {
- // Default values
- config = PluginConfig{
- SmallModelEndpoint: "https://bifrost.home.timandjenni.com/openai/v1/chat/completions", // change to your llama.cpp / ollama / lemonade endpoint
- SmallModelName: "qwen3-4b-instruct",
- RewriteSystemPrompt: `You are an expert prompt engineer. Rewrite the user's raw request into a clear, well-structured, high-quality prompt that will produce better results from a larger coding/reasoning model.
- Keep the original intent. Make it more precise, add useful structure if helpful, and remove ambiguity.
- Return ONLY the improved prompt — no explanations.`,
- EnableLogging: true,
- TimeoutSeconds: 15,
- }
- // Override with config from Bifrost if provided
- if cfgMap, ok := cfg.(map[string]any); ok {
- if v, ok := cfgMap["small_model_endpoint"].(string); ok {
- config.SmallModelEndpoint = v
- }
- if v, ok := cfgMap["small_model_name"].(string); ok {
- config.SmallModelName = v
- }
- if v, ok := cfgMap["rewrite_system_prompt"].(string); ok {
- config.RewriteSystemPrompt = v
- }
- if v, ok := cfgMap["enable_logging"].(bool); ok {
- config.EnableLogging = v
- }
- }
- return nil
- }
- func GetName() string {
- return "prompt-rewriter"
- }
- func PreLLMHook(ctx *schemas.BifrostContext, req *schemas.BifrostRequest) (*schemas.BifrostRequest, *schemas.LLMPluginShortCircuit, error) {
- // Only act on chat requests that have messages
- if req.ChatRequest == nil || len(req.ChatRequest.Input) == 0 {
- return req, nil, nil
- }
- // Find the last user message
- var lastUserMsg *schemas.ChatMessage
- var lastUserIdx int
- for i := len(req.ChatRequest.Input) - 1; i >= 0; i-- {
- if req.ChatRequest.Input[i].Role == "user" {
- lastUserMsg = &req.ChatRequest.Input[i]
- lastUserIdx = i
- break
- }
- }
- if lastUserMsg == nil || lastUserMsg.Content == nil || lastUserMsg.Content.ContentStr == nil {
- return req, nil, nil
- }
- originalPrompt := *lastUserMsg.Content.ContentStr
- if strings.TrimSpace(originalPrompt) == "" {
- return req, nil, nil
- }
- if config.EnableLogging {
- ctx.Log(schemas.LogLevelInfo, fmt.Sprintf("[prompt-rewriter] Original prompt: %s", truncate(originalPrompt, 120)))
- }
- // Call the small model to rewrite the prompt
- improved, err := rewritePrompt(originalPrompt)
- if err != nil {
- ctx.Log(schemas.LogLevelWarn, fmt.Sprintf("[prompt-rewriter] Rewrite failed, using original: %v", err))
- return req, nil, nil // fail open — keep original prompt
- }
- if config.EnableLogging {
- ctx.Log(schemas.LogLevelInfo, fmt.Sprintf("[prompt-rewriter] Improved prompt: %s", truncate(improved, 120)))
- }
- // Replace the user message content with the improved version
- req.ChatRequest.Input[lastUserIdx].Content = &schemas.ChatMessageContent{
- ContentStr: &improved,
- }
- return req, nil, nil
- }
- func rewritePrompt(original string) (string, error) {
- payload := map[string]any{
- "model": config.SmallModelName,
- "messages": []map[string]string{
- {"role": "system", "content": config.RewriteSystemPrompt},
- {"role": "user", "content": original},
- },
- "temperature": 0.3,
- "max_tokens": 1024,
- }
- body, _ := json.Marshal(payload)
- client := &http.Client{Timeout: time.Duration(config.TimeoutSeconds) * time.Second}
- resp, err := client.Post(config.SmallModelEndpoint, "application/json", bytes.NewReader(body))
- if err != nil {
- return "", err
- }
- defer resp.Body.Close()
- if resp.StatusCode != 200 {
- b, _ := io.ReadAll(resp.Body)
- return "", fmt.Errorf("small model returned %d: %s", resp.StatusCode, string(b))
- }
- var result struct {
- Choices []struct {
- Message struct {
- Content string `json:"content"`
- } `json:"message"`
- } `json:"choices"`
- }
- if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
- return "", err
- }
- if len(result.Choices) == 0 {
- return "", fmt.Errorf("no choices returned")
- }
- return strings.TrimSpace(result.Choices[0].Message.Content), nil
- }
- func truncate(s string, max int) string {
- if len(s) <= max {
- return s
- }
- return s[:max] + "..."
- }
- // Required stubs
- func PreRequestHook(_ *schemas.BifrostContext, _ *schemas.BifrostRequest) error { return nil }
- func PostLLMHook(_ *schemas.BifrostContext, resp *schemas.BifrostResponse, err *schemas.BifrostError) (*schemas.BifrostResponse, *schemas.BifrostError, error) {
- return resp, err, nil
- }
- func Cleanup() error { return nil }
|