initial commit to push copy to codeberg
This commit is contained in:
commit
751bfe568c
20 changed files with 1364 additions and 0 deletions
18
Dockerfile
Normal file
18
Dockerfile
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
FROM python:3.12-bookworm
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
build-essential \
|
||||
cmake \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
COPY changelog2 /app/changelog2
|
||||
|
||||
WORKDIR /app/changelog2
|
||||
|
||||
ENTRYPOINT ["python", "main.py"]
|
||||
|
||||
120
README.md
Normal file
120
README.md
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
# Changelog Generator Tool
|
||||
|
||||
This project provides an AI-powered tool that automates the generation of changelogs by collecting information from Git logs and Redmine tickets. It uses a crew of AI agents to improve the quality and efficiency of the final changelog.
|
||||
|
||||
## Project Overview
|
||||
|
||||
The system uses multiple AI agents, each responsible for a specific part of the changelog generation process. This tool is designed to:
|
||||
|
||||
- Gather data from Git logs and Redmine tickets.
|
||||
- Analyze and categorize the collected information.
|
||||
- Generate a well-formatted changelog or release notes.
|
||||
- Ensure the output is consistent with project guidelines and requirements.
|
||||
|
||||
## Key Components
|
||||
|
||||
### Main Script (`main.py`)
|
||||
|
||||
The main entry point of the tool. It handles command-line arguments, sets up the environment, and initiates the changelog generation process.
|
||||
|
||||
### Crew Definition (`crew.py`)
|
||||
|
||||
Defines the AI agents and their tasks using the `crewai` framework. It includes:
|
||||
|
||||
- `GitlogAnalyst`: Analyzes Git commit logs.
|
||||
- `RedmineAnalyst`: Retrieves and analyzes Redmine issues.
|
||||
- `WritingAgent`: Generates the final changelog or release notes.
|
||||
|
||||
### Configuration
|
||||
|
||||
- `config/config.py`: Parses configuration settings.
|
||||
- `config/agents.yaml`: Defines agent configurations.
|
||||
- `config/tasks.yaml`: Defines task configurations.
|
||||
|
||||
### Tools
|
||||
|
||||
- `tools/tools.py`: Contains custom tools for retrieving Git commits and Redmine issues.
|
||||
|
||||
### Data
|
||||
|
||||
- `data/CodeOfConduct.md`: Contains the project's code of conduct (used for context).
|
||||
- `data/template2.txt`: Changelog template used by the writing agent.
|
||||
|
||||
## Usage
|
||||
|
||||
1. **Environment Setup for development**:
|
||||
|
||||
- This project uses Devbox for development environment management and Poetry for dependency management.
|
||||
- Set up the environment using:
|
||||
```
|
||||
devbox shell
|
||||
poetry install
|
||||
```
|
||||
|
||||
2. **Running the Tool**:
|
||||
|
||||
- The tool is started using a shell script that manages Docker containers.
|
||||
- Basic usage:
|
||||
```
|
||||
./create_changelog.sh [options]
|
||||
```
|
||||
- Options:
|
||||
- `--project`: Specify the Redmine project ID
|
||||
- `--version`: Specify the version ID
|
||||
- `--repo`: Path to the Git repository (optional)
|
||||
- `--type`: Type of output (Changelog/ReleaseNotes)
|
||||
- `--local`: Use local LLM instead of OpenAI (optional)
|
||||
- If an error occurs that the docker volume ollama-local cannot be found, create it with `docker volume create ollama-local`
|
||||
3. **Example Usage**:
|
||||
|
||||
```bash
|
||||
./create_changelog.sh -projects 123 -versions 456 -type Changelog -repo /path/to/repo
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Python 3.x
|
||||
- Poetry for dependency management
|
||||
- Devbox for development environment
|
||||
- Docker for running LLM services
|
||||
- External services:
|
||||
- Redmine API
|
||||
- Git repository
|
||||
- OpenAI API (optional)
|
||||
- Local LLM (optional, e.g., Ollama)
|
||||
|
||||
## Configuration
|
||||
|
||||
The project uses a TOML configuration file named changelog-config.toml for storing essential settings. This file should contain the following entries:
|
||||
|
||||
```toml
|
||||
[DEFAULT]
|
||||
OPENAI_API_KEY = your_openai_api_key_here
|
||||
REDMINE_API_KEY = your_redmine_api_key_here
|
||||
REDMINE_HOST = https://your_redmine_host.com
|
||||
GIT_REPO_PATH = /path/to/your/git/repo
|
||||
```
|
||||
|
||||
### Configuration File Location
|
||||
|
||||
The config.py script searches for the changelog-config.toml file in the following locations, in order:
|
||||
|
||||
- Current working directory
|
||||
- changelog2 subdirectory of the current working directory
|
||||
- '.config' subdirectory of the current working directory
|
||||
- User's home directory
|
||||
- Location specified by the CHANGELOG_CONFIG environment variable
|
||||
|
||||
Make sure to create the changelog-config.toml file with your actual API keys and settings before running the tool. Keep this file secure and do not commit it to version control.
|
||||
|
||||
However, a copy of changelog-config.toml should be located in the changelog2 subfolder for execution in the docker container.
|
||||
|
||||
## Development
|
||||
|
||||
- To add new dependencies: `poetry add <package_name>`
|
||||
- To update dependencies: `poetry update`
|
||||
|
||||
## Notes
|
||||
|
||||
- The tool can use either OpenAI's models or a local LLM (like Ollama) for text generation.
|
||||
- Ensure all necessary Docker containers are running before starting the tool.
|
||||
0
changelog2/__init__.py
Normal file
0
changelog2/__init__.py
Normal file
4
changelog2/__main__.py
Normal file
4
changelog2/__main__.py
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
from .main import main
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
29
changelog2/config/agents.yaml
Normal file
29
changelog2/config/agents.yaml
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
gitlog_analyst:
|
||||
role: >
|
||||
Git Log Analyst
|
||||
goal: >
|
||||
Collect all relevant informations from Git logs
|
||||
backstory: >
|
||||
You specialize in analyzing Git Logs. You extract important Information such as commit messages,
|
||||
dates and categorize the commits by new, changed, and fixed based on their commit message.
|
||||
|
||||
redmine_analyst:
|
||||
role: >
|
||||
Redmine Analyst
|
||||
goal: >
|
||||
Gather information from Redmine tickets
|
||||
backstory: >
|
||||
You are an expert in Redmine. You collect relevant information from Redmine tickets for the changelog
|
||||
|
||||
writing_agent:
|
||||
role: >
|
||||
Technical Writer
|
||||
goal: >
|
||||
Transform technical information into clear, concise, and user-friendly documentation
|
||||
backstory: >
|
||||
You are an experienced technical writer with a strong background in software development
|
||||
and documentation. Your expertise lies in translating complex technical concepts into
|
||||
easily understandable content for various audiences, from end-users to developers.
|
||||
You have a keen eye for detail and a passion for creating documentation that enhances
|
||||
user experience and product understanding.
|
||||
|
||||
34
changelog2/config/config.py
Normal file
34
changelog2/config/config.py
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import configparser
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def find_config():
|
||||
possible_locations = [
|
||||
Path.cwd() / "changelog-config.toml",
|
||||
Path.cwd() / "changelog2" / "changelog-config.toml",
|
||||
Path.cwd() / ".config" / "changelog-config.toml",
|
||||
Path.cwd() / ".config" / "changelog-config.toml",
|
||||
Path.home() / "changelog-config.toml",
|
||||
Path(os.getenv("CHANGELOG_CONFIG", "")),
|
||||
]
|
||||
|
||||
for location in possible_locations:
|
||||
if location.is_file():
|
||||
return str(location)
|
||||
|
||||
raise FileNotFoundError("changelog-config.toml nicht gefunden")
|
||||
|
||||
|
||||
config_path = find_config()
|
||||
|
||||
|
||||
def parse_config():
|
||||
config = configparser.ConfigParser()
|
||||
config.read(config_path)
|
||||
return {
|
||||
"REDMINE_HOST": config["DEFAULT"]["REDMINE_HOST"],
|
||||
"REDMINE_API_KEY": config["DEFAULT"]["REDMINE_API_KEY"],
|
||||
"OPENAI_API_KEY": config["DEFAULT"]["OPENAI_API_KEY"],
|
||||
"GIT_REPO_PATH": config["DEFAULT"]["GIT_REPO_PATH"],
|
||||
}
|
||||
47
changelog2/config/tasks.yaml
Normal file
47
changelog2/config/tasks.yaml
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
gitlog_analysis_task:
|
||||
description: >
|
||||
Collect all relevant Git commits since the last tag for the projects {project_id} which are seperated by ",".
|
||||
Analyze the commits and categorize them according to new features, changes, and fixes.
|
||||
|
||||
Git Repo: {repo_path}
|
||||
expected_output: >
|
||||
A JSON string containing a list of relevant Git commits with their hash, date, message, a {changelog_type} entry understandable for end users
|
||||
and category (new feature, change, or fix).
|
||||
|
||||
redmine_analysis_task:
|
||||
description: >
|
||||
Collect all relevant Redmine tickets for the projects {project_id} and their versions {version_id}.
|
||||
Analyze the tickets and categorize them based on their type and importance.
|
||||
expected_output: >
|
||||
A JSON string containing a list of relevant Redmine tickets with their ID, subject, tracker,
|
||||
a {changelog_type} entry understandable for end users and a brief summary of their content and importance.
|
||||
|
||||
formatting_task:
|
||||
description: >
|
||||
Format the collected Git commits and Redmine tickets into a structured {changelog_type}.
|
||||
Ensure that the information is organized logically and easy to read.
|
||||
expected_output: >
|
||||
A well-formatted {changelog_type} text that includes both Git commits and Redmine tickets
|
||||
in a clear and organized manner, with proper categorization and highlighting of key changes.
|
||||
|
||||
writing_task:
|
||||
description: >
|
||||
Using the information from gitlog and redmine tickets, create a user-friendly and informative {changelog_type} document.
|
||||
Translate technical details into clear, concise language suitable for both technical
|
||||
and non-technical readers. Highlight key features, changes, and fixes.
|
||||
expected_output: >
|
||||
A polished {changelog_type} document that effectively communicates all relevant changes,
|
||||
improvements, and fixes in a way that enhances user understanding and engagement.
|
||||
The document should maintain consistency with the rules provided by the RAG tool and should fit the template from the template_tool
|
||||
while accurately reflecting the current changelog's content.
|
||||
instructions: >
|
||||
1. Review the template provided by the template tool.
|
||||
2. Identify the key sections and formatting used.
|
||||
3. Analyze the content of the gitlog and redmine issues.
|
||||
4. Structure the new changelog following the templates format, including similar sections.
|
||||
5. Translate technical details into user-friendly language.
|
||||
6. Ensure all significant changes, features, and fixes are included and highlighted.
|
||||
7. Maintain a consistent tone and style throughout the document.
|
||||
8. Double-check that the final document is informative, clear, and engaging for all readers.
|
||||
9. Skip issues/log entries not relevant for the user
|
||||
10. Do not include any additional output
|
||||
153
changelog2/crew.py
Normal file
153
changelog2/crew.py
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
import os
|
||||
from typing import List
|
||||
|
||||
from config.config import parse_config
|
||||
from crewai import Agent, Crew, Process, Task
|
||||
from crewai.project import CrewBase, agent, crew, task
|
||||
from crewai.tasks.conditional_task import ConditionalTask
|
||||
from crewai_tools import FileReadTool
|
||||
from langchain_ollama import ChatOllama
|
||||
from langchain_openai import ChatOpenAI
|
||||
from pydantic import BaseModel
|
||||
from tools.tools import (
|
||||
get_categorized_commits_since_last_tag,
|
||||
get_commits_since_last_tag,
|
||||
get_redmine_issues,
|
||||
)
|
||||
|
||||
config = parse_config()
|
||||
os.environ["OPENAI_API_KEY"] = config["OPENAI_API_KEY"]
|
||||
rag_tool = FileReadTool(file_path="./data/CodeOfConduct.md")
|
||||
template_tool = FileReadTool(file_path="./data/template2.txt")
|
||||
llmLocal = ChatOllama(model="gemma2", base_url="http://ollama:11434")
|
||||
llmOpenAI = ChatOpenAI(
|
||||
base_url="https://api.openai.com/v1/",
|
||||
model_name="gpt-4o",
|
||||
temperature=0.4,
|
||||
)
|
||||
|
||||
|
||||
@CrewBase
|
||||
class ChangelogCrew:
|
||||
"""Changelog writing crew"""
|
||||
|
||||
agents_config = "config/agents.yaml"
|
||||
tasks_config = "config/tasks.yaml"
|
||||
|
||||
def __init__(self, git_task: bool = False, local: bool = True):
|
||||
self.git_task = git_task
|
||||
self.local = local
|
||||
if self.local:
|
||||
os.environ["OPENAI_API_BASE"] = "http://ollama:11434"
|
||||
os.environ["OPENAI_MODEL_NAME"] = "gemma2"
|
||||
else:
|
||||
os.environ["OPENAI_API_BASE"] = "https://api.openai.com/v1/chat/completions"
|
||||
|
||||
@agent
|
||||
def gitlog_analyst(self) -> Agent:
|
||||
if self.local:
|
||||
llm = llmLocal
|
||||
else:
|
||||
llm = llmOpenAI
|
||||
return Agent(
|
||||
config=self.agents_config["gitlog_analyst"],
|
||||
tools=[get_categorized_commits_since_last_tag],
|
||||
llm=llm,
|
||||
max_iter=5,
|
||||
verbose=False,
|
||||
memory=False,
|
||||
)
|
||||
|
||||
@agent
|
||||
def redmine_analyst(self) -> Agent:
|
||||
if self.local:
|
||||
llm = llmLocal
|
||||
else:
|
||||
llm = llmOpenAI
|
||||
return Agent(
|
||||
config=self.agents_config["redmine_analyst"],
|
||||
tools=[get_redmine_issues],
|
||||
llm=llm,
|
||||
max_iter=5,
|
||||
verbose=False,
|
||||
memory=False,
|
||||
)
|
||||
|
||||
@agent
|
||||
def writing_agent(self) -> Agent:
|
||||
if self.local:
|
||||
llm = llmLocal
|
||||
else:
|
||||
llm = llmOpenAI
|
||||
with open("./data/template2.txt", "r") as template_file:
|
||||
template_content = template_file.read()
|
||||
return Agent(
|
||||
config=self.agents_config["writing_agent"],
|
||||
tools=[template_tool],
|
||||
llm=llm,
|
||||
max_iter=5,
|
||||
verbose=False,
|
||||
memory=False,
|
||||
template=template_content,
|
||||
)
|
||||
|
||||
@task
|
||||
def redmine_analysis_task(self) -> Task:
|
||||
return Task(
|
||||
config=self.tasks_config["redmine_analysis_task"],
|
||||
agent=self.redmine_analyst(),
|
||||
)
|
||||
|
||||
@task
|
||||
def gitlog_analysis_task(self) -> ConditionalTask:
|
||||
return ConditionalTask(
|
||||
config=self.tasks_config["gitlog_analysis_task"],
|
||||
condition=lambda context: self.git_task,
|
||||
agent=self.gitlog_analyst(),
|
||||
)
|
||||
|
||||
@task
|
||||
def writing_task(self) -> Task:
|
||||
context = []
|
||||
context.append(self.redmine_analysis_task())
|
||||
if self.git_task:
|
||||
context.append(self.gitlog_analysis_task())
|
||||
|
||||
return Task(
|
||||
config=self.tasks_config["writing_task"],
|
||||
agent=self.writing_agent(),
|
||||
context=context,
|
||||
)
|
||||
|
||||
@crew
|
||||
def crew(self) -> Crew:
|
||||
"""Creates the Changelog crew"""
|
||||
return Crew(
|
||||
agents=self.agents,
|
||||
tasks=self.tasks,
|
||||
process=Process.sequential,
|
||||
verbose=False,
|
||||
)
|
||||
|
||||
|
||||
class GitCommit(BaseModel):
|
||||
hash: str
|
||||
author: str
|
||||
date: str
|
||||
message: str
|
||||
category: str
|
||||
|
||||
|
||||
class RedmineTicket(BaseModel):
|
||||
id: int
|
||||
subject: str
|
||||
tracker: str
|
||||
summary: str
|
||||
|
||||
|
||||
class Changelog(BaseModel):
|
||||
version: str
|
||||
date: str
|
||||
changes: List[str]
|
||||
fixes: List[str]
|
||||
new_features: List[str]
|
||||
57
changelog2/data/CodeOfConduct.md
Normal file
57
changelog2/data/CodeOfConduct.md
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
# Code of Conduct für Änderungsprotokolle
|
||||
|
||||
## Allgemeine Richtlinien
|
||||
|
||||
1. Klarheit und Konsistenz: Alle Einträge müssen klar und konsistent formuliert sein, um Verwirrung zu vermeiden. Verwenden Sie klare, präzise Sprache und folgen Sie den unten aufgeführten Formatierungsrichtlinien.
|
||||
2. Abkürzungen und Akronyme: Verwenden Sie standardisierte Abkürzungen für Module und Komponenten. Eine Liste der häufig verwendeten Abkürzungen finden Sie im Abschnitt "Abkürzungen".
|
||||
3. Versionsnummern: Geben Sie die Versionsnummern der betroffenen Komponenten klar an. Verwenden Sie die Formatierung Komponente: alte Version -> neue Version.
|
||||
4. Kategorien von Änderungen: Gliedern Sie Änderungen in die Kategorien NEW, CHANGED, FIXED, und DOCUMENTATION. Jede Kategorie sollte klar vom Rest des Dokuments abgegrenzt sein.
|
||||
|
||||
## Struktur und Formatierung
|
||||
|
||||
### Titel und Metadaten
|
||||
|
||||
Titel: Verwenden Sie das Format = [Produktname] - Changelog. Beispiel: = TIXstream FX - Changelog.
|
||||
Website und Sprache: Geben Sie die Website und die Sprache an. Beispiel:
|
||||
|
||||
|
||||
### Copyright und Versionsinfo
|
||||
|
||||
- Copyright: Fügen Sie das Copyright-Dokument ein. Beispiel:
|
||||
|
||||
|
||||
- Versionsinfo: Geben Sie die Versionsinformationen ein. Beispiel:
|
||||
|
||||
|
||||
### Änderungsprotokoll
|
||||
|
||||
- Einführung: Erklären Sie den Zweck des Dokuments und die Bedeutung der einzelnen Einträge. Beispiel:
|
||||
|
||||
|
||||
- Versionseinträge: Jede Version sollte mit der Versionsnummer und dem Datum beginnen. Beispiel:
|
||||
|
||||
|
||||
- Neue Funktionen (NEW): Listen Sie neue Funktionen und Features auf. Verwenden Sie nummerierte Punkte oder Aufzählungen.
|
||||
- Änderungen (CHANGED): Dokumentieren Sie wesentliche Änderungen an bestehenden Funktionen oder Systemen.
|
||||
- Fehlerbehebungen (FIXED): Beschreiben Sie behobene Fehler und Probleme.
|
||||
- Dokumentation (DOCUMENTATION): Erwähnen Sie Änderungen an der Dokumentation oder neuen Dokumenten.
|
||||
|
||||
### Eintragsformatierung
|
||||
|
||||
- Klarheit der Beschreibungen: Beschreibungen sollten so präzise wie möglich sein. Vermeiden Sie unnötigen Jargon und halten Sie die Erklärungen einfach.
|
||||
- Verweise auf Tickets: Falls zutreffend, verwenden Sie Ticket-IDs zur Referenz. Beispiel: (_#12345_).
|
||||
- Modul- und Komponentennamen: Verwenden Sie die offiziellen Namen und Abkürzungen für Komponenten. Beispiel:
|
||||
|
||||
|
||||
### Abkürzungen
|
||||
|
||||
Use standard abbreviations for components:
|
||||
|
||||
- AM: Access Manager
|
||||
- FXweb: FX Web Tools
|
||||
- TCC: TIXEL Control Center
|
||||
- TXEC: TIXstream Express Client
|
||||
- TXEJM: TIXstream Express Job Manager
|
||||
- TXSFX: TIXstream FX
|
||||
- TXY: TIXway
|
||||
|
||||
57
changelog2/data/template2.txt
Normal file
57
changelog2/data/template2.txt
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
= {Produktname} - Changelog
|
||||
:website: http://www.example.com
|
||||
:lang: en
|
||||
:encoding: utf-8
|
||||
|
||||
.COPYRIGHT
|
||||
***********************************************************************
|
||||
include::../shared-doc/src/copyright.adoc[]
|
||||
***********************************************************************
|
||||
|
||||
Software Version -- Document Version:
|
||||
include::version.string[]
|
||||
|
||||
== Redmine Issues
|
||||
|
||||
// Hier können Sie die Redmine-Issues auflisten
|
||||
// Beispiel:
|
||||
* #12345 - Beschreibung des Issues
|
||||
* #12346 - Beschreibung des Issues
|
||||
* #12347 - Beschreibung des Issues
|
||||
|
||||
== Changelog
|
||||
|
||||
This document lists all relevant changes applied over time to {Produktname} packages and their software components.
|
||||
Each entry in the list shows changes made to the *Services*, the installation *Bundle* or the *Documentation*.
|
||||
Entries may have an _ID_ at the end of the line for internal reference.
|
||||
|
||||
== Version {Versionsnummer} ({Datum})
|
||||
|
||||
*NEW*
|
||||
|
||||
{{#each ticket or log entry}}
|
||||
=== _({{id/hash}})_ {{subject}} ===
|
||||
* {{Your generated changelog entry}} _({{ticket_id/git hash}})_
|
||||
{{/each}}
|
||||
|
||||
*CHANGED*
|
||||
|
||||
{{#each ticket or log entry}}
|
||||
=== _({{id/hash}})_ {{subject}} ===
|
||||
* {{Your generated changelog entry}} _({{ticket_id/git hash}})_
|
||||
{{/each}}
|
||||
|
||||
*FIXED*
|
||||
|
||||
{{#each ticket or log entry}}
|
||||
=== _({{id/hash}})_ {{subject}} ===
|
||||
* {{Your generated changelog entry}} _({{ticket_id/git hash}})_
|
||||
{{/each}}
|
||||
|
||||
*DOCUMENTATION*
|
||||
|
||||
{{#each ticket or log entry}}
|
||||
=== _({{id/hash}})_ {{subject}} ===
|
||||
* {{Your generated changelog entry}} _({{ticket_id/git hash}})_
|
||||
{{/each}}
|
||||
|
||||
22
changelog2/gittest.sh
Executable file
22
changelog2/gittest.sh
Executable file
|
|
@ -0,0 +1,22 @@
|
|||
#!/bin/bash
|
||||
|
||||
if ! git describe --tags --abbrev=0 &>/dev/null; then
|
||||
echo "Keine Tags gefunden. Verwende den ersten Commit als Startpunkt."
|
||||
START_POINT=$(git rev-list --max-parents=0 HEAD)
|
||||
else
|
||||
START_POINT=$(git describe --tags --abbrev=0)
|
||||
fi
|
||||
|
||||
echo "Changelog seit $START_POINT:"
|
||||
echo ""
|
||||
echo "Neue Features:"
|
||||
git log --no-merges --pretty=format:"- %s" $START_POINT..HEAD | grep "^feat:" | sort
|
||||
echo ""
|
||||
echo "Fehlerbehebungen:"
|
||||
git log --no-merges --pretty=format:"- %s" $START_POINT..HEAD | grep "^fix:" | sort
|
||||
echo ""
|
||||
echo "Dokumentation:"
|
||||
git log --no-merges --pretty=format:"- %s" $START_POINT..HEAD | grep "^docs:" | sort
|
||||
echo ""
|
||||
echo "Andere Änderungen:"
|
||||
git log --no-merges --pretty=format:"- [%h] %s (%an)" $START_POINT..HEAD | grep -vE "^- (feat|fix|docs):" | sort
|
||||
154
changelog2/main.py
Normal file
154
changelog2/main.py
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
#!/usr/bin/env python
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import ssl
|
||||
import urllib.request as request
|
||||
|
||||
from config.config import parse_config
|
||||
from crew import ChangelogCrew
|
||||
from langchain_ollama import ChatOllama
|
||||
from langchain_openai import ChatOpenAI
|
||||
|
||||
config = parse_config()
|
||||
os.environ["OPENAI_API_KEY"] = config["OPENAI_API_KEY"]
|
||||
|
||||
# os.environ["OPENAI_API_BASE"] = "http://ollama:11434"
|
||||
# os.environ["OPENAI_MODEL_NAME"] = "llama3.1:8b" # Adjust based on available model
|
||||
|
||||
llmLocal = ChatOllama(
|
||||
model="llama3.1:8b", base_url="http://ollama:11434", temperatur=0.4
|
||||
)
|
||||
llmOpenAI = ChatOpenAI(
|
||||
model_name="gpt-4o",
|
||||
temperature=0.4,
|
||||
)
|
||||
config["git_task"] = False
|
||||
|
||||
|
||||
def makeRedmineRequest(url: str, APIKey: str):
|
||||
context = ssl.create_default_context()
|
||||
context.check_hostname = False
|
||||
context.verify_mode = ssl.CERT_NONE
|
||||
|
||||
req = request.Request(url, method="GET")
|
||||
req.add_header("Content-Type", "application/json")
|
||||
req.add_header("X-Redmine-API-Key", APIKey)
|
||||
return request.urlopen(req, context=context)
|
||||
|
||||
|
||||
def makeProjectRequest(url: str, APIKey: str) -> dict:
|
||||
res = makeRedmineRequest(url, APIKey)
|
||||
json_data = json.load(res)
|
||||
project_info = {project["name"]: project["id"] for project in json_data["projects"]}
|
||||
|
||||
return project_info
|
||||
|
||||
|
||||
def makeVersionRequest(url: str, APIKey: str, project_id: int) -> dict:
|
||||
res = makeRedmineRequest(url, APIKey)
|
||||
json_data = json.load(res)
|
||||
|
||||
version_info = {
|
||||
version["name"]: version["id"]
|
||||
for version in json_data["versions"]
|
||||
if version["project"]["id"] == project_id and version["status"] == "open"
|
||||
}
|
||||
|
||||
return version_info
|
||||
|
||||
|
||||
def main():
|
||||
desc = "This is a tool that generates changelog messages from Redmine and Git using AI."
|
||||
parser = argparse.ArgumentParser(description=desc, add_help=True)
|
||||
parser.add_argument(
|
||||
"-p",
|
||||
"--project",
|
||||
help="The project ID for which text is to be generated",
|
||||
# type=int,
|
||||
type=str,
|
||||
)
|
||||
parser.add_argument(
|
||||
"-f",
|
||||
"--fixed-version",
|
||||
help="The version ID of the project for which text is to be generated",
|
||||
# type=int,
|
||||
type=str,
|
||||
)
|
||||
parser.add_argument(
|
||||
"-r",
|
||||
"--repo",
|
||||
help="The path to the Git repository",
|
||||
type=str,
|
||||
)
|
||||
parser.add_argument(
|
||||
"-t",
|
||||
"--type",
|
||||
help="The type of text to be generated",
|
||||
choices=["Changelog", "ReleaseNotes", "test"],
|
||||
type=str,
|
||||
)
|
||||
parser.add_argument(
|
||||
"-l",
|
||||
"--local",
|
||||
help="Execute the generator using a local llm",
|
||||
default=False,
|
||||
action="store_true",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
if not args.project:
|
||||
project_infos = makeProjectRequest(
|
||||
url=config["REDMINE_HOST"] + "/projects.json",
|
||||
APIKey=config["REDMINE_API_KEY"],
|
||||
)
|
||||
print("Enter a project id when calling. There is a choice:")
|
||||
for project_name, project_id in project_infos.items():
|
||||
print(f"{project_name} (ID: {project_id})")
|
||||
exit(0)
|
||||
if args.project and not args.fixed_version:
|
||||
print("Enter a version id when calling. There is a choice:")
|
||||
fixed_version = makeVersionRequest(
|
||||
url=config["REDMINE_HOST"]
|
||||
+ "/projects/"
|
||||
+ str(args.project)
|
||||
+ "/versions.json",
|
||||
APIKey=config["REDMINE_API_KEY"],
|
||||
project_id=args.project,
|
||||
)
|
||||
for version_name, version_id in fixed_version.items():
|
||||
print(f"{version_name}: {version_id}")
|
||||
exit(0)
|
||||
if args.project and args.fixed_version and not args.type:
|
||||
args.type = input("Please choose a type (Changelog/ReleaseNotes)")
|
||||
customField = ""
|
||||
prompt = ""
|
||||
if args.project and args.fixed_version:
|
||||
if args.type == "Changelog":
|
||||
customField = "&cf_21=Changelog"
|
||||
elif args.type == "ReleaseNotes":
|
||||
customField = "&cf_21=Release_notes"
|
||||
else:
|
||||
customField = ""
|
||||
|
||||
if args.repo != None:
|
||||
os.environ["GIT_REPO_PATH"] = args.repo
|
||||
config["git_task"] = True
|
||||
else:
|
||||
os.environ["GIT_REPO_PATH"] = ""
|
||||
|
||||
changelog_crew = ChangelogCrew(git_task=config["git_task"], local=args.local)
|
||||
result = changelog_crew.crew().kickoff(
|
||||
inputs={
|
||||
"project_id": args.project,
|
||||
"version_id": args.fixed_version,
|
||||
"repo_path": args.repo,
|
||||
"changelog_type": args.type,
|
||||
}
|
||||
)
|
||||
|
||||
print("######################")
|
||||
print(result)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
159
changelog2/tools/tools.py
Normal file
159
changelog2/tools/tools.py
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
import json
|
||||
import re
|
||||
import ssl
|
||||
import urllib.request as request
|
||||
from typing import Dict, List
|
||||
|
||||
import git
|
||||
from config.config import parse_config
|
||||
from crewai_tools import tool
|
||||
|
||||
config = parse_config()
|
||||
|
||||
|
||||
@tool("Get Categorized Git Commits Since Last Tag")
|
||||
def get_categorized_commits_since_last_tag(
|
||||
repo_path: str = config["GIT_REPO_PATH"],
|
||||
) -> str:
|
||||
"""
|
||||
Retrieves and categorizes commits since the last tag in the Git repository using git shortlog.
|
||||
|
||||
Args:
|
||||
repo_path (str): Path to the Git repository.
|
||||
|
||||
Returns:
|
||||
str: JSON string containing categorized commit information.
|
||||
"""
|
||||
repo = git.Repo(repo_path)
|
||||
tags = sorted(repo.tags, key=lambda t: t.commit.committed_datetime)
|
||||
last_tag = tags[-1] if tags else None
|
||||
|
||||
if not last_tag:
|
||||
range_spec = "HEAD"
|
||||
else:
|
||||
range_spec = f"{last_tag.name}..HEAD"
|
||||
|
||||
shortlog = repo.git.shortlog("-sne", range_spec)
|
||||
|
||||
author_commits = parse_shortlog(shortlog)
|
||||
|
||||
commits = list(repo.iter_commits(range_spec))
|
||||
|
||||
categorized_commits = categorize_commits(commits)
|
||||
|
||||
result = {
|
||||
"last_tag": last_tag.name if last_tag else "No tags",
|
||||
"author_commits": author_commits,
|
||||
"categorized_commits": categorized_commits,
|
||||
}
|
||||
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
|
||||
def parse_shortlog(shortlog: str) -> List[Dict[str, str]]:
|
||||
"""Parse git shortlog output."""
|
||||
author_commits = []
|
||||
for line in shortlog.split("\n"):
|
||||
if line.strip():
|
||||
count, author = line.strip().split("\t")
|
||||
author_commits.append(
|
||||
{"commit_count": int(count.strip()), "author": author.strip()}
|
||||
)
|
||||
return author_commits
|
||||
|
||||
|
||||
def categorize_commits(commits: List[git.Commit]) -> Dict[str, List[Dict[str, str]]]:
|
||||
"""Categorize commits based on their message."""
|
||||
categories = {"features": [], "fixes": [], "documentation": [], "others": []}
|
||||
|
||||
for commit in commits:
|
||||
commit_info = {
|
||||
"hash": commit.hexsha[:7],
|
||||
"message": commit.summary,
|
||||
"date": commit.committed_datetime.isoformat(),
|
||||
}
|
||||
|
||||
if commit.summary.startswith("feat:"):
|
||||
categories["features"].append(commit_info)
|
||||
elif commit.summary.startswith("fix:"):
|
||||
categories["fixes"].append(commit_info)
|
||||
elif commit.summary.startswith("docs:"):
|
||||
categories["documentation"].append(commit_info)
|
||||
else:
|
||||
categories["others"].append(commit_info)
|
||||
|
||||
return categories
|
||||
|
||||
|
||||
@tool("Get Git Commits Since Last Tag")
|
||||
def get_commits_since_last_tag(repo_path: str = config["GIT_REPO_PATH"]) -> str:
|
||||
"""
|
||||
Retrieves all commits since the last tag in the Git repository.
|
||||
|
||||
Args:
|
||||
repo_path (str): Path to the Git repository.
|
||||
|
||||
Returns:
|
||||
str: JSON string containing commit information.
|
||||
"""
|
||||
repo = git.Repo(repo_path)
|
||||
tags = sorted(repo.tags, key=lambda t: t.commit.committed_datetime)
|
||||
last_tag = tags[-1] if tags else None
|
||||
|
||||
if not last_tag:
|
||||
commits = list(repo.iter_commits())
|
||||
else:
|
||||
commits = list(repo.iter_commits(f"{last_tag.name}..HEAD"))
|
||||
|
||||
commit_info = [
|
||||
{
|
||||
"hash": commit.hexsha,
|
||||
"date": commit.committed_datetime.isoformat(),
|
||||
"message": commit.message.strip(),
|
||||
}
|
||||
for commit in commits
|
||||
]
|
||||
return json.dumps(commit_info)
|
||||
|
||||
|
||||
@tool("Get Redmine Issues")
|
||||
def get_redmine_issues(project_id: int, version_id: int, changelog_type: str) -> str:
|
||||
"""
|
||||
Retrieves issues for a specific Redmine project and version.
|
||||
|
||||
Args:
|
||||
project_id (int): ID of the Redmine project.
|
||||
version_id (int): ID of the project version.
|
||||
changelog_type (str): Type of changelog ("Changelog" or "Release_notes").
|
||||
|
||||
Returns:
|
||||
str: JSON string containing issue information.
|
||||
"""
|
||||
if changelog_type == "Changelog":
|
||||
custom_field = "&cf_21=Changelog"
|
||||
elif changelog_type == "ReleaseNotes":
|
||||
custom_field = "&cf_21=Release_notes"
|
||||
else:
|
||||
custom_field = ""
|
||||
url = f"{config['REDMINE_HOST']}/issues.json?project_id={project_id}&fixed_version_id={version_id}&status_id=*{custom_field}"
|
||||
data = make_redmine_request(url)
|
||||
issues = [
|
||||
{
|
||||
"id": issue["id"],
|
||||
"subject": issue["subject"],
|
||||
"tracker": issue["tracker"]["name"],
|
||||
}
|
||||
for issue in data["issues"]
|
||||
]
|
||||
return json.dumps(issues)
|
||||
|
||||
|
||||
def make_redmine_request(url):
|
||||
context = ssl.create_default_context()
|
||||
context.check_hostname = False
|
||||
context.verify_mode = ssl.CERT_NONE
|
||||
|
||||
req = request.Request(url, method="GET")
|
||||
req.add_header("Content-Type", "application/json")
|
||||
req.add_header("X-Redmine-API-Key", config["REDMINE_API_KEY"])
|
||||
return json.load(request.urlopen(req, context=context))
|
||||
31
compose.yaml
Normal file
31
compose.yaml
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
services:
|
||||
ollama:
|
||||
image: ollama/ollama:latest
|
||||
container_name: ollama
|
||||
ports:
|
||||
- "11434:11434"
|
||||
volumes:
|
||||
- ollama-local:/root/.ollama
|
||||
networks:
|
||||
- app-network
|
||||
|
||||
changelog:
|
||||
build: .
|
||||
container_name: changelog
|
||||
# depends_on:
|
||||
# - ollama
|
||||
environment:
|
||||
- OPENAI_API_BASE=http://ollama:11434
|
||||
volumes:
|
||||
- ./changelog2:/app/changelog2
|
||||
networks:
|
||||
- app-network
|
||||
command: [""]
|
||||
|
||||
volumes:
|
||||
ollama-local:
|
||||
external: true
|
||||
|
||||
networks:
|
||||
app-network:
|
||||
driver: bridge
|
||||
55
create-changelog.sh
Executable file
55
create-changelog.sh
Executable file
|
|
@ -0,0 +1,55 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Funktion zur Anzeige der Verwendung
|
||||
usage() {
|
||||
echo "Verwendung: $0 --projects <projects> --versions <versions> [--repo <repo>] [--local <true/false>] [--type <Changelog/ReleaseNotes]"
|
||||
}
|
||||
|
||||
# Argumente parsen
|
||||
while [[ "$#" -gt 0 ]]; do
|
||||
case $1 in
|
||||
--projects) projects="$2"; shift ;;
|
||||
--versions) versions="$2"; shift ;;
|
||||
--repo) repo="$2"; shift ;;
|
||||
--type) type="$2"; shift ;;
|
||||
--local) local="$2"; shift ;;
|
||||
*) usage ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
# Überprüfen der erforderlichen Argumente
|
||||
if [ -z "$projects" ] || [ -z "$versions" ]; then
|
||||
echo "Verwendung: $0 --projects <projects> --versions <versions> [--repo <repo>] [--local <true/false>] [--type <Changelog/ReleaseNotes]"
|
||||
docker compose up changelog -d
|
||||
docker compose run --rm changelog
|
||||
fi
|
||||
|
||||
# Setzen des Standard-Werts für local, falls nicht angegeben
|
||||
local=${local:-false}
|
||||
|
||||
# Funktion zum Starten des lokalen Setups
|
||||
start_local_setup() {
|
||||
echo "Starte lokales Setup..."
|
||||
docker compose up -d ollama && docker exec ollama ollama pull gemma2
|
||||
docker compose run --rm changelog -p "$projects" -f "$versions" ${repo:+-r "$repo"} ${type:+-t "$type"}
|
||||
docker compose down
|
||||
}
|
||||
|
||||
# Funktion zum Ausführen des Changelog-Containers
|
||||
run_changelog_container() {
|
||||
echo "Führe Changelog-Container aus..."
|
||||
docker compose up changelog -d
|
||||
docker compose run --rm changelog -p "$projects" -f "$versions" ${repo:+-r "$repo"} ${type:+-t "$type"}
|
||||
docker compose down
|
||||
}
|
||||
|
||||
# Hauptlogik
|
||||
echo "Projects: $projects"
|
||||
echo "Versions: $versions"
|
||||
echo "Type: $type"
|
||||
if [ "$local" = "true" ]; then
|
||||
start_local_setup
|
||||
else
|
||||
run_changelog_container
|
||||
fi
|
||||
21
devbox.json
Normal file
21
devbox.json
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"$schema": "https://raw.githubusercontent.com/jetify-com/devbox/0.12.0/.schema/devbox.schema.json",
|
||||
"packages": [
|
||||
"libstdcxx5",
|
||||
"stdenv.cc.cc.lib",
|
||||
"python312",
|
||||
"poetry"
|
||||
],
|
||||
"shell": {
|
||||
"init_hook": [
|
||||
"export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD/.devbox/nix/profile/default/lib:$NIX_CC/lib",
|
||||
"poetry shell"
|
||||
],
|
||||
"scripts": {
|
||||
"test": [
|
||||
"echo \"Error: no test specified\" && exit 1"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
23
devbox.lock
Normal file
23
devbox.lock
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"lockfile_version": "1",
|
||||
"packages": {
|
||||
"libstdcxx5": {
|
||||
"resolved": "github:NixOS/nixpkgs/75a52265bda7fd25e06e3a67dee3f0354e73243c#libstdcxx5",
|
||||
"source": "nixpkg"
|
||||
},
|
||||
"poetry": {
|
||||
"plugin_version": "0.0.4",
|
||||
"resolved": "github:NixOS/nixpkgs/75a52265bda7fd25e06e3a67dee3f0354e73243c#poetry",
|
||||
"source": "nixpkg"
|
||||
},
|
||||
"python312": {
|
||||
"plugin_version": "0.0.3",
|
||||
"resolved": "github:NixOS/nixpkgs/75a52265bda7fd25e06e3a67dee3f0354e73243c#python312",
|
||||
"source": "nixpkg"
|
||||
},
|
||||
"stdenv.cc.cc.lib": {
|
||||
"resolved": "github:NixOS/nixpkgs/75a52265bda7fd25e06e3a67dee3f0354e73243c#stdenv.cc.cc.lib",
|
||||
"source": "nixpkg"
|
||||
}
|
||||
}
|
||||
}
|
||||
121
get_redmine_infos.py
Normal file
121
get_redmine_infos.py
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
import urllib.request as request
|
||||
import json
|
||||
from dotenv import load_dotenv
|
||||
import os
|
||||
import ssl
|
||||
import argparse
|
||||
import textwrap
|
||||
|
||||
|
||||
def makeRedmineRequest(url: str, APIKey: str):
|
||||
context = ssl.create_default_context()
|
||||
context.check_hostname = False
|
||||
context.verify_mode = ssl.CERT_NONE
|
||||
|
||||
req = request.Request(url, method="GET")
|
||||
req.add_header("Content-Type", "application/json")
|
||||
req.add_header("X-Redmine-API-Key", APIKey)
|
||||
return request.urlopen(req, context=context)
|
||||
|
||||
|
||||
def makeProjectRequest(url: str, APIKey: str) -> dict:
|
||||
res = makeRedmineRequest(url, APIKey)
|
||||
json_data = json.load(res)
|
||||
project_info = {project["name"]: project["id"] for project in json_data["projects"]}
|
||||
|
||||
return project_info
|
||||
|
||||
|
||||
def makeVersionRequest(url: str, APIKey: str, project_id: int) -> dict:
|
||||
res = makeRedmineRequest(url, APIKey)
|
||||
json_data = json.load(res)
|
||||
|
||||
version_info = {
|
||||
version["name"]: version["id"]
|
||||
for version in json_data["versions"]
|
||||
if version["project"]["id"] == project_id and version["status"] == "open"
|
||||
}
|
||||
|
||||
return version_info
|
||||
|
||||
|
||||
def main():
|
||||
try:
|
||||
load_dotenv("./.env")
|
||||
rdmnHost = os.getenv("REDMINE_HOST")
|
||||
rdmnAPIKey = os.getenv("REDMINE_API_KEY")
|
||||
openaiAPIKey = os.getenv("OPENAI_API_KEY")
|
||||
except Exception as e:
|
||||
print("No Env_variables set: ", e)
|
||||
finally:
|
||||
rdmnHost = "https://redmine.tixeltec.de/redmine"
|
||||
if rdmnAPIKey is None or openaiAPIKey is None:
|
||||
print(
|
||||
"Please set environment variables for REDMINE_API_KEY and/or OPENAI_API_KEY"
|
||||
)
|
||||
exit(1)
|
||||
|
||||
desc = """This is a simple tool that allows you to generate changelog messages from Redmine using AI.
|
||||
|
||||
Usage:
|
||||
1. Create a `.env` file . Fill in the environment variables as follows:
|
||||
```
|
||||
REDMINE_HOST=your_redmine_host
|
||||
REDMINE_API_KEY=your_redmine_api_key
|
||||
OPENAI_API_KEY=your_openai_api_key
|
||||
```
|
||||
2. Run the script with the following arguments:\n
|
||||
- `-p, --project`: The project ID for which the changelog should be generated.
|
||||
- `-f, --fixed-version`: The version ID for which the changelog should be generated.
|
||||
- `-t, --type`: The type of text to be generated (Changelog or Release Notes).
|
||||
3. If you don't provide a project ID or version ID, a list of available projects and versions will be displayed for you to choose from.
|
||||
4. The generated changelog will be output in AsciiDoc format on the console.
|
||||
|
||||
Examples:
|
||||
python3 changelog_generator.py -p 47 -f 755 -t Changelog
|
||||
python3 changelog_generator.py -p 47 -f 755 -t ReleaseNotes
|
||||
"""
|
||||
clp = argparse.ArgumentParser(
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
description=textwrap.dedent(desc),
|
||||
add_help=True,
|
||||
)
|
||||
clp.add_argument(
|
||||
"-p",
|
||||
"--project",
|
||||
help="The project for which text is to be generated",
|
||||
type=int,
|
||||
)
|
||||
clp.add_argument(
|
||||
"-f",
|
||||
"--fixed-version",
|
||||
help="The version of the project for which text is to be generated",
|
||||
type=int,
|
||||
)
|
||||
clp.add_argument(
|
||||
"-t",
|
||||
"--type",
|
||||
help="The type of text to be generated",
|
||||
choices=["Changelog", "ReleaseNotes"],
|
||||
type=str,
|
||||
)
|
||||
args = clp.parse_args()
|
||||
if not args.project:
|
||||
project_infos = makeProjectRequest(
|
||||
url=rdmnHost + "/projects.json", APIKey=rdmnAPIKey
|
||||
)
|
||||
print("Enter a project id when calling. There is a choice:")
|
||||
for project_name, project_id in project_infos.items():
|
||||
print(f"{project_name} (ID: {project_id})")
|
||||
if args.project and not args.fixed_version:
|
||||
print("Enter a version id when calling. There is a choice:")
|
||||
fixed_version = makeVersionRequest(
|
||||
url=rdmnHost + "/projects/" + str(args.project) + "/versions.json",
|
||||
APIKey=rdmnAPIKey,
|
||||
project_id=args.project,
|
||||
)
|
||||
for version_name, version_id in fixed_version.items():
|
||||
print(f"{version_name}: {version_id}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
29
pyproject.toml
Normal file
29
pyproject.toml
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
[tool.poetry]
|
||||
name = "changelog2"
|
||||
version = "0.0.1"
|
||||
description = "An AI-Agent based tool for creating changelogs"
|
||||
authors = ["Patryk Hegenberg <hegenberg@tixeltec.com>"]
|
||||
readme = "README.md"
|
||||
|
||||
[tool.poetry.scripts]
|
||||
changelog2 = "changelog2.main:main"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = ">=3.11,<=3.13"
|
||||
langchain = "^0.2.16"
|
||||
llama-index = "^0.11.8"
|
||||
crewai = { extras = ["tools"], version = "^0.55.2" }
|
||||
gitpython = "^3.1.43"
|
||||
langchain-ollama = "^0.1.3"
|
||||
nltk = "^3.9.1"
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
black = "^24.8.0"
|
||||
flake8 = "^7.1.1"
|
||||
pytest = "^8.3.2"
|
||||
pyinstaller = "^6.10.0"
|
||||
staticx = "^0.14.1"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
230
requirements.txt
Normal file
230
requirements.txt
Normal file
|
|
@ -0,0 +1,230 @@
|
|||
aiohappyeyeballs==2.4.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
aiohttp==3.10.5 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
aiosignal==1.3.1 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
alembic==1.13.2 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
annotated-types==0.7.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
anyio==4.4.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
appdirs==1.4.4 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
asgiref==3.8.1 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
attrs==24.2.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
auth0-python==4.7.2 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
backoff==2.2.1 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
bcrypt==4.2.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
beautifulsoup4==4.12.3 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
boto3==1.35.16 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
botocore==1.35.16 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
build==1.2.2 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
cachetools==5.5.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
certifi==2024.8.30 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
cffi==1.17.1 ; python_version >= "3.11" and python_full_version <= "3.13.0" and (platform_python_implementation != "PyPy" or os_name == "nt") and (platform_python_implementation != "PyPy" or implementation_name != "pypy")
|
||||
charset-normalizer==3.3.2 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
chroma-hnswlib==0.7.3 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
chromadb==0.4.24 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
click==8.1.7 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
cohere==5.9.1 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
colorama==0.4.6 ; python_version >= "3.11" and python_full_version <= "3.13.0" and (platform_system == "Windows" or sys_platform == "win32" or os_name == "nt")
|
||||
coloredlogs==15.0.1 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
crewai-tools==0.12.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
crewai[tools]==0.55.2 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
cryptography==43.0.1 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
dataclasses-json==0.6.7 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
decorator==5.1.1 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
deprecated==1.2.14 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
deprecation==2.1.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
dirtyjson==1.0.8 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
distro==1.9.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
docker==7.1.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
docstring-parser==0.16 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
docx2txt==0.8 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
embedchain==0.1.121 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
fastapi==0.114.1 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
fastavro==1.9.7 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
filelock==3.16.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
flatbuffers==24.3.25 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
frozenlist==1.4.1 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
fsspec==2024.9.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
gitdb==4.0.11 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
gitpython==3.1.43 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
google-api-core==2.19.2 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
google-api-core[grpc]==2.19.2 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
google-auth==2.34.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
google-cloud-aiplatform==1.66.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
google-cloud-bigquery==3.25.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
google-cloud-core==2.4.1 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
google-cloud-resource-manager==1.12.5 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
google-cloud-storage==2.18.2 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
google-crc32c==1.6.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
google-resumable-media==2.7.2 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
googleapis-common-protos==1.65.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
googleapis-common-protos[grpc]==1.65.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
gptcache==0.1.44 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
greenlet==3.1.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
grpc-google-iam-v1==0.13.1 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
grpcio-status==1.62.3 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
grpcio-tools==1.62.3 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
grpcio==1.66.1 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
h11==0.14.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
h2==4.1.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
hpack==4.0.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
httpcore==1.0.5 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
httptools==0.6.1 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
httpx-sse==0.4.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
httpx==0.27.2 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
httpx[http2]==0.27.2 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
huggingface-hub==0.24.6 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
humanfriendly==10.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
hyperframe==6.0.1 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
idna==3.8 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
importlib-metadata==8.4.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
importlib-resources==6.4.5 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
iniconfig==2.0.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
instructor==1.3.3 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
jiter==0.4.2 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
jmespath==1.0.1 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
joblib==1.4.2 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
json-repair==0.25.3 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
jsonpatch==1.33 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
jsonpointer==3.0.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
jsonref==1.1.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
kubernetes==30.1.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
lancedb==0.5.7 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
langchain-cohere==0.1.9 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
langchain-community==0.2.16 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
langchain-core==0.2.39 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
langchain-experimental==0.0.65 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
langchain-ollama==0.1.3 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
langchain-openai==0.1.23 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
langchain-text-splitters==0.2.4 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
langchain==0.2.16 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
langsmith==0.1.117 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
llama-cloud==0.0.17 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
llama-index-agent-openai==0.3.1 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
llama-index-cli==0.3.1 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
llama-index-core==0.11.8 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
llama-index-embeddings-openai==0.2.4 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
llama-index-indices-managed-llama-cloud==0.3.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
llama-index-legacy==0.9.48.post3 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
llama-index-llms-openai==0.2.3 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
llama-index-multi-modal-llms-openai==0.2.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
llama-index-program-openai==0.2.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
llama-index-question-gen-openai==0.2.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
llama-index-readers-file==0.2.1 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
llama-index-readers-llama-parse==0.3.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
llama-index==0.11.8 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
llama-parse==0.5.5 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
mako==1.3.5 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
markdown-it-py==3.0.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
markupsafe==2.1.5 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
marshmallow==3.22.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
mdurl==0.1.2 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
mem0ai==0.0.20 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
mmh3==4.1.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
monotonic==1.6 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
mpmath==1.3.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
multidict==6.1.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
mypy-extensions==1.0.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
nest-asyncio==1.6.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
networkx==3.3 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
nltk==3.9.1 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
nodeenv==1.9.1 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
numpy==1.26.4 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
oauthlib==3.2.2 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
ollama==0.3.3 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
onnxruntime==1.19.2 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
openai==1.44.1 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
opentelemetry-api==1.27.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
opentelemetry-exporter-otlp-proto-common==1.27.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
opentelemetry-exporter-otlp-proto-grpc==1.27.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
opentelemetry-exporter-otlp-proto-http==1.27.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
opentelemetry-instrumentation-asgi==0.48b0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
opentelemetry-instrumentation-fastapi==0.48b0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
opentelemetry-instrumentation==0.48b0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
opentelemetry-proto==1.27.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
opentelemetry-sdk==1.27.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
opentelemetry-semantic-conventions==0.48b0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
opentelemetry-util-http==0.48b0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
orjson==3.10.7 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
outcome==1.3.0.post0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
overrides==7.7.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
packaging==24.1 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
pandas==2.2.2 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
parameterized==0.9.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
pillow==10.4.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
pluggy==1.5.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
portalocker==2.10.1 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
posthog==3.6.5 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
proto-plus==1.24.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
protobuf==4.25.4 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
pulsar-client==3.5.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
py==1.11.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
pyarrow==17.0.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
pyasn1-modules==0.4.1 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
pyasn1==0.6.1 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
pycparser==2.22 ; python_version >= "3.11" and python_full_version <= "3.13.0" and (platform_python_implementation != "PyPy" or os_name == "nt") and (platform_python_implementation != "PyPy" or implementation_name != "pypy")
|
||||
pydantic-core==2.23.3 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
pydantic==2.9.1 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
pygments==2.18.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
pyjwt==2.9.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
pylance==0.9.18 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
pypdf==4.3.1 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
pypika==0.48.9 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
pyproject-hooks==1.1.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
pyreadline3==3.4.1 ; sys_platform == "win32" and python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
pyright==1.1.380 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
pysbd==0.3.4 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
pysocks==1.7.1 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
pytest==8.3.3 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
python-dateutil==2.9.0.post0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
python-dotenv==1.0.1 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
pytube==15.0.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
pytz==2024.2 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
pywin32==306 ; python_version >= "3.11" and python_full_version <= "3.13.0" and (sys_platform == "win32" or platform_system == "Windows")
|
||||
pyyaml==6.0.2 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
qdrant-client==1.11.1 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
ratelimiter==1.2.0.post0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
regex==2024.7.24 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
requests-oauthlib==2.0.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
requests==2.32.3 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
retry==0.9.2 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
rich==13.8.1 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
rsa==4.9 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
s3transfer==0.10.2 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
schema==0.7.7 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
selenium==4.24.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
semver==3.0.2 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
setuptools==74.1.2 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
shapely==2.0.6 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
shellingham==1.5.4 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
six==1.16.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
smmap==5.0.1 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
sniffio==1.3.1 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
sortedcontainers==2.4.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
soupsieve==2.6 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
sqlalchemy==2.0.34 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
sqlalchemy[asyncio]==2.0.34 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
starlette==0.38.5 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
striprtf==0.0.26 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
sympy==1.13.2 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
tabulate==0.9.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
tenacity==8.5.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
tiktoken==0.7.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
tokenizers==0.20.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
tqdm==4.66.5 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
trio-websocket==0.11.1 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
trio==0.26.2 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
typer==0.12.5 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
types-requests==2.32.0.20240907 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
typing-extensions==4.12.2 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
typing-inspect==0.9.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
tzdata==2024.1 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
urllib3==2.2.2 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
urllib3[socks]==2.2.2 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
uvicorn[standard]==0.30.6 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
uvloop==0.20.0 ; (sys_platform != "win32" and sys_platform != "cygwin") and platform_python_implementation != "PyPy" and python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
watchfiles==0.24.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
websocket-client==1.8.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
websockets==13.0.1 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
wrapt==1.16.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
wsproto==1.2.0 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
yarl==1.11.1 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
zipp==3.20.1 ; python_version >= "3.11" and python_full_version <= "3.13.0"
|
||||
Loading…
Add table
Add a link
Reference in a new issue