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