diff --git a/examples/filters/langfuse_filter_pipeline.py b/examples/filters/langfuse_filter_pipeline.py index 66dfcad..7cc373f 100644 --- a/examples/filters/langfuse_filter_pipeline.py +++ b/examples/filters/langfuse_filter_pipeline.py @@ -11,6 +11,7 @@ requirements: langfuse from typing import List, Optional from schemas import OpenAIChatMessage import os +import uuid from utils.pipelines.main import get_last_user_message, get_last_assistant_message from pydantic import BaseModel @@ -20,64 +21,36 @@ from langfuse.api.resources.commons.errors.unauthorized_error import Unauthorize class Pipeline: class Valves(BaseModel): - # List target pipeline ids (models) that this filter will be connected to. - # If you want to connect this filter to all pipelines, you can set pipelines to ["*"] - # e.g. ["llama3:latest", "gpt-3.5-turbo"] pipelines: List[str] = [] - - # Assign a priority level to the filter pipeline. - # The priority level determines the order in which the filter pipelines are executed. - # The lower the number, the higher the priority. priority: int = 0 - - # Valves secret_key: str public_key: str host: str def __init__(self): - # Pipeline filters are only compatible with Open WebUI - # You can think of filter pipeline as a middleware that can be used to edit the form data before it is sent to the OpenAI API. self.type = "filter" - - # Optionally, you can set the id and name of the pipeline. - # Best practice is to not specify the id so that it can be automatically inferred from the filename, so that users can install multiple versions of the same pipeline. - # The identifier must be unique across all pipelines. - # The identifier must be an alphanumeric string that can include underscores or hyphens. It cannot contain spaces, special characters, slashes, or backslashes. - # self.id = "langfuse_filter_pipeline" self.name = "Langfuse Filter" - - # Initialize self.valves = self.Valves( **{ - "pipelines": ["*"], # Connect to all pipelines + "pipelines": ["*"], "secret_key": os.getenv("LANGFUSE_SECRET_KEY", "your-secret-key-here"), "public_key": os.getenv("LANGFUSE_PUBLIC_KEY", "your-public-key-here"), "host": os.getenv("LANGFUSE_HOST", "https://cloud.langfuse.com"), } ) - self.langfuse = None self.chat_generations = {} - pass async def on_startup(self): - # This function is called when the server is started. print(f"on_startup:{__name__}") self.set_langfuse() - pass async def on_shutdown(self): - # This function is called when the server is stopped. print(f"on_shutdown:{__name__}") self.langfuse.flush() - pass async def on_valves_updated(self): - # This function is called when the valves are updated. - self.set_langfuse() - pass def set_langfuse(self): try: @@ -97,6 +70,22 @@ class Pipeline: async def inlet(self, body: dict, user: Optional[dict] = None) -> dict: print(f"inlet:{__name__}") + print(f"Received body: {body}") + print(f"User: {user}") + + # Check for presence of required keys and generate chat_id if missing + if "chat_id" not in body: + unique_id = f"SYSTEM MESSAGE {uuid.uuid4()}" + body["chat_id"] = unique_id + print(f"chat_id was missing, set to: {unique_id}") + + required_keys = ["model", "messages"] + missing_keys = [key for key in required_keys if key not in body] + + if missing_keys: + error_message = f"Error: Missing keys in the request body: {', '.join(missing_keys)}" + print(error_message) + raise ValueError(error_message) trace = self.langfuse.trace( name=f"filter:{__name__}", @@ -128,9 +117,6 @@ class Pipeline: user_message = get_last_user_message(body["messages"]) generated_message = get_last_assistant_message(body["messages"]) - # Update usage cost based on the length of the input and output messages - # Below does not reflect the actual cost of the API - # You can adjust the cost based on your requirements generation.end( output=generated_message, usage={ diff --git a/examples/pipelines/providers/google_manifold_pipeline.py b/examples/pipelines/providers/google_manifold_pipeline.py index 9c8ae28..8e71bdd 100644 --- a/examples/pipelines/providers/google_manifold_pipeline.py +++ b/examples/pipelines/providers/google_manifold_pipeline.py @@ -2,7 +2,7 @@ title: Google GenAI Manifold Pipeline author: Marc Lopez (refactor by justinh-rahb) date: 2024-06-06 -version: 1.2 +version: 1.3 license: MIT description: A pipeline for generating text using Google's GenAI models in Open-WebUI. requirements: google-generativeai @@ -127,11 +127,14 @@ class Pipeline: "role": "user" if message["role"] == "user" else "model", "parts": [{"text": message["content"]}] }) - - if system_message: - contents.insert(0, {"role": "user", "parts": [{"text": f"System: {system_message}"}]}) - - model = genai.GenerativeModel(model_name=model_id) + + if "gemini-1.5" in model_id: + model = genai.GenerativeModel(model_name=model_id, system_instruction=system_message) + else: + if system_message: + contents.insert(0, {"role": "user", "parts": [{"text": f"System: {system_message}"}]}) + + model = genai.GenerativeModel(model_name=model_id) generation_config = GenerationConfig( temperature=body.get("temperature", 0.7), diff --git a/examples/pipelines/providers/litellm_manifold_pipeline.py b/examples/pipelines/providers/litellm_manifold_pipeline.py index 79609eb..904a9f0 100644 --- a/examples/pipelines/providers/litellm_manifold_pipeline.py +++ b/examples/pipelines/providers/litellm_manifold_pipeline.py @@ -2,7 +2,7 @@ title: LiteLLM Manifold Pipeline author: open-webui date: 2024-05-30 -version: 1.0 +version: 1.0.1 license: MIT description: A manifold pipeline that uses LiteLLM. """ @@ -46,12 +46,15 @@ class Pipeline: "LITELLM_PIPELINE_DEBUG": os.getenv("LITELLM_PIPELINE_DEBUG", False), } ) - self.pipelines = [] + # Get models on initialization + self.pipelines = self.get_litellm_models() pass async def on_startup(self): # This function is called when the server is started. print(f"on_startup:{__name__}") + # Get models on startup + self.pipelines = self.get_litellm_models() pass async def on_shutdown(self): @@ -85,7 +88,7 @@ class Pipeline: for model in models["data"] ] except Exception as e: - print(f"Error: {e}") + print(f"Error fetching models from LiteLLM: {e}") return [ { "id": "error", @@ -93,6 +96,7 @@ class Pipeline: }, ] else: + print("LITELLM_BASE_URL not set. Please configure it in the valves.") return [] def pipe( diff --git a/examples/pipelines/providers/perplexity_manifold_pipeline.py b/examples/pipelines/providers/perplexity_manifold_pipeline.py index a5d6c51..2d87aff 100644 --- a/examples/pipelines/providers/perplexity_manifold_pipeline.py +++ b/examples/pipelines/providers/perplexity_manifold_pipeline.py @@ -3,6 +3,9 @@ from pydantic import BaseModel import os import requests +from utils.pipelines.main import pop_system_message + + class Pipeline: class Valves(BaseModel): PERPLEXITY_API_BASE_URL: str = "https://api.perplexity.ai" @@ -26,14 +29,28 @@ class Pipeline: # List of models self.pipelines = [ - {"id": "llama-3-sonar-large-32k-online", "name": "Llama 3 Sonar Large 32K Online"}, - {"id": "llama-3-sonar-small-32k-online", "name": "Llama 3 Sonar Small 32K Online"}, - {"id": "llama-3-sonar-large-32k-chat", "name": "Llama 3 Sonar Large 32K Chat"}, - {"id": "llama-3-sonar-small-32k-chat", "name": "Llama 3 Sonar Small 32K Chat"}, - {"id": "llama-3-8b-instruct", "name": "Llama 3 8B Instruct"}, - {"id": "llama-3-70b-instruct", "name": "Llama 3 70B Instruct"}, - {"id": "mixtral-8x7b-instruct", "name": "Mixtral 8x7B Instruct"}, - {"id": "related", "name": "Related"} + { + "id": "llama-3.1-sonar-large-128k-online", + "name": "Llama 3.1 Sonar Large 128k Online" + }, + { + "id": "llama-3.1-sonar-small-128k-online", + "name": "Llama 3.1 Sonar Small 128k Online" + }, + { + "id": "llama-3.1-sonar-large-128k-chat", + "name": "Llama 3.1 Sonar Large 128k Chat" + }, + { + "id": "llama-3.1-sonar-small-128k-chat", + "name": "Llama 3.1 Sonar Small 128k Chat" + }, + { + "id": "llama-3.1-8b-instruct", "name": "Llama 3.1 8B Instruct" + }, + { + "id": "llama-3.1-70b-instruct", "name": "Llama 3.1 70B Instruct" + } ] pass @@ -59,6 +76,12 @@ class Pipeline: # This is where you can add your custom pipelines like RAG. print(f"pipe:{__name__}") + system_message, messages = pop_system_message(messages) + system_prompt = "You are a helpful assistant." + if system_message is not None: + system_prompt = system_message["content"] + + print(system_prompt) print(messages) print(user_message) @@ -71,8 +94,8 @@ class Pipeline: payload = { "model": model_id, "messages": [ - {"role": "system", "content": "Be precise and concise."}, - {"role": "user", "content": user_message} + {"role": "system", "content": system_prompt}, + *messages ], "stream": body.get("stream", True), "return_citations": True, @@ -124,17 +147,21 @@ class Pipeline: except Exception as e: return f"Error: {e}" + if __name__ == "__main__": import argparse parser = argparse.ArgumentParser(description="Perplexity API Client") - parser.add_argument("--api-key", type=str, required=True, help="API key for Perplexity") - parser.add_argument("--prompt", type=str, required=True, help="Prompt to send to the Perplexity API") + parser.add_argument("--api-key", type=str, required=True, + help="API key for Perplexity") + parser.add_argument("--prompt", type=str, required=True, + help="Prompt to send to the Perplexity API") args = parser.parse_args() pipeline = Pipeline() pipeline.valves.PERPLEXITY_API_KEY = args.api_key - response = pipeline.pipe(user_message=args.prompt, model_id="llama-3-sonar-large-32k-online", messages=[], body={"stream": False}) + response = pipeline.pipe( + user_message=args.prompt, model_id="llama-3-sonar-large-32k-online", messages=[], body={"stream": False}) print("Response:", response) diff --git a/main.py b/main.py index 7c6d7e9..33c0499 100644 --- a/main.py +++ b/main.py @@ -26,6 +26,7 @@ import time import json import uuid import sys +import subprocess from config import API_KEY, PIPELINES_DIR @@ -105,12 +106,45 @@ def get_all_pipelines(): return pipelines +def parse_frontmatter(content): + frontmatter = {} + for line in content.split('\n'): + if ':' in line: + key, value = line.split(':', 1) + frontmatter[key.strip().lower()] = value.strip() + return frontmatter + +def install_frontmatter_requirements(requirements): + if requirements: + req_list = [req.strip() for req in requirements.split(',')] + for req in req_list: + print(f"Installing requirement: {req}") + subprocess.check_call([sys.executable, "-m", "pip", "install", req]) + else: + print("No requirements found in frontmatter.") async def load_module_from_path(module_name, module_path): - spec = importlib.util.spec_from_file_location(module_name, module_path) - module = importlib.util.module_from_spec(spec) try: + # Read the module content + with open(module_path, 'r') as file: + content = file.read() + + # Parse frontmatter + frontmatter = {} + if content.startswith('"""'): + end = content.find('"""', 3) + if end != -1: + frontmatter_content = content[3:end] + frontmatter = parse_frontmatter(frontmatter_content) + + # Install requirements if specified + if 'requirements' in frontmatter: + install_frontmatter_requirements(frontmatter['requirements']) + + # Load the module + spec = importlib.util.spec_from_file_location(module_name, module_path) + module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) print(f"Loaded module: {module.__name__}") if hasattr(module, "Pipeline"):