mirror of
https://github.com/danielmiessler/Fabric.git
synced 2026-01-09 22:38:10 -05:00
Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f927fdf40f | ||
|
|
918862ef57 | ||
|
|
d9b8bc3233 | ||
|
|
da29b8e388 | ||
|
|
5e6d4110fa | ||
|
|
4bb090694b | ||
|
|
d232222787 | ||
|
|
095890a556 | ||
|
|
64c1fe18ef | ||
|
|
1cea32a677 | ||
|
|
49658a3214 | ||
|
|
f236cab276 | ||
|
|
5e0aaa1f93 | ||
|
|
eb16806931 | ||
|
|
474dd786a4 | ||
|
|
edad63df19 | ||
|
|
c7eb7439ef | ||
|
|
23d678d62f | ||
|
|
de5260a661 | ||
|
|
baeadc2270 | ||
|
|
5b4cec81c3 | ||
|
|
eda5531087 | ||
|
|
66925d188a | ||
|
|
6179742e79 | ||
|
|
d8fc6940f0 | ||
|
|
44f7e8dfef | ||
|
|
c5ada714ff | ||
|
|
80c4807f7e |
3
.github/workflows/release.yml
vendored
3
.github/workflows/release.yml
vendored
@@ -2,7 +2,7 @@ name: Go Release
|
||||
|
||||
on:
|
||||
repository_dispatch:
|
||||
types: [ tag_created ]
|
||||
types: [tag_created]
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
@@ -108,6 +108,7 @@ jobs:
|
||||
Add-Content -Path $env:GITHUB_ENV -Value "latest_tag=$latest_tag"
|
||||
|
||||
- name: Create release if it doesn't exist
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
|
||||
@@ -5,6 +5,7 @@ This document explains the complete workflow for managing pattern descriptions a
|
||||
## System Overview
|
||||
|
||||
The pattern system follows this hierarchy:
|
||||
|
||||
1. `~/.config/fabric/patterns/` directory: The source of truth for available patterns
|
||||
2. `pattern_extracts.json`: Contains first 500 words of each pattern for reference
|
||||
3. `pattern_descriptions.json`: Stores pattern metadata (descriptions and tags)
|
||||
@@ -13,17 +14,21 @@ The pattern system follows this hierarchy:
|
||||
## Pattern Processing Workflow
|
||||
|
||||
### 1. Adding New Patterns
|
||||
|
||||
- Add patterns to `~/.config/fabric/patterns/`
|
||||
- Run extract_patterns.py to process new additions:
|
||||
|
||||
```bash
|
||||
python extract_patterns.py
|
||||
|
||||
The Python Script automatically:
|
||||
|
||||
- Creates pattern extracts for reference
|
||||
- Adds placeholder entries in descriptions file
|
||||
- Syncs to web interface
|
||||
|
||||
### 2. Pattern Extract Creation
|
||||
|
||||
The script extracts first 500 words from each pattern's system.md file to:
|
||||
|
||||
- Provide context for writing descriptions
|
||||
@@ -31,8 +36,8 @@ The script extracts first 500 words from each pattern's system.md file to:
|
||||
- Aid in pattern categorization
|
||||
|
||||
### 3. Description and Tag Management
|
||||
Pattern descriptions and tags are managed in pattern_descriptions.json:
|
||||
|
||||
Pattern descriptions and tags are managed in pattern_descriptions.json:
|
||||
|
||||
{
|
||||
"patterns": [
|
||||
@@ -44,20 +49,21 @@ Pattern descriptions and tags are managed in pattern_descriptions.json:
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
## Completing Pattern Metadata
|
||||
|
||||
### Writing Descriptions
|
||||
|
||||
1. Check pattern_descriptions.json for "[Description pending]" entries
|
||||
2. Reference pattern_extracts.json for context
|
||||
|
||||
3. How to update Pattern short descriptions (one sentence).
|
||||
3. How to update Pattern short descriptions (one sentence).
|
||||
|
||||
You can update your descriptions in pattern_descriptions.json manually or using LLM assistance (preferred approach).
|
||||
You can update your descriptions in pattern_descriptions.json manually or using LLM assistance (preferred approach).
|
||||
|
||||
Tell AI to look for "Description pending" entries in this file and write a short description based on the extract info in the pattern_extracts.json file. You can also ask your LLM to add tags for those newly added patterns, using other patterns tag assignments as example.
|
||||
Tell AI to look for "Description pending" entries in this file and write a short description based on the extract info in the pattern_extracts.json file. You can also ask your LLM to add tags for those newly added patterns, using other patterns tag assignments as example.
|
||||
|
||||
### Managing Tags
|
||||
|
||||
1. Add appropriate tags to new patterns
|
||||
2. Update existing tags as needed
|
||||
3. Tags are stored as arrays: ["TAG1", "TAG2"]
|
||||
@@ -67,6 +73,7 @@ Tell AI to look for "Description pending" entries in this file and write a short
|
||||
## File Synchronization
|
||||
|
||||
The script maintains synchronization between:
|
||||
|
||||
- Local pattern_descriptions.json
|
||||
- Web interface copy in static/data/
|
||||
- No manual file copying needed
|
||||
@@ -91,6 +98,7 @@ The script maintains synchronization between:
|
||||
## Troubleshooting
|
||||
|
||||
If patterns are not showing in the web interface:
|
||||
|
||||
1. Verify pattern_descriptions.json format
|
||||
2. Check web static copy exists
|
||||
3. Ensure proper file permissions
|
||||
@@ -108,17 +116,3 @@ fabric/
|
||||
└── static/
|
||||
└── data/
|
||||
└── pattern_descriptions.json # Web interface copy
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
106
Pattern_Descriptions/extract_patterns.py
Normal file → Executable file
106
Pattern_Descriptions/extract_patterns.py
Normal file → Executable file
@@ -1,81 +1,96 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""Extracts pattern information from the ~/.config/fabric/patterns directory,
|
||||
creates JSON files for pattern extracts and descriptions, and updates web static files.
|
||||
"""
|
||||
import os
|
||||
import json
|
||||
import shutil
|
||||
|
||||
|
||||
def load_existing_file(filepath):
|
||||
"""Load existing JSON file or return default structure"""
|
||||
if os.path.exists(filepath):
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
try:
|
||||
with open(filepath, "r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
except json.JSONDecodeError:
|
||||
print(
|
||||
f"Warning: Malformed JSON in {filepath}. Starting with an empty list."
|
||||
)
|
||||
return {"patterns": []}
|
||||
return {"patterns": []}
|
||||
|
||||
|
||||
def get_pattern_extract(pattern_path):
|
||||
"""Extract first 500 words from pattern's system.md file"""
|
||||
system_md_path = os.path.join(pattern_path, "system.md")
|
||||
with open(system_md_path, 'r', encoding='utf-8') as f:
|
||||
content = ' '.join(f.read().split()[:500])
|
||||
with open(system_md_path, "r", encoding="utf-8") as f:
|
||||
content = " ".join(f.read().split()[:500])
|
||||
return content
|
||||
|
||||
|
||||
def extract_pattern_info():
|
||||
"""Extract pattern information from the patterns directory"""
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
patterns_dir = os.path.expanduser("~/.config/fabric/patterns")
|
||||
print(f"\nScanning patterns directory: {patterns_dir}")
|
||||
|
||||
|
||||
extracts_path = os.path.join(script_dir, "pattern_extracts.json")
|
||||
descriptions_path = os.path.join(script_dir, "pattern_descriptions.json")
|
||||
|
||||
|
||||
existing_extracts = load_existing_file(extracts_path)
|
||||
existing_descriptions = load_existing_file(descriptions_path)
|
||||
|
||||
|
||||
existing_extract_names = {p["patternName"] for p in existing_extracts["patterns"]}
|
||||
existing_description_names = {p["patternName"] for p in existing_descriptions["patterns"]}
|
||||
existing_description_names = {
|
||||
p["patternName"] for p in existing_descriptions["patterns"]
|
||||
}
|
||||
print(f"Found existing patterns: {len(existing_extract_names)}")
|
||||
|
||||
|
||||
new_extracts = []
|
||||
new_descriptions = []
|
||||
|
||||
|
||||
|
||||
for dirname in sorted(os.listdir(patterns_dir)):
|
||||
# Only log new pattern processing
|
||||
if dirname not in existing_extract_names:
|
||||
print(f"Processing new pattern: {dirname}")
|
||||
|
||||
|
||||
pattern_path = os.path.join(patterns_dir, dirname)
|
||||
system_md_path = os.path.join(pattern_path, "system.md")
|
||||
print(f"Checking system.md at: {system_md_path}")
|
||||
|
||||
|
||||
if os.path.isdir(pattern_path) and os.path.exists(system_md_path):
|
||||
print(f"Valid pattern directory found: {dirname}")
|
||||
if dirname not in existing_extract_names:
|
||||
print(f"Processing new pattern: {dirname}")
|
||||
|
||||
try:
|
||||
if dirname not in existing_extract_names:
|
||||
print(f"Creating new extract for: {dirname}")
|
||||
pattern_extract = get_pattern_extract(pattern_path) # Pass directory path
|
||||
new_extracts.append({
|
||||
"patternName": dirname,
|
||||
"pattern_extract": pattern_extract
|
||||
})
|
||||
|
||||
pattern_extract = get_pattern_extract(
|
||||
pattern_path
|
||||
) # Pass directory path
|
||||
new_extracts.append(
|
||||
{"patternName": dirname, "pattern_extract": pattern_extract}
|
||||
)
|
||||
|
||||
if dirname not in existing_description_names:
|
||||
print(f"Creating new description for: {dirname}")
|
||||
new_descriptions.append({
|
||||
"patternName": dirname,
|
||||
"description": "[Description pending]",
|
||||
"tags": []
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
new_descriptions.append(
|
||||
{
|
||||
"patternName": dirname,
|
||||
"description": "[Description pending]",
|
||||
"tags": [],
|
||||
}
|
||||
)
|
||||
|
||||
except OSError as e:
|
||||
print(f"Error processing {dirname}: {str(e)}")
|
||||
else:
|
||||
print(f"Invalid pattern directory or missing system.md: {dirname}")
|
||||
|
||||
print(f"\nProcessing summary:")
|
||||
|
||||
print("\nProcessing summary:")
|
||||
print(f"New extracts created: {len(new_extracts)}")
|
||||
print(f"New descriptions added: {len(new_descriptions)}")
|
||||
|
||||
|
||||
existing_extracts["patterns"].extend(new_extracts)
|
||||
existing_descriptions["patterns"].extend(new_descriptions)
|
||||
|
||||
|
||||
return existing_extracts, existing_descriptions, len(new_descriptions)
|
||||
|
||||
|
||||
@@ -87,28 +102,29 @@ def update_web_static(descriptions_path):
|
||||
static_path = os.path.join(static_dir, "pattern_descriptions.json")
|
||||
shutil.copy2(descriptions_path, static_path)
|
||||
|
||||
|
||||
def save_pattern_files():
|
||||
"""Save both pattern files and sync to web"""
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
extracts_path = os.path.join(script_dir, "pattern_extracts.json")
|
||||
descriptions_path = os.path.join(script_dir, "pattern_descriptions.json")
|
||||
|
||||
|
||||
pattern_extracts, pattern_descriptions, new_count = extract_pattern_info()
|
||||
|
||||
|
||||
# Save files
|
||||
with open(extracts_path, 'w', encoding='utf-8') as f:
|
||||
with open(extracts_path, "w", encoding="utf-8") as f:
|
||||
json.dump(pattern_extracts, f, indent=2, ensure_ascii=False)
|
||||
|
||||
with open(descriptions_path, 'w', encoding='utf-8') as f:
|
||||
|
||||
with open(descriptions_path, "w", encoding="utf-8") as f:
|
||||
json.dump(pattern_descriptions, f, indent=2, ensure_ascii=False)
|
||||
|
||||
|
||||
# Update web static
|
||||
update_web_static(descriptions_path)
|
||||
|
||||
print(f"\nProcessing complete:")
|
||||
|
||||
print("\nProcessing complete:")
|
||||
print(f"Total patterns: {len(pattern_descriptions['patterns'])}")
|
||||
print(f"New patterns added: {new_count}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
save_pattern_files()
|
||||
|
||||
|
||||
@@ -1710,7 +1710,7 @@
|
||||
},
|
||||
{
|
||||
"patternName": "analyze_bill_short",
|
||||
"description": "Consended - Analyze a legislative bill and implications.",
|
||||
"description": "Condensed - Analyze a legislative bill and implications.",
|
||||
"tags": [
|
||||
"ANALYSIS",
|
||||
"BILL"
|
||||
@@ -1815,6 +1815,35 @@
|
||||
"WRITING",
|
||||
"CREATIVITY"
|
||||
]
|
||||
},
|
||||
{
|
||||
"patternName": "extract_alpha",
|
||||
"description": "Extracts the most novel and surprising ideas (\"alpha\") from content, inspired by information theory.",
|
||||
"tags": [
|
||||
"EXTRACT",
|
||||
"ANALYSIS",
|
||||
"CR THINKING",
|
||||
"WISDOM"
|
||||
]
|
||||
},
|
||||
{
|
||||
"patternName": "extract_mcp_servers",
|
||||
"description": "Analyzes content to identify and extract detailed information about Model Context Protocol (MCP) servers.",
|
||||
"tags": [
|
||||
"ANALYSIS",
|
||||
"EXTRACT",
|
||||
"DEVELOPMENT",
|
||||
"AI"
|
||||
]
|
||||
},
|
||||
{
|
||||
"patternName": "review_code",
|
||||
"description": "Performs a comprehensive code review, providing detailed feedback on correctness, security, and performance.",
|
||||
"tags": [
|
||||
"DEVELOPMENT",
|
||||
"REVIEW",
|
||||
"SECURITY"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -883,6 +883,18 @@
|
||||
{
|
||||
"patternName": "write_essay",
|
||||
"pattern_extract": "# Identity and Purpose You are an expert on writing clear, and illuminating essays on the topic of the input provided. # Output Instructions - Write the essay in the style of {{author_name}}, embodying all the qualities that they are known for. - Look up some example Essays by {{author_name}} (Use web search if the tool is available) - Write the essay exactly like {{author_name}} would write it as seen in the examples you find. - Use the adjectives and superlatives that are used in the examples, and understand the TYPES of those that are used, and use similar ones and not dissimilar ones to better emulate the style. - Use the same style, vocabulary level, and sentence structure as {{author_name}}. # Output Format - Output a full, publish-ready essay about the content provided using the instructions above. - Write in {{author_name}}'s natural and clear style, without embellishment. - Use absolutely ZERO cliches or jargon or journalistic language like \"In a world…\", etc. - Do not use cliches or jargon. - Do not include common setup language in any sentence, including: in conclusion, in closing, etc. - Do not output warnings or notes—just the output requested. # INPUT: INPUT:"
|
||||
},
|
||||
{
|
||||
"patternName": "extract_alpha",
|
||||
"pattern_extract": "# IDENTITY You're an expert at finding Alpha in content. # PHILOSOPHY I love the idea of Claude Shannon's information theory where basically the only real information is the stuff that's different and anything that's the same as kind of background noise. I love that idea for novelty and surprise inside of content when I think about a presentation or a talk or a podcast or an essay or anything I'm looking for the net new ideas or the new presentation of ideas for the new frameworks of how to use ideas or combine ideas so I'm looking for a way to capture that inside of content. # INSTRUCTIONS I want you to extract the 24 highest alpha ideas and thoughts and insights and recommendations in this piece of content, and I want you to output them in unformatted marked down in 8-word bullets written in the approachable style of Paul Graham. # INPUT"
|
||||
},
|
||||
{
|
||||
"patternName": "extract_mcp_servers",
|
||||
"pattern_extract": "# IDENTITY and PURPOSE You are an expert at analyzing content related to MCP (Model Context Protocol) servers. You excel at identifying and extracting mentions of MCP servers, their features, capabilities, integrations, and usage patterns. Take a step back and think step-by-step about how to achieve the best results for extracting MCP server information. # STEPS - Read and analyze the entire content carefully - Identify all mentions of MCP servers, including: - Specific MCP server names - Server capabilities and features - Integration details - Configuration examples - Use cases and applications - Installation or setup instructions - API endpoints or methods exposed - Any limitations or requirements # OUTPUT SECTIONS - Output a summary of all MCP servers mentioned with the following sections: ## SERVERS FOUND - List each MCP server found with a 15-word description - Include the server name and its primary purpose - Use bullet points for each server ## SERVER DETAILS For each server found, provide: - **Server Name**: The official name - **Purpose**: Main functionality in 25 words or less - **Key Features**: Up to 5 main features as bullet points - **Integration**: How it integrates with systems (if mentioned) - **Configuration**: Any configuration details mentioned - **Requirements**: Dependencies or requirements (if specified) ## USAGE EXAMPLES - Extract any code snippets or usage examples - Include configuration files or setup instructions - Present each example with context ## INSIGHTS - Provide 3-5 insights about the MCP servers mentioned - Focus on patterns, trends, or notable characteristics - Each insight should be a 20-word bullet point # OUTPUT INSTRUCTIONS - Output in clean, readable Markdown - Use proper heading hierarchy - Include code blocks with appropriate language tags - Do not include warnings or notes about the content - If no MCP servers are found, simply state \"No MCP servers mentioned in the content\" - Ensure all server names are accurately captured - Preserve technical details and specifications # INPUT: INPUT:"
|
||||
},
|
||||
{
|
||||
"patternName": "review_code",
|
||||
"pattern_extract": "# Code Review Task ## ROLE AND GOAL You are a Principal Software Engineer, renowned for your meticulous attention to detail and your ability to provide clear, constructive, and educational code reviews. Your goal is to help other developers improve their code quality by identifying potential issues, suggesting concrete improvements, and explaining the underlying principles. ## TASK You will be given a snippet of code or a diff. Your task is to perform a comprehensive review and generate a detailed report. ## STEPS 1. **Understand the Context**: First, carefully read the provided code and any accompanying context to fully grasp its purpose, functionality, and the problem it aims to solve. 2. **Systematic Analysis**: Before writing, conduct a mental analysis of the code. Evaluate it against the following key aspects. Do not write this analysis in the output; use it to form your review. * **Correctness**: Are there bugs, logic errors, or race conditions? * **Security**: Are there any potential vulnerabilities (e.g., injection attacks, improper handling of sensitive data)? * **Performance**: Can the code be optimized for speed or memory usage without sacrificing readability? * **Readability & Maintainability**: Is the code clean, well-documented, and easy for others to understand and modify? * **Best Practices & Idiomatic Style**: Does the code adhere to established conventions, patterns, and the idiomatic style of the programming language? * **Error Handling & Edge Cases**: Are errors handled gracefully? Have all relevant edge cases been considered? 3. **Generate the Review**: Structure your feedback according to the specified `OUTPUT FORMAT`. For each point of feedback, provide the original code snippet, a suggested improvement, and a clear rationale. ## OUTPUT FORMAT Your review must be in Markdown and follow this exact structure: --- ### Overall Assessment A brief, high-level summary of the code's quality. Mention its strengths and the primary areas for improvement. ### **Prioritized Recommendations** A numbered list of the most important changes, ordered from most to least critical. 1. (Most critical change) 2. (Second most critical change) 3. ... ### **Detailed Feedback** For each issue you identified, provide a detailed breakdown in the following format. --- **[ISSUE TITLE]** - (e.g., `Security`, `Readability`, `Performance`) **Original Code:** ```[language] // The specific lines of code with the issue ``` **Suggested Improvement:** ```[language] // The revised, improved code ``` **Rationale:** A clear and concise explanation of why the change is recommended. Reference best practices, design patterns, or potential risks. If you use advanced concepts, briefly explain them. --- (Repeat this section for each issue) ## EXAMPLE Here is an example of a review for a simple Python function: --- ### **Overall Assessment** The function correctly fetches user data, but it can be made more robust and efficient. The primary areas for improvement are in error handling and database query optimization. ### **Prioritized Recommendations** 1. Avoid making database queries inside a loop to prevent performance issues (N+1 query problem). 2. Add specific error handling for when a user is not found. ### **Detailed Feedback** --- **[PERFORMANCE]** - N+1 Database Query **Original Code:**"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -74,6 +74,8 @@ type Flags struct {
|
||||
ListStrategies bool `long:"liststrategies" description:"List all strategies"`
|
||||
ListVendors bool `long:"listvendors" description:"List all vendors"`
|
||||
ShellCompleteOutput bool `long:"shell-complete-list" description:"Output raw list without headers/formatting (for shell completion)"`
|
||||
Search bool `long:"search" description:"Enable web search tool for supported models (Anthropic)"`
|
||||
SearchLocation string `long:"search-location" description:"Set location for web search results (e.g., 'America/Los_Angeles')"`
|
||||
}
|
||||
|
||||
var debug = false
|
||||
@@ -263,6 +265,8 @@ func (o *Flags) BuildChatOptions() (ret *common.ChatOptions) {
|
||||
Raw: o.Raw,
|
||||
Seed: o.Seed,
|
||||
ModelContextLength: o.ModelContextLength,
|
||||
Search: o.Search,
|
||||
SearchLocation: o.SearchLocation,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@ type ChatOptions struct {
|
||||
Seed int
|
||||
ModelContextLength int
|
||||
MaxTokens int
|
||||
Search bool
|
||||
SearchLocation string
|
||||
}
|
||||
|
||||
// NormalizeMessages remove empty messages and ensure messages order user-assist-user
|
||||
|
||||
@@ -1 +1 @@
|
||||
"1.4.221"
|
||||
"1.4.225"
|
||||
|
||||
@@ -22,19 +22,20 @@ Take a deep breath and think step by step about how to best accomplish this goal
|
||||
This must be under the heading "INSIGHTFULNESS SCORE (0 = not very interesting and insightful to 10 = very interesting and insightful)".
|
||||
- A rating of how emotional the debate was from 0 (very calm) to 5 (very emotional). This must be under the heading "EMOTIONALITY SCORE (0 (very calm) to 5 (very emotional))".
|
||||
- A list of the participants of the debate and a score of their emotionality from 0 (very calm) to 5 (very emotional). This must be under the heading "PARTICIPANTS".
|
||||
- A list of arguments attributed to participants with names and quotes. If possible, this should include external references that disprove or back up their claims.
|
||||
- A list of arguments attributed to participants with names and quotes. Each argument summary must be EXACTLY 16 words. If possible, this should include external references that disprove or back up their claims.
|
||||
It is IMPORTANT that these references are from trusted and verifiable sources that can be easily accessed. These sources have to BE REAL and NOT MADE UP. This must be under the heading "ARGUMENTS".
|
||||
If possible, provide an objective assessment of the truth of these arguments. If you assess the truth of the argument, provide some sources that back up your assessment. The material you provide should be from reliable, verifiable, and trustworthy sources. DO NOT MAKE UP SOURCES.
|
||||
- A list of agreements the participants have reached, attributed with names and quotes. This must be under the heading "AGREEMENTS".
|
||||
- A list of disagreements the participants were unable to resolve and the reasons why they remained unresolved, attributed with names and quotes. This must be under the heading "DISAGREEMENTS".
|
||||
- A list of possible misunderstandings and why they may have occurred, attributed with names and quotes. This must be under the heading "POSSIBLE MISUNDERSTANDINGS".
|
||||
- A list of learnings from the debate. This must be under the heading "LEARNINGS".
|
||||
- A list of takeaways that highlight ideas to think about, sources to explore, and actionable items. This must be under the heading "TAKEAWAYS".
|
||||
- A list of agreements the participants have reached. Each agreement summary must be EXACTLY 16 words, followed by names and quotes. This must be under the heading "AGREEMENTS".
|
||||
- A list of disagreements the participants were unable to resolve. Each disagreement summary must be EXACTLY 16 words, followed by names and quotes explaining why they remained unresolved. This must be under the heading "DISAGREEMENTS".
|
||||
- A list of possible misunderstandings. Each misunderstanding summary must be EXACTLY 16 words, followed by names and quotes explaining why they may have occurred. This must be under the heading "POSSIBLE MISUNDERSTANDINGS".
|
||||
- A list of learnings from the debate. Each learning must be EXACTLY 16 words. This must be under the heading "LEARNINGS".
|
||||
- A list of takeaways that highlight ideas to think about, sources to explore, and actionable items. Each takeaway must be EXACTLY 16 words. This must be under the heading "TAKEAWAYS".
|
||||
|
||||
# OUTPUT INSTRUCTIONS
|
||||
|
||||
- Output all sections above.
|
||||
- Use Markdown to structure your output.
|
||||
- Do not use any markdown formatting (no asterisks, no bullet points, no headers).
|
||||
- Keep all agreements, arguments, recommendations, learnings, and takeaways to EXACTLY 16 words each.
|
||||
- When providing quotes, these quotes should clearly express the points you are using them for. If necessary, use multiple quotes.
|
||||
|
||||
# INPUT:
|
||||
|
||||
16
patterns/extract_alpha/system.md
Normal file
16
patterns/extract_alpha/system.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# IDENTITY
|
||||
|
||||
You're an expert at finding Alpha in content.
|
||||
|
||||
# PHILOSOPHY
|
||||
|
||||
I love the idea of Claude Shannon's information theory where basically the only real information is the stuff that's different and anything that's the same as kind of background noise.
|
||||
|
||||
I love that idea for novelty and surprise inside of content when I think about a presentation or a talk or a podcast or an essay or anything I'm looking for the net new ideas or the new presentation of ideas for the new frameworks of how to use ideas or combine ideas so I'm looking for a way to capture that inside of content.
|
||||
|
||||
# INSTRUCTIONS
|
||||
|
||||
I want you to extract the 24 highest alpha ideas and thoughts and insights and recommendations in this piece of content, and I want you to output them in unformatted marked down in 8-word bullets written in the approachable style of Paul Graham.
|
||||
|
||||
# INPUT
|
||||
|
||||
140
patterns/review_code/system.md
Normal file
140
patterns/review_code/system.md
Normal file
@@ -0,0 +1,140 @@
|
||||
# Code Review Task
|
||||
|
||||
## ROLE AND GOAL
|
||||
|
||||
You are a Principal Software Engineer, renowned for your meticulous attention to detail and your ability to provide clear, constructive, and educational code reviews. Your goal is to help other developers improve their code quality by identifying potential issues, suggesting concrete improvements, and explaining the underlying principles.
|
||||
|
||||
## TASK
|
||||
|
||||
You will be given a snippet of code or a diff. Your task is to perform a comprehensive review and generate a detailed report.
|
||||
|
||||
## STEPS
|
||||
|
||||
1. **Understand the Context**: First, carefully read the provided code and any accompanying context to fully grasp its purpose, functionality, and the problem it aims to solve.
|
||||
2. **Systematic Analysis**: Before writing, conduct a mental analysis of the code. Evaluate it against the following key aspects. Do not write this analysis in the output; use it to form your review.
|
||||
* **Correctness**: Are there bugs, logic errors, or race conditions?
|
||||
* **Security**: Are there any potential vulnerabilities (e.g., injection attacks, improper handling of sensitive data)?
|
||||
* **Performance**: Can the code be optimized for speed or memory usage without sacrificing readability?
|
||||
* **Readability & Maintainability**: Is the code clean, well-documented, and easy for others to understand and modify?
|
||||
* **Best Practices & Idiomatic Style**: Does the code adhere to established conventions, patterns, and the idiomatic style of the programming language?
|
||||
* **Error Handling & Edge Cases**: Are errors handled gracefully? Have all relevant edge cases been considered?
|
||||
3. **Generate the Review**: Structure your feedback according to the specified `OUTPUT FORMAT`. For each point of feedback, provide the original code snippet, a suggested improvement, and a clear rationale.
|
||||
|
||||
## OUTPUT FORMAT
|
||||
|
||||
Your review must be in Markdown and follow this exact structure:
|
||||
|
||||
---
|
||||
|
||||
### Overall Assessment
|
||||
|
||||
A brief, high-level summary of the code's quality. Mention its strengths and the primary areas for improvement.
|
||||
|
||||
### **Prioritized Recommendations**
|
||||
|
||||
A numbered list of the most important changes, ordered from most to least critical.
|
||||
|
||||
1. (Most critical change)
|
||||
2. (Second most critical change)
|
||||
3. ...
|
||||
|
||||
### **Detailed Feedback**
|
||||
|
||||
For each issue you identified, provide a detailed breakdown in the following format.
|
||||
|
||||
---
|
||||
|
||||
**[ISSUE TITLE]** - (e.g., `Security`, `Readability`, `Performance`)
|
||||
|
||||
**Original Code:**
|
||||
|
||||
```[language]
|
||||
// The specific lines of code with the issue
|
||||
```
|
||||
|
||||
**Suggested Improvement:**
|
||||
|
||||
```[language]
|
||||
// The revised, improved code
|
||||
```
|
||||
|
||||
**Rationale:**
|
||||
A clear and concise explanation of why the change is recommended. Reference best practices, design patterns, or potential risks. If you use advanced concepts, briefly explain them.
|
||||
|
||||
---
|
||||
(Repeat this section for each issue)
|
||||
|
||||
## EXAMPLE
|
||||
|
||||
Here is an example of a review for a simple Python function:
|
||||
|
||||
---
|
||||
|
||||
### **Overall Assessment**
|
||||
|
||||
The function correctly fetches user data, but it can be made more robust and efficient. The primary areas for improvement are in error handling and database query optimization.
|
||||
|
||||
### **Prioritized Recommendations**
|
||||
|
||||
1. Avoid making database queries inside a loop to prevent performance issues (N+1 query problem).
|
||||
2. Add specific error handling for when a user is not found.
|
||||
|
||||
### **Detailed Feedback**
|
||||
|
||||
---
|
||||
|
||||
**[PERFORMANCE]** - N+1 Database Query
|
||||
|
||||
**Original Code:**
|
||||
|
||||
```python
|
||||
def get_user_emails(user_ids):
|
||||
emails = []
|
||||
for user_id in user_ids:
|
||||
user = db.query(User).filter(User.id == user_id).one()
|
||||
emails.append(user.email)
|
||||
return emails
|
||||
```
|
||||
|
||||
**Suggested Improvement:**
|
||||
|
||||
```python
|
||||
def get_user_emails(user_ids):
|
||||
if not user_ids:
|
||||
return []
|
||||
users = db.query(User).filter(User.id.in_(user_ids)).all()
|
||||
return [user.email for user in users]
|
||||
```
|
||||
|
||||
**Rationale:**
|
||||
The original code executes one database query for each `user_id` in the list. This is known as the "N+1 query problem" and performs very poorly on large lists. The suggested improvement fetches all users in a single query using `IN`, which is significantly more efficient.
|
||||
|
||||
---
|
||||
|
||||
**[CORRECTNESS]** - Lacks Specific Error Handling
|
||||
|
||||
**Original Code:**
|
||||
|
||||
```python
|
||||
user = db.query(User).filter(User.id == user_id).one()
|
||||
```
|
||||
|
||||
**Suggested Improvement:**
|
||||
|
||||
```python
|
||||
from sqlalchemy.orm.exc import NoResultFound
|
||||
|
||||
try:
|
||||
user = db.query(User).filter(User.id == user_id).one()
|
||||
except NoResultFound:
|
||||
# Handle the case where the user doesn't exist
|
||||
# e.g., log a warning, skip the user, or raise a custom exception
|
||||
continue
|
||||
```
|
||||
|
||||
**Rationale:**
|
||||
The `.one()` method will raise a `NoResultFound` exception if a user with the given ID doesn't exist, which would crash the entire function. It's better to explicitly handle this case using a try/except block to make the function more resilient.
|
||||
|
||||
---
|
||||
|
||||
## INPUT
|
||||
@@ -5,8 +5,6 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/samber/lo"
|
||||
|
||||
"github.com/anthropics/anthropic-sdk-go"
|
||||
"github.com/anthropics/anthropic-sdk-go/option"
|
||||
"github.com/danielmiessler/fabric/chat"
|
||||
@@ -16,6 +14,10 @@ import (
|
||||
|
||||
const defaultBaseUrl = "https://api.anthropic.com/"
|
||||
|
||||
const webSearchToolName = "web_search"
|
||||
const webSearchToolType = "web_search_20250305"
|
||||
const sourcesHeader = "## Sources"
|
||||
|
||||
func NewClient() (ret *Client) {
|
||||
vendorName := "Anthropic"
|
||||
ret = &Client{}
|
||||
@@ -29,9 +31,6 @@ func NewClient() (ret *Client) {
|
||||
ret.ApiBaseURL = ret.AddSetupQuestion("API Base URL", false)
|
||||
ret.ApiBaseURL.Value = defaultBaseUrl
|
||||
ret.ApiKey = ret.PluginBase.AddSetupQuestion("API key", true)
|
||||
ret.UseWebTool = ret.AddSetupQuestionBool("Web Search Tool Enabled", false)
|
||||
ret.WebToolLocation = ret.AddSetupQuestionCustom("Web Search Tool Location", false,
|
||||
"Enter your approximate timezone location for web search (e.g., 'America/Los_Angeles', see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones).")
|
||||
|
||||
ret.maxTokens = 4096
|
||||
ret.defaultRequiredUserMessage = "Hi"
|
||||
@@ -49,10 +48,8 @@ func NewClient() (ret *Client) {
|
||||
|
||||
type Client struct {
|
||||
*plugins.PluginBase
|
||||
ApiBaseURL *plugins.SetupQuestion
|
||||
ApiKey *plugins.SetupQuestion
|
||||
UseWebTool *plugins.SetupQuestion
|
||||
WebToolLocation *plugins.SetupQuestion
|
||||
ApiBaseURL *plugins.SetupQuestion
|
||||
ApiKey *plugins.SetupQuestion
|
||||
|
||||
maxTokens int
|
||||
defaultRequiredUserMessage string
|
||||
@@ -127,20 +124,17 @@ func (an *Client) buildMessageParams(msgs []anthropic.MessageParam, opts *common
|
||||
Messages: msgs,
|
||||
}
|
||||
|
||||
if plugins.ParseBoolElseFalse(an.UseWebTool.Value) {
|
||||
if opts.Search {
|
||||
// Build the web-search tool definition:
|
||||
webTool := anthropic.WebSearchTool20250305Param{
|
||||
Name: "web_search", // string literal instead of constant
|
||||
Type: "web_search_20250305", // string literal instead of constant
|
||||
Name: webSearchToolName,
|
||||
Type: webSearchToolType,
|
||||
CacheControl: anthropic.NewCacheControlEphemeralParam(),
|
||||
// Optional: restrict domains or max uses
|
||||
// AllowedDomains: []string{"wikipedia.org", "openai.com"},
|
||||
// MaxUses: anthropic.Opt[int64](5),
|
||||
}
|
||||
|
||||
if an.WebToolLocation.Value != "" {
|
||||
if opts.SearchLocation != "" {
|
||||
webTool.UserLocation.Type = "approximate"
|
||||
webTool.UserLocation.Timezone = anthropic.Opt(an.WebToolLocation.Value)
|
||||
webTool.UserLocation.Timezone = anthropic.Opt(opts.SearchLocation)
|
||||
}
|
||||
|
||||
// Wrap it in the union:
|
||||
@@ -165,13 +159,42 @@ func (an *Client) Send(ctx context.Context, msgs []*chat.ChatCompletionMessage,
|
||||
return
|
||||
}
|
||||
|
||||
texts := lo.FilterMap(message.Content, func(block anthropic.ContentBlockUnion, _ int) (ret string, ok bool) {
|
||||
if ok = block.Type == "text" && block.Text != ""; ok {
|
||||
ret = block.Text
|
||||
var textParts []string
|
||||
var citations []string
|
||||
citationMap := make(map[string]bool) // To avoid duplicate citations
|
||||
|
||||
for _, block := range message.Content {
|
||||
if block.Type == "text" && block.Text != "" {
|
||||
textParts = append(textParts, block.Text)
|
||||
|
||||
// Extract citations from this text block
|
||||
for _, citation := range block.Citations {
|
||||
if citation.Type == "web_search_result_location" {
|
||||
citationKey := citation.URL + "|" + citation.Title
|
||||
if !citationMap[citationKey] {
|
||||
citationMap[citationKey] = true
|
||||
citationText := fmt.Sprintf("- [%s](%s)", citation.Title, citation.URL)
|
||||
if citation.CitedText != "" {
|
||||
citationText += fmt.Sprintf(" - \"%s\"", citation.CitedText)
|
||||
}
|
||||
citations = append(citations, citationText)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
})
|
||||
ret = strings.Join(texts, "")
|
||||
}
|
||||
|
||||
var resultBuilder strings.Builder
|
||||
resultBuilder.WriteString(strings.Join(textParts, ""))
|
||||
|
||||
// Append citations if any were found
|
||||
if len(citations) > 0 {
|
||||
resultBuilder.WriteString("\n\n")
|
||||
resultBuilder.WriteString(sourcesHeader)
|
||||
resultBuilder.WriteString("\n\n")
|
||||
resultBuilder.WriteString(strings.Join(citations, "\n"))
|
||||
}
|
||||
ret = resultBuilder.String()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
package anthropic
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/anthropics/anthropic-sdk-go"
|
||||
"github.com/danielmiessler/fabric/common"
|
||||
)
|
||||
|
||||
// Test generated using Keploy
|
||||
@@ -63,3 +67,192 @@ func TestClient_ListModels_ReturnsCorrectModels(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildMessageParams_WithoutSearch(t *testing.T) {
|
||||
client := NewClient()
|
||||
opts := &common.ChatOptions{
|
||||
Model: "claude-3-5-sonnet-latest",
|
||||
Temperature: 0.7,
|
||||
Search: false,
|
||||
}
|
||||
|
||||
messages := []anthropic.MessageParam{
|
||||
anthropic.NewUserMessage(anthropic.NewTextBlock("Hello")),
|
||||
}
|
||||
|
||||
params := client.buildMessageParams(messages, opts)
|
||||
|
||||
if params.Tools != nil {
|
||||
t.Error("Expected no tools when search is disabled, got tools")
|
||||
}
|
||||
|
||||
if params.Model != anthropic.Model(opts.Model) {
|
||||
t.Errorf("Expected model %s, got %s", opts.Model, params.Model)
|
||||
}
|
||||
|
||||
if params.Temperature.Value != opts.Temperature {
|
||||
t.Errorf("Expected temperature %f, got %f", opts.Temperature, params.Temperature.Value)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildMessageParams_WithSearch(t *testing.T) {
|
||||
client := NewClient()
|
||||
opts := &common.ChatOptions{
|
||||
Model: "claude-3-5-sonnet-latest",
|
||||
Temperature: 0.7,
|
||||
Search: true,
|
||||
}
|
||||
|
||||
messages := []anthropic.MessageParam{
|
||||
anthropic.NewUserMessage(anthropic.NewTextBlock("What's the weather today?")),
|
||||
}
|
||||
|
||||
params := client.buildMessageParams(messages, opts)
|
||||
|
||||
if params.Tools == nil {
|
||||
t.Fatal("Expected tools when search is enabled, got nil")
|
||||
}
|
||||
|
||||
if len(params.Tools) != 1 {
|
||||
t.Errorf("Expected 1 tool, got %d", len(params.Tools))
|
||||
}
|
||||
|
||||
webTool := params.Tools[0].OfWebSearchTool20250305
|
||||
if webTool == nil {
|
||||
t.Fatal("Expected web search tool, got nil")
|
||||
}
|
||||
|
||||
if webTool.Name != "web_search" {
|
||||
t.Errorf("Expected tool name 'web_search', got %s", webTool.Name)
|
||||
}
|
||||
|
||||
if webTool.Type != "web_search_20250305" {
|
||||
t.Errorf("Expected tool type 'web_search_20250305', got %s", webTool.Type)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildMessageParams_WithSearchAndLocation(t *testing.T) {
|
||||
client := NewClient()
|
||||
opts := &common.ChatOptions{
|
||||
Model: "claude-3-5-sonnet-latest",
|
||||
Temperature: 0.7,
|
||||
Search: true,
|
||||
SearchLocation: "America/Los_Angeles",
|
||||
}
|
||||
|
||||
messages := []anthropic.MessageParam{
|
||||
anthropic.NewUserMessage(anthropic.NewTextBlock("What's the weather in San Francisco?")),
|
||||
}
|
||||
|
||||
params := client.buildMessageParams(messages, opts)
|
||||
|
||||
if params.Tools == nil {
|
||||
t.Fatal("Expected tools when search is enabled, got nil")
|
||||
}
|
||||
|
||||
webTool := params.Tools[0].OfWebSearchTool20250305
|
||||
if webTool == nil {
|
||||
t.Fatal("Expected web search tool, got nil")
|
||||
}
|
||||
|
||||
if webTool.UserLocation.Type != "approximate" {
|
||||
t.Errorf("Expected location type 'approximate', got %s", webTool.UserLocation.Type)
|
||||
}
|
||||
|
||||
if webTool.UserLocation.Timezone.Value != opts.SearchLocation {
|
||||
t.Errorf("Expected timezone %s, got %s", opts.SearchLocation, webTool.UserLocation.Timezone.Value)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCitationFormatting(t *testing.T) {
|
||||
// Test the citation formatting logic by creating a mock message with citations
|
||||
message := &anthropic.Message{
|
||||
Content: []anthropic.ContentBlockUnion{
|
||||
{
|
||||
Type: "text",
|
||||
Text: "Based on recent research, artificial intelligence is advancing rapidly.",
|
||||
Citations: []anthropic.TextCitationUnion{
|
||||
{
|
||||
Type: "web_search_result_location",
|
||||
URL: "https://example.com/ai-research",
|
||||
Title: "AI Research Advances 2025",
|
||||
CitedText: "artificial intelligence is advancing rapidly",
|
||||
},
|
||||
{
|
||||
Type: "web_search_result_location",
|
||||
URL: "https://another-source.com/tech-news",
|
||||
Title: "Technology News Today",
|
||||
CitedText: "recent developments in AI",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: "text",
|
||||
Text: " Machine learning models are becoming more sophisticated.",
|
||||
Citations: []anthropic.TextCitationUnion{
|
||||
{
|
||||
Type: "web_search_result_location",
|
||||
URL: "https://example.com/ai-research", // Duplicate URL should be deduplicated
|
||||
Title: "AI Research Advances 2025",
|
||||
CitedText: "machine learning models",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Extract text and citations using the same logic as the Send method
|
||||
var textParts []string
|
||||
var citations []string
|
||||
citationMap := make(map[string]bool)
|
||||
|
||||
for _, block := range message.Content {
|
||||
if block.Type == "text" && block.Text != "" {
|
||||
textParts = append(textParts, block.Text)
|
||||
|
||||
for _, citation := range block.Citations {
|
||||
if citation.Type == "web_search_result_location" {
|
||||
citationKey := citation.URL + "|" + citation.Title
|
||||
if !citationMap[citationKey] {
|
||||
citationMap[citationKey] = true
|
||||
citationText := "- [" + citation.Title + "](" + citation.URL + ")"
|
||||
if citation.CitedText != "" {
|
||||
citationText += " - \"" + citation.CitedText + "\""
|
||||
}
|
||||
citations = append(citations, citationText)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result := strings.Join(textParts, "")
|
||||
if len(citations) > 0 {
|
||||
result += "\n\n## Sources\n\n" + strings.Join(citations, "\n")
|
||||
}
|
||||
|
||||
// Verify the result contains the expected text
|
||||
expectedText := "Based on recent research, artificial intelligence is advancing rapidly. Machine learning models are becoming more sophisticated."
|
||||
if !strings.Contains(result, expectedText) {
|
||||
t.Errorf("Expected result to contain text: %s", expectedText)
|
||||
}
|
||||
|
||||
// Verify citations are included
|
||||
if !strings.Contains(result, "## Sources") {
|
||||
t.Error("Expected result to contain Sources section")
|
||||
}
|
||||
|
||||
if !strings.Contains(result, "[AI Research Advances 2025](https://example.com/ai-research)") {
|
||||
t.Error("Expected result to contain first citation")
|
||||
}
|
||||
|
||||
if !strings.Contains(result, "[Technology News Today](https://another-source.com/tech-news)") {
|
||||
t.Error("Expected result to contain second citation")
|
||||
}
|
||||
|
||||
// Verify deduplication - should only have 2 unique citations, not 3
|
||||
citationCount := strings.Count(result, "- [")
|
||||
if citationCount != 2 {
|
||||
t.Errorf("Expected 2 unique citations, got %d", citationCount)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,6 +76,12 @@ func (c *Client) formatOptions(opts *common.ChatOptions) string {
|
||||
if opts.ModelContextLength != 0 {
|
||||
builder.WriteString(fmt.Sprintf("ModelContextLength: %d\n", opts.ModelContextLength))
|
||||
}
|
||||
if opts.Search {
|
||||
builder.WriteString("Search: enabled\n")
|
||||
if opts.SearchLocation != "" {
|
||||
builder.WriteString(fmt.Sprintf("SearchLocation: %s\n", opts.SearchLocation))
|
||||
}
|
||||
}
|
||||
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
116
plugins/ai/openai/chat_completions.go
Normal file
116
plugins/ai/openai/chat_completions.go
Normal file
@@ -0,0 +1,116 @@
|
||||
package openai
|
||||
|
||||
// This file contains helper methods for the Chat Completions API.
|
||||
// These methods are used as fallbacks for OpenAI-compatible providers
|
||||
// that don't support the newer Responses API (e.g., Groq, Mistral, etc.).
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/danielmiessler/fabric/chat"
|
||||
"github.com/danielmiessler/fabric/common"
|
||||
openai "github.com/openai/openai-go"
|
||||
"github.com/openai/openai-go/shared"
|
||||
)
|
||||
|
||||
// sendChatCompletions sends a request using the Chat Completions API
|
||||
func (o *Client) sendChatCompletions(ctx context.Context, msgs []*chat.ChatCompletionMessage, opts *common.ChatOptions) (ret string, err error) {
|
||||
req := o.buildChatCompletionParams(msgs, opts)
|
||||
|
||||
var resp *openai.ChatCompletion
|
||||
if resp, err = o.ApiClient.Chat.Completions.New(ctx, req); err != nil {
|
||||
return
|
||||
}
|
||||
if len(resp.Choices) > 0 {
|
||||
ret = resp.Choices[0].Message.Content
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// sendStreamChatCompletions sends a streaming request using the Chat Completions API
|
||||
func (o *Client) sendStreamChatCompletions(
|
||||
msgs []*chat.ChatCompletionMessage, opts *common.ChatOptions, channel chan string,
|
||||
) (err error) {
|
||||
defer close(channel)
|
||||
|
||||
req := o.buildChatCompletionParams(msgs, opts)
|
||||
stream := o.ApiClient.Chat.Completions.NewStreaming(context.Background(), req)
|
||||
for stream.Next() {
|
||||
chunk := stream.Current()
|
||||
if len(chunk.Choices) > 0 && chunk.Choices[0].Delta.Content != "" {
|
||||
channel <- chunk.Choices[0].Delta.Content
|
||||
}
|
||||
}
|
||||
if stream.Err() == nil {
|
||||
channel <- "\n"
|
||||
}
|
||||
return stream.Err()
|
||||
}
|
||||
|
||||
// buildChatCompletionParams builds parameters for the Chat Completions API
|
||||
func (o *Client) buildChatCompletionParams(
|
||||
inputMsgs []*chat.ChatCompletionMessage, opts *common.ChatOptions,
|
||||
) (ret openai.ChatCompletionNewParams) {
|
||||
|
||||
messages := make([]openai.ChatCompletionMessageParamUnion, len(inputMsgs))
|
||||
for i, msgPtr := range inputMsgs {
|
||||
msg := *msgPtr
|
||||
if strings.Contains(opts.Model, "deepseek") && len(inputMsgs) == 1 && msg.Role == chat.ChatMessageRoleSystem {
|
||||
msg.Role = chat.ChatMessageRoleUser
|
||||
}
|
||||
messages[i] = o.convertChatMessage(msg)
|
||||
}
|
||||
|
||||
ret = openai.ChatCompletionNewParams{
|
||||
Model: shared.ChatModel(opts.Model),
|
||||
Messages: messages,
|
||||
}
|
||||
|
||||
if !opts.Raw {
|
||||
ret.Temperature = openai.Float(opts.Temperature)
|
||||
ret.TopP = openai.Float(opts.TopP)
|
||||
if opts.MaxTokens != 0 {
|
||||
ret.MaxTokens = openai.Int(int64(opts.MaxTokens))
|
||||
}
|
||||
if opts.PresencePenalty != 0 {
|
||||
ret.PresencePenalty = openai.Float(opts.PresencePenalty)
|
||||
}
|
||||
if opts.FrequencyPenalty != 0 {
|
||||
ret.FrequencyPenalty = openai.Float(opts.FrequencyPenalty)
|
||||
}
|
||||
if opts.Seed != 0 {
|
||||
ret.Seed = openai.Int(int64(opts.Seed))
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// convertChatMessage converts fabric chat message to OpenAI chat completion message
|
||||
func (o *Client) convertChatMessage(msg chat.ChatCompletionMessage) openai.ChatCompletionMessageParamUnion {
|
||||
result := convertMessageCommon(msg)
|
||||
|
||||
switch result.Role {
|
||||
case chat.ChatMessageRoleSystem:
|
||||
return openai.SystemMessage(result.Content)
|
||||
case chat.ChatMessageRoleUser:
|
||||
// Handle multi-content messages (text + images)
|
||||
if result.HasMultiContent {
|
||||
var parts []openai.ChatCompletionContentPartUnionParam
|
||||
for _, p := range result.MultiContent {
|
||||
switch p.Type {
|
||||
case chat.ChatMessagePartTypeText:
|
||||
parts = append(parts, openai.TextContentPart(p.Text))
|
||||
case chat.ChatMessagePartTypeImageURL:
|
||||
parts = append(parts, openai.ImageContentPart(openai.ChatCompletionContentPartImageImageURLParam{URL: p.ImageURL.URL}))
|
||||
}
|
||||
}
|
||||
return openai.UserMessage(parts)
|
||||
}
|
||||
return openai.UserMessage(result.Content)
|
||||
case chat.ChatMessageRoleAssistant:
|
||||
return openai.AssistantMessage(result.Content)
|
||||
default:
|
||||
return openai.UserMessage(result.Content)
|
||||
}
|
||||
}
|
||||
21
plugins/ai/openai/message_conversion.go
Normal file
21
plugins/ai/openai/message_conversion.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package openai
|
||||
|
||||
import "github.com/danielmiessler/fabric/chat"
|
||||
|
||||
// MessageConversionResult holds the common conversion result
|
||||
type MessageConversionResult struct {
|
||||
Role string
|
||||
Content string
|
||||
MultiContent []chat.ChatMessagePart
|
||||
HasMultiContent bool
|
||||
}
|
||||
|
||||
// convertMessageCommon extracts common conversion logic
|
||||
func convertMessageCommon(msg chat.ChatCompletionMessage) MessageConversionResult {
|
||||
return MessageConversionResult{
|
||||
Role: msg.Role,
|
||||
Content: msg.Content,
|
||||
MultiContent: msg.MultiContent,
|
||||
HasMultiContent: len(msg.MultiContent) > 0,
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package openai
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
@@ -12,10 +11,13 @@ import (
|
||||
openai "github.com/openai/openai-go"
|
||||
"github.com/openai/openai-go/option"
|
||||
"github.com/openai/openai-go/packages/pagination"
|
||||
"github.com/openai/openai-go/responses"
|
||||
"github.com/openai/openai-go/shared"
|
||||
"github.com/openai/openai-go/shared/constant"
|
||||
)
|
||||
|
||||
func NewClient() (ret *Client) {
|
||||
return NewClientCompatible("OpenAI", "https://api.openai.com/v1", nil)
|
||||
return NewClientCompatibleWithResponses("OpenAI", "https://api.openai.com/v1", true, nil)
|
||||
}
|
||||
|
||||
func NewClientCompatible(vendorName string, defaultBaseUrl string, configureCustom func() error) (ret *Client) {
|
||||
@@ -28,6 +30,17 @@ func NewClientCompatible(vendorName string, defaultBaseUrl string, configureCust
|
||||
return
|
||||
}
|
||||
|
||||
func NewClientCompatibleWithResponses(vendorName string, defaultBaseUrl string, implementsResponses bool, configureCustom func() error) (ret *Client) {
|
||||
ret = NewClientCompatibleNoSetupQuestions(vendorName, configureCustom)
|
||||
|
||||
ret.ApiKey = ret.AddSetupQuestion("API Key", true)
|
||||
ret.ApiBaseURL = ret.AddSetupQuestion("API Base URL", false)
|
||||
ret.ApiBaseURL.Value = defaultBaseUrl
|
||||
ret.ImplementsResponses = implementsResponses
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func NewClientCompatibleNoSetupQuestions(vendorName string, configureCustom func() error) (ret *Client) {
|
||||
ret = &Client{}
|
||||
|
||||
@@ -46,9 +59,10 @@ func NewClientCompatibleNoSetupQuestions(vendorName string, configureCustom func
|
||||
|
||||
type Client struct {
|
||||
*plugins.PluginBase
|
||||
ApiKey *plugins.SetupQuestion
|
||||
ApiBaseURL *plugins.SetupQuestion
|
||||
ApiClient *openai.Client
|
||||
ApiKey *plugins.SetupQuestion
|
||||
ApiBaseURL *plugins.SetupQuestion
|
||||
ApiClient *openai.Client
|
||||
ImplementsResponses bool // Whether this provider supports the Responses API
|
||||
}
|
||||
|
||||
func (o *Client) configure() (ret error) {
|
||||
@@ -75,35 +89,59 @@ func (o *Client) ListModels() (ret []string, err error) {
|
||||
func (o *Client) SendStream(
|
||||
msgs []*chat.ChatCompletionMessage, opts *common.ChatOptions, channel chan string,
|
||||
) (err error) {
|
||||
req := o.buildChatCompletionParams(msgs, opts)
|
||||
stream := o.ApiClient.Chat.Completions.NewStreaming(context.Background(), req)
|
||||
// Use Responses API for OpenAI, Chat Completions API for other providers
|
||||
if o.supportsResponsesAPI() {
|
||||
return o.sendStreamResponses(msgs, opts, channel)
|
||||
}
|
||||
return o.sendStreamChatCompletions(msgs, opts, channel)
|
||||
}
|
||||
|
||||
func (o *Client) sendStreamResponses(
|
||||
msgs []*chat.ChatCompletionMessage, opts *common.ChatOptions, channel chan string,
|
||||
) (err error) {
|
||||
defer close(channel)
|
||||
|
||||
req := o.buildResponseParams(msgs, opts)
|
||||
stream := o.ApiClient.Responses.NewStreaming(context.Background(), req)
|
||||
for stream.Next() {
|
||||
chunk := stream.Current()
|
||||
if len(chunk.Choices) > 0 {
|
||||
channel <- chunk.Choices[0].Delta.Content
|
||||
event := stream.Current()
|
||||
switch event.Type {
|
||||
case string(constant.ResponseOutputTextDelta("").Default()):
|
||||
channel <- event.AsResponseOutputTextDelta().Delta
|
||||
case string(constant.ResponseOutputTextDone("").Default()):
|
||||
channel <- event.AsResponseOutputTextDone().Text
|
||||
}
|
||||
}
|
||||
if stream.Err() == nil {
|
||||
channel <- "\n"
|
||||
}
|
||||
close(channel)
|
||||
return stream.Err()
|
||||
}
|
||||
|
||||
func (o *Client) Send(ctx context.Context, msgs []*chat.ChatCompletionMessage, opts *common.ChatOptions) (ret string, err error) {
|
||||
req := o.buildChatCompletionParams(msgs, opts)
|
||||
// Use Responses API for OpenAI, Chat Completions API for other providers
|
||||
if o.supportsResponsesAPI() {
|
||||
return o.sendResponses(ctx, msgs, opts)
|
||||
}
|
||||
return o.sendChatCompletions(ctx, msgs, opts)
|
||||
}
|
||||
|
||||
var resp *openai.ChatCompletion
|
||||
if resp, err = o.ApiClient.Chat.Completions.New(ctx, req); err != nil {
|
||||
func (o *Client) sendResponses(ctx context.Context, msgs []*chat.ChatCompletionMessage, opts *common.ChatOptions) (ret string, err error) {
|
||||
req := o.buildResponseParams(msgs, opts)
|
||||
|
||||
var resp *responses.Response
|
||||
if resp, err = o.ApiClient.Responses.New(ctx, req); err != nil {
|
||||
return
|
||||
}
|
||||
if len(resp.Choices) > 0 {
|
||||
ret = resp.Choices[0].Message.Content
|
||||
slog.Debug("SystemFingerprint: " + resp.SystemFingerprint)
|
||||
}
|
||||
ret = o.extractText(resp)
|
||||
return
|
||||
}
|
||||
|
||||
// supportsResponsesAPI determines if the provider supports the new Responses API
|
||||
func (o *Client) supportsResponsesAPI() bool {
|
||||
return o.ImplementsResponses
|
||||
}
|
||||
|
||||
func (o *Client) NeedsRawMode(modelName string) bool {
|
||||
openaiModelsPrefixes := []string{
|
||||
"o1",
|
||||
@@ -115,8 +153,6 @@ func (o *Client) NeedsRawMode(modelName string) bool {
|
||||
"gpt-4o-mini-search-preview-2025-03-11",
|
||||
"gpt-4o-search-preview",
|
||||
"gpt-4o-search-preview-2025-03-11",
|
||||
"o4-mini-deep-research",
|
||||
"o4-mini-deep-research-2025-06-26",
|
||||
}
|
||||
for _, prefix := range openaiModelsPrefixes {
|
||||
if strings.HasPrefix(modelName, prefix) {
|
||||
@@ -126,56 +162,85 @@ func (o *Client) NeedsRawMode(modelName string) bool {
|
||||
return slices.Contains(openAIModelsNeedingRaw, modelName)
|
||||
}
|
||||
|
||||
func (o *Client) buildChatCompletionParams(
|
||||
func (o *Client) buildResponseParams(
|
||||
inputMsgs []*chat.ChatCompletionMessage, opts *common.ChatOptions,
|
||||
) (ret openai.ChatCompletionNewParams) {
|
||||
) (ret responses.ResponseNewParams) {
|
||||
|
||||
// Create a new slice for messages to be sent, converting from []*Msg to []Msg.
|
||||
// This also serves as a mutable copy for provider-specific modifications.
|
||||
messagesForRequest := make([]openai.ChatCompletionMessageParamUnion, len(inputMsgs))
|
||||
items := make([]responses.ResponseInputItemUnionParam, len(inputMsgs))
|
||||
for i, msgPtr := range inputMsgs {
|
||||
msg := *msgPtr // copy
|
||||
// Provider-specific modification for DeepSeek:
|
||||
msg := *msgPtr
|
||||
if strings.Contains(opts.Model, "deepseek") && len(inputMsgs) == 1 && msg.Role == chat.ChatMessageRoleSystem {
|
||||
msg.Role = chat.ChatMessageRoleUser
|
||||
}
|
||||
messagesForRequest[i] = convertMessage(msg)
|
||||
items[i] = convertMessage(msg)
|
||||
}
|
||||
ret = openai.ChatCompletionNewParams{
|
||||
Model: openai.ChatModel(opts.Model),
|
||||
Messages: messagesForRequest,
|
||||
|
||||
ret = responses.ResponseNewParams{
|
||||
Model: shared.ResponsesModel(opts.Model),
|
||||
Input: responses.ResponseNewParamsInputUnion{
|
||||
OfInputItemList: items,
|
||||
},
|
||||
}
|
||||
|
||||
if !opts.Raw {
|
||||
ret.Temperature = openai.Float(opts.Temperature)
|
||||
ret.TopP = openai.Float(opts.TopP)
|
||||
ret.PresencePenalty = openai.Float(opts.PresencePenalty)
|
||||
ret.FrequencyPenalty = openai.Float(opts.FrequencyPenalty)
|
||||
if opts.MaxTokens != 0 {
|
||||
ret.MaxOutputTokens = openai.Int(int64(opts.MaxTokens))
|
||||
}
|
||||
|
||||
// Add parameters not officially supported by Responses API as extra fields
|
||||
extraFields := make(map[string]any)
|
||||
if opts.PresencePenalty != 0 {
|
||||
extraFields["presence_penalty"] = opts.PresencePenalty
|
||||
}
|
||||
if opts.FrequencyPenalty != 0 {
|
||||
extraFields["frequency_penalty"] = opts.FrequencyPenalty
|
||||
}
|
||||
if opts.Seed != 0 {
|
||||
ret.Seed = openai.Int(int64(opts.Seed))
|
||||
extraFields["seed"] = opts.Seed
|
||||
}
|
||||
if len(extraFields) > 0 {
|
||||
ret.SetExtraFields(extraFields)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func convertMessage(msg chat.ChatCompletionMessage) openai.ChatCompletionMessageParamUnion {
|
||||
switch msg.Role {
|
||||
case chat.ChatMessageRoleSystem:
|
||||
return openai.SystemMessage(msg.Content)
|
||||
case chat.ChatMessageRoleUser:
|
||||
if len(msg.MultiContent) > 0 {
|
||||
var parts []openai.ChatCompletionContentPartUnionParam
|
||||
for _, p := range msg.MultiContent {
|
||||
switch p.Type {
|
||||
case chat.ChatMessagePartTypeText:
|
||||
parts = append(parts, openai.TextContentPart(p.Text))
|
||||
case chat.ChatMessagePartTypeImageURL:
|
||||
parts = append(parts, openai.ImageContentPart(openai.ChatCompletionContentPartImageImageURLParam{URL: p.ImageURL.URL}))
|
||||
func convertMessage(msg chat.ChatCompletionMessage) responses.ResponseInputItemUnionParam {
|
||||
result := convertMessageCommon(msg)
|
||||
role := responses.EasyInputMessageRole(result.Role)
|
||||
|
||||
if result.HasMultiContent {
|
||||
var parts []responses.ResponseInputContentUnionParam
|
||||
for _, p := range result.MultiContent {
|
||||
switch p.Type {
|
||||
case chat.ChatMessagePartTypeText:
|
||||
parts = append(parts, responses.ResponseInputContentParamOfInputText(p.Text))
|
||||
case chat.ChatMessagePartTypeImageURL:
|
||||
part := responses.ResponseInputContentParamOfInputImage(responses.ResponseInputImageDetailAuto)
|
||||
if part.OfInputImage != nil {
|
||||
part.OfInputImage.ImageURL = openai.String(p.ImageURL.URL)
|
||||
}
|
||||
parts = append(parts, part)
|
||||
}
|
||||
}
|
||||
contentList := responses.ResponseInputMessageContentListParam(parts)
|
||||
return responses.ResponseInputItemParamOfMessage(contentList, role)
|
||||
}
|
||||
return responses.ResponseInputItemParamOfMessage(result.Content, role)
|
||||
}
|
||||
|
||||
func (o *Client) extractText(resp *responses.Response) (ret string) {
|
||||
for _, item := range resp.Output {
|
||||
if item.Type == "message" {
|
||||
for _, c := range item.Content {
|
||||
if c.Type == "output_text" {
|
||||
ret += c.AsOutputText().Text
|
||||
}
|
||||
}
|
||||
return openai.UserMessage(parts)
|
||||
break
|
||||
}
|
||||
return openai.UserMessage(msg.Content)
|
||||
default:
|
||||
return openai.AssistantMessage(msg.Content)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -6,10 +6,11 @@ import (
|
||||
"github.com/danielmiessler/fabric/chat"
|
||||
"github.com/danielmiessler/fabric/common"
|
||||
openai "github.com/openai/openai-go"
|
||||
"github.com/openai/openai-go/shared"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestBuildChatCompletionRequestPinSeed(t *testing.T) {
|
||||
func TestBuildResponseRequestWithMaxTokens(t *testing.T) {
|
||||
|
||||
var msgs []*chat.ChatCompletionMessage
|
||||
|
||||
@@ -21,25 +22,21 @@ func TestBuildChatCompletionRequestPinSeed(t *testing.T) {
|
||||
}
|
||||
|
||||
opts := &common.ChatOptions{
|
||||
Temperature: 0.8,
|
||||
TopP: 0.9,
|
||||
PresencePenalty: 0.1,
|
||||
FrequencyPenalty: 0.2,
|
||||
Raw: false,
|
||||
Seed: 1,
|
||||
Temperature: 0.8,
|
||||
TopP: 0.9,
|
||||
Raw: false,
|
||||
MaxTokens: 50,
|
||||
}
|
||||
|
||||
var client = NewClient()
|
||||
request := client.buildChatCompletionParams(msgs, opts)
|
||||
assert.Equal(t, openai.ChatModel(opts.Model), request.Model)
|
||||
request := client.buildResponseParams(msgs, opts)
|
||||
assert.Equal(t, shared.ResponsesModel(opts.Model), request.Model)
|
||||
assert.Equal(t, openai.Float(opts.Temperature), request.Temperature)
|
||||
assert.Equal(t, openai.Float(opts.TopP), request.TopP)
|
||||
assert.Equal(t, openai.Float(opts.PresencePenalty), request.PresencePenalty)
|
||||
assert.Equal(t, openai.Float(opts.FrequencyPenalty), request.FrequencyPenalty)
|
||||
assert.Equal(t, openai.Int(int64(opts.Seed)), request.Seed)
|
||||
assert.Equal(t, openai.Int(int64(opts.MaxTokens)), request.MaxOutputTokens)
|
||||
}
|
||||
|
||||
func TestBuildChatCompletionRequestNilSeed(t *testing.T) {
|
||||
func TestBuildResponseRequestNoMaxTokens(t *testing.T) {
|
||||
|
||||
var msgs []*chat.ChatCompletionMessage
|
||||
|
||||
@@ -51,20 +48,15 @@ func TestBuildChatCompletionRequestNilSeed(t *testing.T) {
|
||||
}
|
||||
|
||||
opts := &common.ChatOptions{
|
||||
Temperature: 0.8,
|
||||
TopP: 0.9,
|
||||
PresencePenalty: 0.1,
|
||||
FrequencyPenalty: 0.2,
|
||||
Raw: false,
|
||||
Seed: 0,
|
||||
Temperature: 0.8,
|
||||
TopP: 0.9,
|
||||
Raw: false,
|
||||
}
|
||||
|
||||
var client = NewClient()
|
||||
request := client.buildChatCompletionParams(msgs, opts)
|
||||
assert.Equal(t, openai.ChatModel(opts.Model), request.Model)
|
||||
request := client.buildResponseParams(msgs, opts)
|
||||
assert.Equal(t, shared.ResponsesModel(opts.Model), request.Model)
|
||||
assert.Equal(t, openai.Float(opts.Temperature), request.Temperature)
|
||||
assert.Equal(t, openai.Float(opts.TopP), request.TopP)
|
||||
assert.Equal(t, openai.Float(opts.PresencePenalty), request.PresencePenalty)
|
||||
assert.Equal(t, openai.Float(opts.FrequencyPenalty), request.FrequencyPenalty)
|
||||
assert.False(t, request.Seed.Valid())
|
||||
assert.False(t, request.MaxOutputTokens.Valid())
|
||||
}
|
||||
|
||||
@@ -9,8 +9,9 @@ import (
|
||||
|
||||
// ProviderConfig defines the configuration for an OpenAI-compatible API provider
|
||||
type ProviderConfig struct {
|
||||
Name string
|
||||
BaseURL string
|
||||
Name string
|
||||
BaseURL string
|
||||
ImplementsResponses bool // Whether the provider supports OpenAI's new Responses API
|
||||
}
|
||||
|
||||
// Client is the common structure for all OpenAI-compatible providers
|
||||
@@ -21,51 +22,66 @@ type Client struct {
|
||||
// NewClient creates a new OpenAI-compatible client for the specified provider
|
||||
func NewClient(providerConfig ProviderConfig) *Client {
|
||||
client := &Client{}
|
||||
client.Client = openai.NewClientCompatible(providerConfig.Name, providerConfig.BaseURL, nil)
|
||||
client.Client = openai.NewClientCompatibleWithResponses(
|
||||
providerConfig.Name,
|
||||
providerConfig.BaseURL,
|
||||
providerConfig.ImplementsResponses,
|
||||
nil,
|
||||
)
|
||||
return client
|
||||
}
|
||||
|
||||
// ProviderMap is a map of provider name to ProviderConfig for O(1) lookup
|
||||
var ProviderMap = map[string]ProviderConfig{
|
||||
"AIML": {
|
||||
Name: "AIML",
|
||||
BaseURL: "https://api.aimlapi.com/v1",
|
||||
Name: "AIML",
|
||||
BaseURL: "https://api.aimlapi.com/v1",
|
||||
ImplementsResponses: false,
|
||||
},
|
||||
"Cerebras": {
|
||||
Name: "Cerebras",
|
||||
BaseURL: "https://api.cerebras.ai/v1",
|
||||
Name: "Cerebras",
|
||||
BaseURL: "https://api.cerebras.ai/v1",
|
||||
ImplementsResponses: false,
|
||||
},
|
||||
"DeepSeek": {
|
||||
Name: "DeepSeek",
|
||||
BaseURL: "https://api.deepseek.com",
|
||||
Name: "DeepSeek",
|
||||
BaseURL: "https://api.deepseek.com",
|
||||
ImplementsResponses: false,
|
||||
},
|
||||
"GrokAI": {
|
||||
Name: "GrokAI",
|
||||
BaseURL: "https://api.x.ai/v1",
|
||||
Name: "GrokAI",
|
||||
BaseURL: "https://api.x.ai/v1",
|
||||
ImplementsResponses: false,
|
||||
},
|
||||
"Groq": {
|
||||
Name: "Groq",
|
||||
BaseURL: "https://api.groq.com/openai/v1",
|
||||
Name: "Groq",
|
||||
BaseURL: "https://api.groq.com/openai/v1",
|
||||
ImplementsResponses: false,
|
||||
},
|
||||
"Langdock": {
|
||||
Name: "Langdock",
|
||||
BaseURL: "https://api.langdock.com/openai/{{REGION=us}}/v1",
|
||||
Name: "Langdock",
|
||||
BaseURL: "https://api.langdock.com/openai/{{REGION=us}}/v1",
|
||||
ImplementsResponses: false,
|
||||
},
|
||||
"LiteLLM": {
|
||||
Name: "LiteLLM",
|
||||
BaseURL: "http://localhost:4000",
|
||||
Name: "LiteLLM",
|
||||
BaseURL: "http://localhost:4000",
|
||||
ImplementsResponses: false,
|
||||
},
|
||||
"Mistral": {
|
||||
Name: "Mistral",
|
||||
BaseURL: "https://api.mistral.ai/v1",
|
||||
Name: "Mistral",
|
||||
BaseURL: "https://api.mistral.ai/v1",
|
||||
ImplementsResponses: false,
|
||||
},
|
||||
"OpenRouter": {
|
||||
Name: "OpenRouter",
|
||||
BaseURL: "https://openrouter.ai/api/v1",
|
||||
Name: "OpenRouter",
|
||||
BaseURL: "https://openrouter.ai/api/v1",
|
||||
ImplementsResponses: false,
|
||||
},
|
||||
"SiliconCloud": {
|
||||
Name: "SiliconCloud",
|
||||
BaseURL: "https://api.siliconflow.cn/v1",
|
||||
Name: "SiliconCloud",
|
||||
BaseURL: "https://api.siliconflow.cn/v1",
|
||||
ImplementsResponses: false,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -152,7 +152,6 @@ func (o *Setting) FillEnvFileContent(buffer *bytes.Buffer) {
|
||||
}
|
||||
buffer.WriteString("\n")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ParseBoolElseFalse(val string) (ret bool) {
|
||||
@@ -279,7 +278,6 @@ func (o Settings) FillEnvFileContent(buffer *bytes.Buffer) {
|
||||
for _, setting := range o {
|
||||
setting.FillEnvFileContent(buffer)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type SetupQuestions []*SetupQuestion
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
package main
|
||||
|
||||
var version = "v1.4.221"
|
||||
var version = "v1.4.225"
|
||||
|
||||
@@ -1710,7 +1710,7 @@
|
||||
},
|
||||
{
|
||||
"patternName": "analyze_bill_short",
|
||||
"description": "Consended - Analyze a legislative bill and implications.",
|
||||
"description": "Condensed - Analyze a legislative bill and implications.",
|
||||
"tags": [
|
||||
"ANALYSIS",
|
||||
"BILL"
|
||||
@@ -1815,6 +1815,35 @@
|
||||
"WRITING",
|
||||
"CREATIVITY"
|
||||
]
|
||||
},
|
||||
{
|
||||
"patternName": "extract_alpha",
|
||||
"description": "Extracts the most novel and surprising ideas (\"alpha\") from content, inspired by information theory.",
|
||||
"tags": [
|
||||
"EXTRACT",
|
||||
"ANALYSIS",
|
||||
"CR THINKING",
|
||||
"WISDOM"
|
||||
]
|
||||
},
|
||||
{
|
||||
"patternName": "extract_mcp_servers",
|
||||
"description": "Analyzes content to identify and extract detailed information about Model Context Protocol (MCP) servers.",
|
||||
"tags": [
|
||||
"ANALYSIS",
|
||||
"EXTRACT",
|
||||
"DEVELOPMENT",
|
||||
"AI"
|
||||
]
|
||||
},
|
||||
{
|
||||
"patternName": "review_code",
|
||||
"description": "Performs a comprehensive code review, providing detailed feedback on correctness, security, and performance.",
|
||||
"tags": [
|
||||
"DEVELOPMENT",
|
||||
"REVIEW",
|
||||
"SECURITY"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user