Compare commits

...

14 Commits

Author SHA1 Message Date
github-actions[bot]
ce9d4ad831 Update version to v1.4.159 and commit 2025-03-16 19:23:15 +00:00
Daniel Miessler
657bcab48c Added flashcard generator. 2025-03-16 12:21:50 -07:00
github-actions[bot]
cd11dcc7a9 Update version to v1.4.158 and commit 2025-03-16 17:26:52 +00:00
Eugen Eisler
22040a42f2 Merge pull request #1367 from ksylvan/fix/code_cleanup
Remove Generic Type Parameters from StorageHandler Initialization
2025-03-16 18:25:33 +01:00
Kayvan Sylvan
705ccd750b refactor: remove generic type parameters from NewStorageHandler calls
## CHANGES

- Remove explicit type parameters from StorageHandler initialization
- Update contexts handler constructor implementation
- Update patterns handler constructor implementation
- Update sessions handler constructor implementation
- Simplify API by relying on type inference
2025-03-16 09:21:17 -07:00
github-actions[bot]
db7c2b70cb Update version to v1.4.157 and commit 2025-03-16 07:36:39 +00:00
Eugen Eisler
9dc9bfa1d5 Merge pull request #1365 from ksylvan/feature/strategies
Implement Prompt Strategies in Fabric
2025-03-16 08:35:23 +01:00
Kayvan Sylvan
6b93658191 chore: remove redundant yt function definition 2025-03-15 17:35:45 -07:00
Kayvan Sylvan
ea7a425a26 add newline to end of cod.json 2025-03-15 13:18:53 -07:00
Kayvan Sylvan
9582978adb Fix help message when no strategies found. 2025-03-15 13:05:51 -07:00
Kayvan Sylvan
453d8e75e4 fix: fix handling of the installed strategies dir 2025-03-15 09:36:34 -07:00
Kayvan Sylvan
901a010efd chore: remove fallback to local strategies directory if missing 2025-03-15 08:29:58 -07:00
Kayvan Sylvan
f744e25b39 change [optional] to [required] in strategies 2025-03-15 00:33:58 -07:00
Kayvan Sylvan
096f40df68 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
2025-03-15 00:30:30 -07:00
25 changed files with 526 additions and 215 deletions

126
README.md
View File

@@ -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)
@@ -341,11 +349,6 @@ for pattern_file in ~/.config/fabric/patterns/*; do
}
"
done
yt() {
local video_link="$1"
fabric -y "$video_link" --transcript
}
```
This will allow you to use the patterns as aliases like in the above for example `summarize` instead of `fabric --pattern summarize --stream`, however if you pass in an extra argument like this `summarize "my_article_title"` your output will be saved in the destination that you set in `obsidian_base="/path/to/obsidian"` in the following format `YYYY-MM-DD-my_article_title.md` where the date gets autogenerated for you.
@@ -391,48 +394,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 +518,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 +605,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.

View File

@@ -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
}

View File

@@ -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,

View File

@@ -13,6 +13,7 @@ type ChatRequest struct {
Language string
Meta string
InputHasVars bool
StrategyName string
}
type ChatOptions struct {

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -1 +1 @@
"1.4.156"
"1.4.159"

14
patterns/system.md Normal file
View File

@@ -0,0 +1,14 @@
# IDENTITY
You are an expert educator AI with a 4,221 IQ. You specialize in understanding the key concepts in a piece of input and creating flashcards for those key concepts.
# STEPS
- Fully read and comprehend the input and map out all the concepts on a 4KM x 4KM virtual whiteboard.
- Make a list of the key concepts, definitions, terms, etc. that are associated with the input.
- Create flashcards for each key concept, definition, term, etc. that you have identified.
- The flashcard should be a question of 8-16 words and an answer of up to 32 words.
# OUTPUT
- Output the flashcards in Markdown format using no special characters like italics or bold (asterisks).

View File

@@ -0,0 +1,231 @@
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) [required]",
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) {
stageDir, _ := getStrategyDir()
fmt.Printf("Downloading strategies and Populating %s...\n", stageDir)
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
}
return filepath.Join(homeDir, ".config", "fabric", "strategies"), nil
}
// 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
}

View 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
})
}

View File

@@ -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
}

View File

@@ -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)

View File

@@ -14,6 +14,6 @@ type ContextsHandler struct {
// NewContextsHandler creates a new ContextsHandler
func NewContextsHandler(r *gin.Engine, contexts *fsdb.ContextsEntity) (ret *ContextsHandler) {
ret = &ContextsHandler{
StorageHandler: NewStorageHandler[fsdb.Context](r, "contexts", contexts), contexts: contexts}
StorageHandler: NewStorageHandler(r, "contexts", contexts), contexts: contexts}
return
}

View File

@@ -16,7 +16,7 @@ type PatternsHandler struct {
// NewPatternsHandler creates a new PatternsHandler
func NewPatternsHandler(r *gin.Engine, patterns *fsdb.PatternsEntity) (ret *PatternsHandler) {
ret = &PatternsHandler{
StorageHandler: NewStorageHandler[fsdb.Pattern](r, "patterns", patterns), patterns: patterns}
StorageHandler: NewStorageHandler(r, "patterns", patterns), patterns: patterns}
// TODO: Add custom, replacement routes here
//r.GET("/patterns/:name", ret.Get)

View File

@@ -14,6 +14,6 @@ type SessionsHandler struct {
// NewSessionsHandler creates a new SessionsHandler
func NewSessionsHandler(r *gin.Engine, sessions *fsdb.SessionsEntity) (ret *SessionsHandler) {
ret = &SessionsHandler{
StorageHandler: NewStorageHandler[fsdb.Session](r, "sessions", sessions), sessions: sessions}
StorageHandler: NewStorageHandler(r, "sessions", sessions), sessions: sessions}
return ret
}

4
strategies/cod.json Normal file
View 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
View 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
View 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."
}

View File

@@ -0,0 +1,4 @@
{
"description": "Reflexion Prompting",
"prompt": "Answer concisely, critique your reasoning briefly, and provide a refined answer."
}

View File

@@ -0,0 +1,4 @@
{
"description": "Self-Consistency Prompting",
"prompt": "Provide multiple reasoning paths and select the most consistent answer."
}

View 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
View File

@@ -0,0 +1,4 @@
{
"description": "Standard Prompting",
"prompt": "Answer the question directly without any explanation or reasoning."
}

4
strategies/tot.json Normal file
View File

@@ -0,0 +1,4 @@
{
"description": "Tree-of-Thought (ToT) Prompting",
"prompt": "Generate multiple reasoning paths briefly and select the best one."
}

View File

@@ -1,3 +1,3 @@
package main
var version = "v1.4.156"
var version = "v1.4.159"

View File

@@ -26,6 +26,9 @@
"eslint-plugin-svelte": "^2.46.1",
"lucide-svelte": "^0.309.0",
"mdsvex": "^0.11.2",
"patch-package": "^8.0.0",
"pdf-to-markdown-core": "github:jzillmann/pdf-to-markdown#modularize",
"pdfjs-dist": "^2.5.207",
"postcss": "^8.4.49",
"postcss-load-config": "^6.0.1",
"rehype-autolink-headings": "^7.1.0",