mirror of
https://github.com/danielmiessler/Fabric.git
synced 2026-01-09 14:28:01 -05:00
feat: add prompt strategies and improve installation documentation
## CHANGES - Add prompt strategies like Chain of Thought (CoT) - Implement strategy selection with `--strategy` flag - Improve README with platform-specific installation instructions - Fix web interface documentation link - Refactor git operations with new githelper package - Add `--liststrategies` command to view available strategies - Support applying strategies to system prompts - Fix YouTube configuration check - Improve error handling in session management
This commit is contained in:
121
README.md
121
README.md
@@ -34,13 +34,18 @@
|
||||
- [`fabric`](#fabric)
|
||||
- [Navigation](#navigation)
|
||||
- [Updates](#updates)
|
||||
- [Intro videos](#intro-videos)
|
||||
- [What and why](#what-and-why)
|
||||
- [Intro videos](#intro-videos)
|
||||
- [Philosophy](#philosophy)
|
||||
- [Breaking problems into components](#breaking-problems-into-components)
|
||||
- [Too many prompts](#too-many-prompts)
|
||||
- [Installation](#installation)
|
||||
- [Get Latest Release Binaries](#get-latest-release-binaries)
|
||||
- [Windows:](#windows)
|
||||
- [MacOS (arm64):](#macos-arm64)
|
||||
- [MacOS (amd64):](#macos-amd64)
|
||||
- [Linux (amd64):](#linux-amd64)
|
||||
- [Linux (arm64):](#linux-arm64)
|
||||
- [From Source](#from-source)
|
||||
- [Environment Variables](#environment-variables)
|
||||
- [Setup](#setup)
|
||||
@@ -52,12 +57,15 @@
|
||||
- [Our approach to prompting](#our-approach-to-prompting)
|
||||
- [Examples](#examples)
|
||||
- [Just use the Patterns](#just-use-the-patterns)
|
||||
- [Prompt Strategies](#prompt-strategies)
|
||||
- [Custom Patterns](#custom-patterns)
|
||||
- [Helper Apps](#helper-apps)
|
||||
- [`to_pdf`](#to_pdf)
|
||||
- [`to_pdf` Installation](#to_pdf-installation)
|
||||
- [pbpaste](#pbpaste)
|
||||
- [Web Interface](#Web_Interface)
|
||||
- [Web Interface](#web-interface)
|
||||
- [Installing](#installing)
|
||||
- [Streamlit UI](#streamlit-ui)
|
||||
- [Meta](#meta)
|
||||
- [Primary contributors](#primary-contributors)
|
||||
|
||||
@@ -391,48 +399,60 @@ Usage:
|
||||
fabric [OPTIONS]
|
||||
|
||||
Application Options:
|
||||
-p, --pattern= Choose a pattern from the available patterns
|
||||
-v, --variable= Values for pattern variables, e.g. -v=#role:expert -v=#points:30"
|
||||
-C, --context= Choose a context from the available contexts
|
||||
--session= Choose a session from the available sessions
|
||||
-a, --attachment= Attachment path or URL (e.g. for OpenAI image recognition messages)
|
||||
-S, --setup Run setup for all reconfigurable parts of fabric
|
||||
-t, --temperature= Set temperature (default: 0.7)
|
||||
-T, --topp= Set top P (default: 0.9)
|
||||
-s, --stream Stream
|
||||
-P, --presencepenalty= Set presence penalty (default: 0.0)
|
||||
-r, --raw Use the defaults of the model without sending chat options (like temperature etc.) and use the user role instead of the system role for patterns.
|
||||
-F, --frequencypenalty= Set frequency penalty (default: 0.0)
|
||||
-l, --listpatterns List all patterns
|
||||
-L, --listmodels List all available models
|
||||
-x, --listcontexts List all contexts
|
||||
-X, --listsessions List all sessions
|
||||
-U, --updatepatterns Update patterns
|
||||
-c, --copy Copy to clipboard
|
||||
-m, --model= Choose model
|
||||
-o, --output= Output to file
|
||||
--output-session Output the entire session (also a temporary one) to the output file
|
||||
-n, --latest= Number of latest patterns to list (default: 0)
|
||||
-d, --changeDefaultModel Change default model
|
||||
-y, --youtube= YouTube video "URL" to grab transcript, comments from it and send to chat
|
||||
--transcript Grab transcript from YouTube video and send to chat (it used per default).
|
||||
--comments Grab comments from YouTube video and send to chat
|
||||
--metadata Grab metadata from YouTube video and send to chat
|
||||
-g, --language= Specify the Language Code for the chat, e.g. -g=en -g=zh
|
||||
-u, --scrape_url= Scrape website URL to markdown using Jina AI
|
||||
-q, --scrape_question= Search question using Jina AI
|
||||
-e, --seed= Seed to be used for LMM generation
|
||||
-w, --wipecontext= Wipe context
|
||||
-W, --wipesession= Wipe session
|
||||
--printcontext= Print context
|
||||
--printsession= Print session
|
||||
--readability Convert HTML input into a clean, readable view
|
||||
--serve Initiate the API server
|
||||
--dry-run Show what would be sent to the model without actually sending it
|
||||
--version Print current version
|
||||
-p, --pattern= Choose a pattern from the available patterns
|
||||
-v, --variable= Values for pattern variables, e.g. -v=#role:expert -v=#points:30
|
||||
-C, --context= Choose a context from the available contexts
|
||||
--session= Choose a session from the available sessions
|
||||
-a, --attachment= Attachment path or URL (e.g. for OpenAI image recognition messages)
|
||||
-S, --setup Run setup for all reconfigurable parts of fabric
|
||||
-t, --temperature= Set temperature (default: 0.7)
|
||||
-T, --topp= Set top P (default: 0.9)
|
||||
-s, --stream Stream
|
||||
-P, --presencepenalty= Set presence penalty (default: 0.0)
|
||||
-r, --raw Use the defaults of the model without sending chat options (like temperature etc.) and use the user role instead of the system role for patterns.
|
||||
-F, --frequencypenalty= Set frequency penalty (default: 0.0)
|
||||
-l, --listpatterns List all patterns
|
||||
-L, --listmodels List all available models
|
||||
-x, --listcontexts List all contexts
|
||||
-X, --listsessions List all sessions
|
||||
-U, --updatepatterns Update patterns
|
||||
-c, --copy Copy to clipboard
|
||||
-m, --model= Choose model
|
||||
--modelContextLength= Model context length (only affects ollama)
|
||||
-o, --output= Output to file
|
||||
--output-session Output the entire session (also a temporary one) to the output file
|
||||
-n, --latest= Number of latest patterns to list (default: 0)
|
||||
-d, --changeDefaultModel Change default model
|
||||
-y, --youtube= YouTube video or play list "URL" to grab transcript, comments from it and send to chat or print it put to the console and store it in the output file
|
||||
--playlist Prefer playlist over video if both ids are present in the URL
|
||||
--transcript Grab transcript from YouTube video and send to chat (it is used per default).
|
||||
--transcript-with-timestamps Grab transcript from YouTube video with timestamps and send to chat
|
||||
--comments Grab comments from YouTube video and send to chat
|
||||
--metadata Output video metadata
|
||||
-g, --language= Specify the Language Code for the chat, e.g. -g=en -g=zh
|
||||
-u, --scrape_url= Scrape website URL to markdown using Jina AI
|
||||
-q, --scrape_question= Search question using Jina AI
|
||||
-e, --seed= Seed to be used for LMM generation
|
||||
-w, --wipecontext= Wipe context
|
||||
-W, --wipesession= Wipe session
|
||||
--printcontext= Print context
|
||||
--printsession= Print session
|
||||
--readability Convert HTML input into a clean, readable view
|
||||
--input-has-vars Apply variables to user input
|
||||
--dry-run Show what would be sent to the model without actually sending it
|
||||
--serve Serve the Fabric Rest API
|
||||
--serveOllama Serve the Fabric Rest API with ollama endpoints
|
||||
--address= The address to bind the REST API (default: :8080)
|
||||
--config= Path to YAML config file
|
||||
--version Print current version
|
||||
--listextensions List all registered extensions
|
||||
--addextension= Register a new extension from config file path
|
||||
--rmextension= Remove a registered extension by name
|
||||
--strategy= Choose a strategy from the available strategies
|
||||
--liststrategies List all strategies
|
||||
|
||||
Help Options:
|
||||
-h, --help Show this help message
|
||||
-h, --help Show this help message
|
||||
|
||||
```
|
||||
|
||||
@@ -503,6 +523,21 @@ You can use any of the Patterns you see there in any AI application that you hav
|
||||
|
||||
The wisdom of crowds for the win.
|
||||
|
||||
### Prompt Strategies
|
||||
|
||||
Fabric also implements prompt strategies like "Chain of Thought" or "Chain of Draft" which can
|
||||
be used in addition to the basic patterns.
|
||||
|
||||
See the [Thinking Faster by Writing Less](https://arxiv.org/pdf/2502.18600) paper and
|
||||
the [Thought Generation section of Learn Prompting](https://learnprompting.org/docs/advanced/thought_generation/introduction) for examples of prompt strategies.
|
||||
|
||||
Each strategy is available as a small `json` file in the [`/strategies`](https://github.com/danielmiessler/fabric/tree/main/strategies) directory.
|
||||
|
||||
The prompt modification of the strategy is applied to the system prompt and passed on to the
|
||||
LLM in the chat session.
|
||||
|
||||
Use `fabric -S` and select the option to install the strategies in your `~/.config/fabric` directory.
|
||||
|
||||
## Custom Patterns
|
||||
|
||||
You may want to use Fabric to create your own custom Patterns—but not share them with others. No problem!
|
||||
@@ -575,7 +610,7 @@ alias pbpaste='xclip -selection clipboard -o'
|
||||
|
||||
## Web Interface
|
||||
|
||||
Fabric now includes a built-in web interface that provides a GUI alternative to the command-line interface and an out-of-the-box website for those who want to get started with web development or blogging.
|
||||
Fabric now includes a built-in web interface that provides a GUI alternative to the command-line interface and an out-of-the-box website for those who want to get started with web development or blogging.
|
||||
You can use this app as a GUI interface for Fabric, a ready to go blog-site, or a website template for your own projects.
|
||||
|
||||
The `web/src/lib/content` directory includes starter `.obsidian/` and `templates/` directories, allowing you to open up the `web/src/lib/content/` directory as an [Obsidian.md](https://obsidian.md) vault. You can place your posts in the posts directory when you're ready to publish.
|
||||
|
||||
@@ -156,6 +156,11 @@ func Cli(version string) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
if currentFlags.ListStrategies {
|
||||
err = registry.Strategies.ListStrategies()
|
||||
return
|
||||
}
|
||||
|
||||
// if the interactive flag is set, run the interactive function
|
||||
// if currentFlags.Interactive {
|
||||
// interactive.Interactive()
|
||||
@@ -166,7 +171,7 @@ func Cli(version string) (err error) {
|
||||
var messageTools string
|
||||
|
||||
if currentFlags.YouTube != "" {
|
||||
if registry.YouTube.IsConfigured() == false {
|
||||
if !registry.YouTube.IsConfigured() {
|
||||
err = fmt.Errorf("YouTube is not configured, please run the setup procedure")
|
||||
return
|
||||
}
|
||||
@@ -241,7 +246,7 @@ func Cli(version string) (err error) {
|
||||
}
|
||||
|
||||
var chatter *core.Chatter
|
||||
if chatter, err = registry.GetChatter(currentFlags.Model, currentFlags.ModelContextLength, currentFlags.Stream, currentFlags.DryRun); err != nil {
|
||||
if chatter, err = registry.GetChatter(currentFlags.Model, currentFlags.ModelContextLength, currentFlags.Strategy, currentFlags.Stream, currentFlags.DryRun); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -69,6 +69,8 @@ type Flags struct {
|
||||
ListExtensions bool `long:"listextensions" description:"List all registered extensions"`
|
||||
AddExtension string `long:"addextension" description:"Register a new extension from config file path"`
|
||||
RemoveExtension string `long:"rmextension" description:"Remove a registered extension by name"`
|
||||
Strategy string `long:"strategy" description:"Choose a strategy from the available strategies" default:""`
|
||||
ListStrategies bool `long:"liststrategies" description:"List all strategies"`
|
||||
}
|
||||
|
||||
var debug = false
|
||||
@@ -267,13 +269,14 @@ func (o *Flags) BuildChatRequest(Meta string) (ret *common.ChatRequest, err erro
|
||||
ContextName: o.Context,
|
||||
SessionName: o.Session,
|
||||
PatternName: o.Pattern,
|
||||
StrategyName: o.Strategy,
|
||||
PatternVariables: o.PatternVariables,
|
||||
InputHasVars: o.InputHasVars,
|
||||
Meta: Meta,
|
||||
}
|
||||
|
||||
var message *goopenai.ChatCompletionMessage
|
||||
if o.Attachments == nil || len(o.Attachments) == 0 {
|
||||
if len(o.Attachments) == 0 {
|
||||
if o.Message != "" {
|
||||
message = &goopenai.ChatCompletionMessage{
|
||||
Role: goopenai.ChatMessageRoleUser,
|
||||
|
||||
@@ -13,6 +13,7 @@ type ChatRequest struct {
|
||||
Language string
|
||||
Meta string
|
||||
InputHasVars bool
|
||||
StrategyName string
|
||||
}
|
||||
|
||||
type ChatOptions struct {
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/danielmiessler/fabric/common"
|
||||
"github.com/danielmiessler/fabric/plugins/ai"
|
||||
"github.com/danielmiessler/fabric/plugins/db/fsdb"
|
||||
"github.com/danielmiessler/fabric/plugins/strategy"
|
||||
"github.com/danielmiessler/fabric/plugins/template"
|
||||
)
|
||||
|
||||
@@ -24,6 +25,7 @@ type Chatter struct {
|
||||
model string
|
||||
modelContextLength int
|
||||
vendor ai.Vendor
|
||||
strategy string
|
||||
}
|
||||
|
||||
func (o *Chatter) Send(request *common.ChatRequest, opts *common.ChatOptions) (session *fsdb.Session, err error) {
|
||||
@@ -35,6 +37,9 @@ func (o *Chatter) Send(request *common.ChatRequest, opts *common.ChatOptions) (s
|
||||
if len(vendorMessages) == 0 {
|
||||
if session.Name != "" {
|
||||
err = o.db.Sessions.SaveSession(session)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
err = fmt.Errorf("no messages provided")
|
||||
return
|
||||
@@ -140,6 +145,19 @@ func (o *Chatter) BuildSession(request *common.ChatRequest, raw bool) (session *
|
||||
}
|
||||
|
||||
systemMessage := strings.TrimSpace(contextContent) + strings.TrimSpace(patternContent)
|
||||
|
||||
// Apply strategy if specified
|
||||
if request.StrategyName != "" {
|
||||
strategy, err := strategy.LoadStrategy(request.StrategyName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not load strategy %s: %v", request.StrategyName, err)
|
||||
}
|
||||
if strategy != nil && strategy.Prompt != "" {
|
||||
// prepend the strategy prompt to the system message
|
||||
systemMessage = fmt.Sprintf("%s\n%s", strategy.Prompt, systemMessage)
|
||||
}
|
||||
}
|
||||
|
||||
if request.Language != "" {
|
||||
systemMessage = fmt.Sprintf("%s. Please use the language '%s' for the output.", systemMessage, request.Language)
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"strconv"
|
||||
|
||||
"github.com/danielmiessler/fabric/plugins/ai/exolab"
|
||||
"github.com/danielmiessler/fabric/plugins/strategy"
|
||||
|
||||
"github.com/samber/lo"
|
||||
|
||||
@@ -44,6 +45,7 @@ func NewPluginRegistry(db *fsdb.Db) (ret *PluginRegistry, err error) {
|
||||
YouTube: youtube.NewYouTube(),
|
||||
Language: lang.NewLanguage(),
|
||||
Jina: jina.NewClient(),
|
||||
Strategies: strategy.NewStrategiesManager(),
|
||||
}
|
||||
|
||||
var homedir string
|
||||
@@ -86,6 +88,7 @@ type PluginRegistry struct {
|
||||
Language *lang.Language
|
||||
Jina *jina.Client
|
||||
TemplateExtensions *template.ExtensionManager
|
||||
Strategies *strategy.StrategiesManager
|
||||
}
|
||||
|
||||
func (o *PluginRegistry) SaveEnvFile() (err error) {
|
||||
@@ -94,6 +97,7 @@ func (o *PluginRegistry) SaveEnvFile() (err error) {
|
||||
|
||||
o.Defaults.Settings.FillEnvFileContent(&envFileContent)
|
||||
o.PatternsLoader.SetupFillEnvFileContent(&envFileContent)
|
||||
o.Strategies.SetupFillEnvFileContent(&envFileContent)
|
||||
|
||||
for _, vendor := range o.VendorManager.Vendors {
|
||||
vendor.SetupFillEnvFileContent(&envFileContent)
|
||||
@@ -109,7 +113,7 @@ func (o *PluginRegistry) SaveEnvFile() (err error) {
|
||||
|
||||
func (o *PluginRegistry) Setup() (err error) {
|
||||
setupQuestion := plugins.NewSetupQuestion("Enter the number of the plugin to setup")
|
||||
groupsPlugins := common.NewGroupsItemsSelector[plugins.Plugin]("Available plugins (please configure all required plugins):",
|
||||
groupsPlugins := common.NewGroupsItemsSelector("Available plugins (please configure all required plugins):",
|
||||
func(plugin plugins.Plugin) string {
|
||||
var configuredLabel string
|
||||
if plugin.IsConfigured() {
|
||||
@@ -125,7 +129,7 @@ func (o *PluginRegistry) Setup() (err error) {
|
||||
return vendor
|
||||
})...)
|
||||
|
||||
groupsPlugins.AddGroupItems("Tools", o.Defaults, o.PatternsLoader, o.YouTube, o.Language, o.Jina)
|
||||
groupsPlugins.AddGroupItems("Tools", o.Defaults, o.PatternsLoader, o.YouTube, o.Language, o.Jina, o.Strategies)
|
||||
|
||||
for {
|
||||
groupsPlugins.Print()
|
||||
@@ -206,7 +210,7 @@ func (o *PluginRegistry) Configure() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (o *PluginRegistry) GetChatter(model string, modelContextLength int, stream bool, dryRun bool) (ret *Chatter, err error) {
|
||||
func (o *PluginRegistry) GetChatter(model string, modelContextLength int, strategy string, stream bool, dryRun bool) (ret *Chatter, err error) {
|
||||
ret = &Chatter{
|
||||
db: o.Db,
|
||||
Stream: stream,
|
||||
@@ -258,5 +262,6 @@ func (o *PluginRegistry) GetChatter(model string, modelContextLength int, stream
|
||||
model, defaultModel, defaultVendor, errMsg)
|
||||
return
|
||||
}
|
||||
ret.strategy = strategy
|
||||
return
|
||||
}
|
||||
|
||||
242
plugins/strategy/strategy.go
Normal file
242
plugins/strategy/strategy.go
Normal file
@@ -0,0 +1,242 @@
|
||||
package strategy
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/danielmiessler/fabric/plugins"
|
||||
"github.com/danielmiessler/fabric/plugins/tools/githelper"
|
||||
)
|
||||
|
||||
const DefaultStrategiesGitRepoUrl = "https://github.com/danielmiessler/fabric.git"
|
||||
const DefaultStrategiesGitRepoFolder = "strategies"
|
||||
|
||||
func NewStrategiesManager() (sm *StrategiesManager) {
|
||||
label := "Prompt Strategies"
|
||||
strategies, err := LoadAllFiles()
|
||||
if err != nil {
|
||||
strategies = make(map[string]Strategy) // empty map
|
||||
}
|
||||
sm = &StrategiesManager{
|
||||
Strategies: strategies,
|
||||
}
|
||||
sm.PluginBase = &plugins.PluginBase{
|
||||
Name: label,
|
||||
SetupDescription: "Strategies - Downloads Prompting Strategies (like chain of thought) [optional]",
|
||||
EnvNamePrefix: plugins.BuildEnvVariablePrefix(label),
|
||||
ConfigureCustom: sm.configure,
|
||||
}
|
||||
|
||||
sm.DefaultGitRepoUrl = sm.AddSetupQuestionCustom("Git Repo Url", true,
|
||||
"Enter the default Git repository URL for the strategies")
|
||||
sm.DefaultGitRepoUrl.Value = DefaultStrategiesGitRepoUrl
|
||||
|
||||
sm.DefaultFolder = sm.AddSetupQuestionCustom("Git Repo Strategies Folder", true,
|
||||
"Enter the default folder in the Git repository where strategies are stored")
|
||||
sm.DefaultFolder.Value = DefaultStrategiesGitRepoFolder
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type StrategiesManager struct {
|
||||
*plugins.PluginBase
|
||||
Strategies map[string]Strategy
|
||||
|
||||
DefaultGitRepoUrl *plugins.SetupQuestion
|
||||
DefaultFolder *plugins.SetupQuestion
|
||||
}
|
||||
|
||||
type Strategy struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Prompt string `json:"prompt"`
|
||||
}
|
||||
|
||||
func LoadAllFiles() (strategies map[string]Strategy, err error) {
|
||||
strategies = make(map[string]Strategy)
|
||||
strategyDir, err := getStrategyDir()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
filepath.WalkDir(strategyDir, func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if d.IsDir() && path != strategyDir {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
|
||||
if filepath.Ext(path) == ".json" {
|
||||
strategyName := strings.TrimSuffix(filepath.Base(path), ".json")
|
||||
strategy, err := LoadStrategy(strategyName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
strategies[strategy.Name] = *strategy
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
func (sm *StrategiesManager) IsConfigured() (ret bool) {
|
||||
ret = sm.PluginBase.IsConfigured()
|
||||
if ret {
|
||||
if len(sm.Strategies) == 0 {
|
||||
ret = false
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (sm *StrategiesManager) Setup() (err error) {
|
||||
if err = sm.PluginBase.Setup(); err != nil {
|
||||
return
|
||||
}
|
||||
if err = sm.PopulateDB(); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// PopulateDB downloads strategies from the internet and populates the strategies folder
|
||||
func (sm *StrategiesManager) PopulateDB() (err error) {
|
||||
fmt.Printf("Downloading strategies and Populating %s...\n", sm.DefaultFolder.Value)
|
||||
fmt.Println()
|
||||
if err = sm.gitCloneAndCopy(); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (sm *StrategiesManager) gitCloneAndCopy() (err error) {
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("could not get home directory: %v", err)
|
||||
return
|
||||
}
|
||||
strategyDir := filepath.Join(homeDir, ".config", "fabric", "strategies")
|
||||
|
||||
// Create the directory if it doesn't exist
|
||||
if err = os.MkdirAll(strategyDir, os.ModePerm); err != nil {
|
||||
return fmt.Errorf("failed to create strategies directory: %w", err)
|
||||
}
|
||||
|
||||
// Use the helper to fetch files
|
||||
err = githelper.FetchFilesFromRepo(githelper.FetchOptions{
|
||||
RepoURL: sm.DefaultGitRepoUrl.Value,
|
||||
PathPrefix: sm.DefaultFolder.Value,
|
||||
DestDir: strategyDir,
|
||||
SingleDirectory: true,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to download strategies: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sm *StrategiesManager) configure() (err error) {
|
||||
sm.Strategies, err = LoadAllFiles()
|
||||
return
|
||||
}
|
||||
|
||||
// getStrategyDir returns the path to the strategies directory
|
||||
func getStrategyDir() (ret string, err error) {
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("could not get home directory: %v, using current directory instead", err)
|
||||
ret = filepath.Join(".", "strategies")
|
||||
return
|
||||
}
|
||||
|
||||
strategiesDir := filepath.Join(homeDir, ".config", "fabric", "strategies")
|
||||
|
||||
// Check if directory exists, if not use local strategies directory
|
||||
if _, err := os.Stat(strategiesDir); os.IsNotExist(err) {
|
||||
strategiesDir = filepath.Join(".", "strategies")
|
||||
}
|
||||
|
||||
if _, err := os.Stat(strategiesDir); os.IsNotExist(err) {
|
||||
return "", err
|
||||
}
|
||||
ret = strategiesDir
|
||||
return
|
||||
}
|
||||
|
||||
// LoadStrategy loads a strategy from the given name
|
||||
func LoadStrategy(filename string) (*Strategy, error) {
|
||||
if filename == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Get the strategy directory path
|
||||
strategyDir, err := getStrategyDir()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// First try with .json extension
|
||||
strategyPath := filepath.Join(strategyDir, filename+".json")
|
||||
if _, err := os.Stat(strategyPath); os.IsNotExist(err) {
|
||||
// Try without extension
|
||||
strategyPath = filepath.Join(strategyDir, filename)
|
||||
if _, err := os.Stat(strategyPath); os.IsNotExist(err) {
|
||||
return nil, fmt.Errorf("strategy %s not found. Please run 'fabric --liststrategies' for list", filename)
|
||||
}
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(strategyPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var strategy Strategy
|
||||
if err := json.Unmarshal(data, &strategy); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
strategy.Name = strings.TrimSuffix(filepath.Base(strategyPath), ".json")
|
||||
|
||||
return &strategy, nil
|
||||
}
|
||||
|
||||
// ListStrategies prints available strategies
|
||||
func (sm *StrategiesManager) ListStrategies() error {
|
||||
if len(sm.Strategies) == 0 {
|
||||
return fmt.Errorf("no strategies found. Please run 'fabric setup' to download strategies")
|
||||
}
|
||||
fmt.Print("Available Strategies:\n\n")
|
||||
|
||||
// Get all strategy names for sorting
|
||||
names := []string{}
|
||||
for name := range sm.Strategies {
|
||||
names = append(names, name)
|
||||
}
|
||||
|
||||
// Sort the strategy names alphabetically
|
||||
sort.Strings(names)
|
||||
|
||||
// Find the longest name to align descriptions
|
||||
maxNameLength := 0
|
||||
for _, name := range names {
|
||||
if len(name) > maxNameLength {
|
||||
maxNameLength = len(name)
|
||||
}
|
||||
}
|
||||
|
||||
// Print each strategy with its description aligned
|
||||
formatString := "%-" + fmt.Sprintf("%d", maxNameLength+2) + "s %s\n"
|
||||
for _, name := range names {
|
||||
strategy := sm.Strategies[name]
|
||||
fmt.Printf(formatString, strategy.Name, strategy.Description)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
111
plugins/tools/githelper/githelper.go
Normal file
111
plugins/tools/githelper/githelper.go
Normal file
@@ -0,0 +1,111 @@
|
||||
package githelper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/go-git/go-git/v5"
|
||||
"github.com/go-git/go-git/v5/plumbing/object"
|
||||
"github.com/go-git/go-git/v5/storage/memory"
|
||||
)
|
||||
|
||||
// FetchOptions defines options for fetching files from a git repo
|
||||
type FetchOptions struct {
|
||||
// RepoURL is the URL of the git repository
|
||||
RepoURL string
|
||||
|
||||
// PathPrefix is the folder within the repo to extract (e.g. "patterns/")
|
||||
PathPrefix string
|
||||
|
||||
// DestDir is where the files will be saved locally
|
||||
DestDir string
|
||||
|
||||
// SingleDirectory if true, only fetch files directly in the specified directory
|
||||
// without recursing into subdirectories
|
||||
SingleDirectory bool
|
||||
}
|
||||
|
||||
// FetchFilesFromRepo clones a git repo and extracts files from a specific folder
|
||||
func FetchFilesFromRepo(opts FetchOptions) error {
|
||||
// Ensure path prefix ends with slash
|
||||
if !strings.HasSuffix(opts.PathPrefix, "/") {
|
||||
opts.PathPrefix = opts.PathPrefix + "/"
|
||||
}
|
||||
|
||||
// Clone the repository in memory
|
||||
r, err := git.Clone(memory.NewStorage(), nil, &git.CloneOptions{
|
||||
URL: opts.RepoURL,
|
||||
Depth: 1,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to clone repository: %w", err)
|
||||
}
|
||||
|
||||
// Get HEAD reference
|
||||
ref, err := r.Head()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get repository HEAD: %w", err)
|
||||
}
|
||||
|
||||
// Get commit object
|
||||
commit, err := r.CommitObject(ref.Hash())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get commit: %w", err)
|
||||
}
|
||||
|
||||
// Get the file tree
|
||||
tree, err := commit.Tree()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get tree: %w", err)
|
||||
}
|
||||
|
||||
// Ensure destination directory exists
|
||||
if err := os.MkdirAll(opts.DestDir, 0755); err != nil {
|
||||
return fmt.Errorf("failed to create destination directory: %w", err)
|
||||
}
|
||||
|
||||
// Extract files from the tree
|
||||
return tree.Files().ForEach(func(f *object.File) error {
|
||||
// Only process files in the specified path
|
||||
if !strings.HasPrefix(f.Name, opts.PathPrefix) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// For SingleDirectory mode, skip files in subdirectories
|
||||
if opts.SingleDirectory {
|
||||
remainingPath := strings.TrimPrefix(f.Name, opts.PathPrefix)
|
||||
if strings.Contains(remainingPath, "/") {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Create local path for the file, removing the prefix
|
||||
relativePath := strings.TrimPrefix(f.Name, opts.PathPrefix)
|
||||
localPath := filepath.Join(opts.DestDir, relativePath)
|
||||
|
||||
// Ensure directory structure exists
|
||||
if err := os.MkdirAll(filepath.Dir(localPath), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get file contents
|
||||
reader, err := f.Reader()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer reader.Close()
|
||||
|
||||
// Create and write to local file
|
||||
file, err := os.Create(localPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
_, err = io.Copy(file, reader)
|
||||
return err
|
||||
})
|
||||
}
|
||||
@@ -2,19 +2,13 @@ package tools
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/danielmiessler/fabric/plugins"
|
||||
"github.com/danielmiessler/fabric/plugins/db/fsdb"
|
||||
"github.com/danielmiessler/fabric/plugins/tools/githelper"
|
||||
|
||||
"github.com/go-git/go-git/v5"
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
"github.com/go-git/go-git/v5/plumbing/object"
|
||||
"github.com/go-git/go-git/v5/storage/memory"
|
||||
"github.com/otiai10/copy"
|
||||
)
|
||||
|
||||
@@ -89,7 +83,7 @@ func (o *PatternsLoader) Setup() (err error) {
|
||||
|
||||
// PopulateDB downloads patterns from the internet and populates the patterns folder
|
||||
func (o *PatternsLoader) PopulateDB() (err error) {
|
||||
fmt.Printf("Downloading patterns and Populating %s..\n", o.Patterns.Dir)
|
||||
fmt.Printf("Downloading patterns and Populating %s...\n", o.Patterns.Dir)
|
||||
fmt.Println()
|
||||
if err = o.gitCloneAndCopy(); err != nil {
|
||||
return
|
||||
@@ -148,156 +142,20 @@ func (o *PatternsLoader) movePatterns() (err error) {
|
||||
}
|
||||
|
||||
func (o *PatternsLoader) gitCloneAndCopy() (err error) {
|
||||
// Clones the given repository, creating the remote, the local branches
|
||||
// and fetching the objects, everything in memory:
|
||||
var r *git.Repository
|
||||
if r, err = git.Clone(memory.NewStorage(), nil, &git.CloneOptions{
|
||||
URL: o.DefaultGitRepoUrl.Value,
|
||||
}); err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
// Create temp folder if it doesn't exist
|
||||
if err = os.MkdirAll(filepath.Dir(o.tempPatternsFolder), os.ModePerm); err != nil {
|
||||
return fmt.Errorf("failed to create temp directory: %w", err)
|
||||
}
|
||||
|
||||
// ... retrieves the branch pointed by HEAD
|
||||
var ref *plumbing.Reference
|
||||
if ref, err = r.Head(); err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
// ... retrieves the commit history for /patterns folder
|
||||
var cIter object.CommitIter
|
||||
if cIter, err = r.Log(&git.LogOptions{
|
||||
From: ref.Hash(),
|
||||
PathFilter: func(path string) bool {
|
||||
return path == o.DefaultFolder.Value || strings.HasPrefix(path, o.pathPatternsPrefix)
|
||||
},
|
||||
}); err != nil {
|
||||
fmt.Println(err)
|
||||
return err
|
||||
}
|
||||
|
||||
var changes []fsdb.DirectoryChange
|
||||
// ... iterates over the commits
|
||||
if err = cIter.ForEach(func(c *object.Commit) (err error) {
|
||||
// GetApplyVariables the files changed in this commit by comparing with its parents
|
||||
parentIter := c.Parents()
|
||||
if err = parentIter.ForEach(func(parent *object.Commit) (err error) {
|
||||
var patch *object.Patch
|
||||
if patch, err = parent.Patch(c); err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, fileStat := range patch.Stats() {
|
||||
if strings.HasPrefix(fileStat.Name, o.pathPatternsPrefix) {
|
||||
dir := filepath.Dir(fileStat.Name)
|
||||
changes = append(changes, fsdb.DirectoryChange{Dir: dir, Timestamp: c.Committer.When})
|
||||
}
|
||||
}
|
||||
return
|
||||
}); err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
return
|
||||
}); err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Sort changes by timestamp
|
||||
sort.Slice(changes, func(i, j int) bool {
|
||||
return changes[i].Timestamp.Before(changes[j].Timestamp)
|
||||
// Use the helper to fetch files
|
||||
err = githelper.FetchFilesFromRepo(githelper.FetchOptions{
|
||||
RepoURL: o.DefaultGitRepoUrl.Value,
|
||||
PathPrefix: o.DefaultFolder.Value,
|
||||
DestDir: o.tempPatternsFolder,
|
||||
})
|
||||
|
||||
if err = o.makeUniqueList(changes); err != nil {
|
||||
return
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to download patterns: %w", err)
|
||||
}
|
||||
|
||||
var commit *object.Commit
|
||||
if commit, err = r.CommitObject(ref.Hash()); err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
var tree *object.Tree
|
||||
if tree, err = commit.Tree(); err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = tree.Files().ForEach(func(f *object.File) (err error) {
|
||||
if strings.HasPrefix(f.Name, o.pathPatternsPrefix) {
|
||||
// Create the local file path
|
||||
localPath := filepath.Join(os.TempDir(), f.Name)
|
||||
|
||||
// Create the directories if they don't exist
|
||||
if err = os.MkdirAll(filepath.Dir(localPath), os.ModePerm); err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Write the file to the local filesystem
|
||||
var blob *object.Blob
|
||||
if blob, err = r.BlobObject(f.Hash); err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
err = o.writeBlobToFile(blob, localPath)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (o *PatternsLoader) writeBlobToFile(blob *object.Blob, path string) (err error) {
|
||||
var reader io.ReadCloser
|
||||
if reader, err = blob.Reader(); err != nil {
|
||||
return
|
||||
}
|
||||
defer reader.Close()
|
||||
|
||||
// Create the file
|
||||
var file *os.File
|
||||
if file, err = os.Create(path); err != nil {
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// Copy the contents of the blob to the file
|
||||
if _, err = io.Copy(file, reader); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *PatternsLoader) makeUniqueList(changes []fsdb.DirectoryChange) (err error) {
|
||||
uniqueItems := make(map[string]bool)
|
||||
for _, change := range changes {
|
||||
if strings.TrimSpace(change.Dir) != "" && !strings.Contains(change.Dir, "=>") {
|
||||
pattern := strings.ReplaceAll(change.Dir, o.pathPatternsPrefix, "")
|
||||
pattern = strings.TrimSpace(pattern)
|
||||
uniqueItems[pattern] = true
|
||||
}
|
||||
}
|
||||
|
||||
finalList := make([]string, 0, len(uniqueItems))
|
||||
for _, change := range changes {
|
||||
pattern := strings.ReplaceAll(change.Dir, o.pathPatternsPrefix, "")
|
||||
pattern = strings.TrimSpace(pattern)
|
||||
if _, exists := uniqueItems[pattern]; exists {
|
||||
finalList = append(finalList, pattern)
|
||||
delete(uniqueItems, pattern) // Remove to avoid duplicates in the final list
|
||||
}
|
||||
}
|
||||
|
||||
joined := strings.Join(finalList, "\n")
|
||||
err = os.WriteFile(o.Patterns.UniquePatternsFilePath, []byte(joined), 0o644)
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ func (h *ChatHandler) HandleChat(c *gin.Context) {
|
||||
go func(p PromptRequest) {
|
||||
defer close(streamChan)
|
||||
|
||||
chatter, err := h.registry.GetChatter(p.Model, 2048, false, false)
|
||||
chatter, err := h.registry.GetChatter(p.Model, 2048, "", false, false)
|
||||
if err != nil {
|
||||
log.Printf("Error creating chatter: %v", err)
|
||||
streamChan <- fmt.Sprintf("Error: %v", err)
|
||||
|
||||
4
strategies/cod.json
Normal file
4
strategies/cod.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"description": "Chain-of-Draft (CoD) Prompting",
|
||||
"prompt": "Think step by step, keeping a minimal draft (5 words max) for each step. Return the final answer in the required format."
|
||||
}
|
||||
4
strategies/cot.json
Normal file
4
strategies/cot.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"description": "Chain-of-Thought (CoT) Prompting",
|
||||
"prompt": "Think step by step to answer the question. Return the final answer in the required format."
|
||||
}
|
||||
4
strategies/ltm.json
Normal file
4
strategies/ltm.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"description": "Least-to-Most Prompting",
|
||||
"prompt": "Break down the problem into simpler sub-problems from easiest to hardest; answer concisely at each step."
|
||||
}
|
||||
4
strategies/reflexion.json
Normal file
4
strategies/reflexion.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"description": "Reflexion Prompting",
|
||||
"prompt": "Answer concisely, critique your reasoning briefly, and provide a refined answer."
|
||||
}
|
||||
4
strategies/self-consistent.json
Normal file
4
strategies/self-consistent.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"description": "Self-Consistency Prompting",
|
||||
"prompt": "Provide multiple reasoning paths and select the most consistent answer."
|
||||
}
|
||||
4
strategies/self-refine.json
Normal file
4
strategies/self-refine.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"description": "Self-Refinement",
|
||||
"prompt": "Provide an initial concise answer, critique it briefly, and refine if necessary."
|
||||
}
|
||||
4
strategies/standard.json
Normal file
4
strategies/standard.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"description": "Standard Prompting",
|
||||
"prompt": "Answer the question directly without any explanation or reasoning."
|
||||
}
|
||||
4
strategies/tot.json
Normal file
4
strategies/tot.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"description": "Tree-of-Thought (ToT) Prompting",
|
||||
"prompt": "Generate multiple reasoning paths briefly and select the best one."
|
||||
}
|
||||
Reference in New Issue
Block a user