Mohammed Foud
commited on
Commit
·
5a2d62e
1
Parent(s):
5cc14db
Add application file
Browse files- .gitignore +199 -0
- Dockerfile +13 -0
- agents/analysis/__init__.py +15 -0
- agents/analysis/actors_analysis.py +90 -0
- agents/analysis/architecture_analysis.py +33 -0
- agents/analysis/deployment_analysis.py +31 -0
- agents/analysis/entities_analysis.py +60 -0
- agents/analysis/sequence_analysis.py +77 -0
- agents/analysis/state_analysis.py +32 -0
- agents/code/__init__.py +21 -0
- agents/code/activity_diagram_generator.py +45 -0
- agents/code/class_diagram_generator.py +83 -0
- agents/code/component_diagram_generator.py +40 -0
- agents/code/deployment_diagram_generator.py +40 -0
- agents/code/object_diagram_generator.py +45 -0
- agents/code/sequence_diagram_generator.py +86 -0
- agents/code/state_diagram_generator.py +40 -0
- agents/code/timing_diagram_generator.py +45 -0
- agents/code/use_case_diagram_generator.py +69 -0
- app.py +68 -0
- base_agent.py +46 -0
- config.py +25 -0
- d.sh +3 -0
- file_manager.py +106 -0
- logger.py +26 -0
- main.py +153 -0
- models.py +31 -0
- requirements.txt +8 -0
- research_generator.py +202 -0
.gitignore
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.DS_Store
|
| 2 |
+
|
| 3 |
+
# Byte-compiled / optimized / DLL files
|
| 4 |
+
__pycache__/
|
| 5 |
+
*.py[cod]
|
| 6 |
+
*$py.class
|
| 7 |
+
|
| 8 |
+
# C extensions
|
| 9 |
+
*.so
|
| 10 |
+
|
| 11 |
+
# Distribution / packaging
|
| 12 |
+
.Python
|
| 13 |
+
build/
|
| 14 |
+
develop-eggs/
|
| 15 |
+
dist/
|
| 16 |
+
downloads/
|
| 17 |
+
eggs/
|
| 18 |
+
.eggs/
|
| 19 |
+
lib64/
|
| 20 |
+
parts/
|
| 21 |
+
sdist/
|
| 22 |
+
var/
|
| 23 |
+
wheels/
|
| 24 |
+
share/python-wheels/
|
| 25 |
+
*.egg-info/
|
| 26 |
+
.installed.cfg
|
| 27 |
+
*.egg
|
| 28 |
+
MANIFEST
|
| 29 |
+
|
| 30 |
+
# PyInstaller
|
| 31 |
+
# Usually these files are written by a python script from a template
|
| 32 |
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
| 33 |
+
*.manifest
|
| 34 |
+
*.spec
|
| 35 |
+
|
| 36 |
+
# Installer logs
|
| 37 |
+
pip-log.txt
|
| 38 |
+
pip-delete-this-directory.txt
|
| 39 |
+
|
| 40 |
+
# Unit test / coverage reports
|
| 41 |
+
htmlcov/
|
| 42 |
+
.tox/
|
| 43 |
+
.nox/
|
| 44 |
+
.coverage
|
| 45 |
+
.coverage.*
|
| 46 |
+
.cache
|
| 47 |
+
nosetests.xml
|
| 48 |
+
coverage.xml
|
| 49 |
+
*.cover
|
| 50 |
+
*.py,cover
|
| 51 |
+
.hypothesis/
|
| 52 |
+
.pytest_cache/
|
| 53 |
+
cover/
|
| 54 |
+
|
| 55 |
+
# Translations
|
| 56 |
+
*.mo
|
| 57 |
+
*.pot
|
| 58 |
+
|
| 59 |
+
# Django stuff:
|
| 60 |
+
*.log
|
| 61 |
+
local_settings.py
|
| 62 |
+
db.sqlite3
|
| 63 |
+
db.sqlite3-journal
|
| 64 |
+
|
| 65 |
+
# Flask stuff:
|
| 66 |
+
instance/
|
| 67 |
+
.webassets-cache
|
| 68 |
+
|
| 69 |
+
# Scrapy stuff:
|
| 70 |
+
.scrapy
|
| 71 |
+
|
| 72 |
+
# Sphinx documentation
|
| 73 |
+
docs/_build/
|
| 74 |
+
|
| 75 |
+
# PyBuilder
|
| 76 |
+
.pybuilder/
|
| 77 |
+
target/
|
| 78 |
+
|
| 79 |
+
# Jupyter Notebook
|
| 80 |
+
.ipynb_checkpoints
|
| 81 |
+
|
| 82 |
+
# IPython
|
| 83 |
+
profile_default/
|
| 84 |
+
ipython_config.py
|
| 85 |
+
|
| 86 |
+
test/
|
| 87 |
+
|
| 88 |
+
# pyenv
|
| 89 |
+
# For a library or package, you might want to ignore these files since the code is
|
| 90 |
+
# intended to run in multiple environments; otherwise, check them in:
|
| 91 |
+
# .python-version
|
| 92 |
+
|
| 93 |
+
# pipenv
|
| 94 |
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
| 95 |
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
| 96 |
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
| 97 |
+
# install all needed dependencies.
|
| 98 |
+
#Pipfile.lock
|
| 99 |
+
|
| 100 |
+
# poetry
|
| 101 |
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
| 102 |
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
| 103 |
+
# commonly ignored for libraries.
|
| 104 |
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
| 105 |
+
#poetry.lock
|
| 106 |
+
|
| 107 |
+
# pdm
|
| 108 |
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
| 109 |
+
#pdm.lock
|
| 110 |
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
| 111 |
+
# in version control.
|
| 112 |
+
# https://pdm.fming.dev/#use-with-ide
|
| 113 |
+
.pdm.toml
|
| 114 |
+
|
| 115 |
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
| 116 |
+
__pypackages__/
|
| 117 |
+
|
| 118 |
+
# Celery stuff
|
| 119 |
+
celerybeat-schedule
|
| 120 |
+
celerybeat.pid
|
| 121 |
+
|
| 122 |
+
# SageMath parsed files
|
| 123 |
+
*.sage.py
|
| 124 |
+
|
| 125 |
+
# Environments
|
| 126 |
+
.env
|
| 127 |
+
.venv
|
| 128 |
+
env/
|
| 129 |
+
venv/
|
| 130 |
+
ENV/
|
| 131 |
+
env.bak/
|
| 132 |
+
venv.bak/
|
| 133 |
+
|
| 134 |
+
# Spyder project settings
|
| 135 |
+
.spyderproject
|
| 136 |
+
.spyproject
|
| 137 |
+
|
| 138 |
+
# Rope project settings
|
| 139 |
+
.ropeproject
|
| 140 |
+
|
| 141 |
+
# mkdocs documentation
|
| 142 |
+
/site
|
| 143 |
+
|
| 144 |
+
# mypy
|
| 145 |
+
.mypy_cache/
|
| 146 |
+
.dmypy.json
|
| 147 |
+
dmypy.json
|
| 148 |
+
|
| 149 |
+
# Pyre type checker
|
| 150 |
+
.pyre/
|
| 151 |
+
|
| 152 |
+
# pytype static type analyzer
|
| 153 |
+
.pytype/
|
| 154 |
+
|
| 155 |
+
# Cython debug symbols
|
| 156 |
+
cython_debug/
|
| 157 |
+
|
| 158 |
+
# PyCharm
|
| 159 |
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
| 160 |
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
| 161 |
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
| 162 |
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
| 163 |
+
#.idea/
|
| 164 |
+
|
| 165 |
+
/threads
|
| 166 |
+
state.json
|
| 167 |
+
/workspace/
|
| 168 |
+
/workspace/*
|
| 169 |
+
/workspace/**
|
| 170 |
+
|
| 171 |
+
*.venvy/*
|
| 172 |
+
*.venvy*
|
| 173 |
+
|
| 174 |
+
# SQLite
|
| 175 |
+
*.db
|
| 176 |
+
|
| 177 |
+
# .DS_Store files
|
| 178 |
+
.DS_Store
|
| 179 |
+
**/.DS_Store
|
| 180 |
+
.aider*
|
| 181 |
+
supabase/.temp/cli-latest
|
| 182 |
+
supabase/.temp/gotrue-version
|
| 183 |
+
supabase/.temp/pooler-url
|
| 184 |
+
supabase/.temp/postgres-version
|
| 185 |
+
supabase/.temp/project-ref
|
| 186 |
+
supabase/.temp/rest-version
|
| 187 |
+
supabase/.temp/storage-version
|
| 188 |
+
|
| 189 |
+
**/.prompts/
|
| 190 |
+
**/__pycache__/
|
| 191 |
+
|
| 192 |
+
|
| 193 |
+
.env.scripts
|
| 194 |
+
server/.git
|
| 195 |
+
trash
|
| 196 |
+
templates
|
| 197 |
+
output
|
| 198 |
+
|
| 199 |
+
rand.edu.backend/.git
|
Dockerfile
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.9
|
| 2 |
+
|
| 3 |
+
RUN useradd -m -u 1000 user
|
| 4 |
+
USER user
|
| 5 |
+
ENV PATH="/home/user/.local/bin:$PATH"
|
| 6 |
+
|
| 7 |
+
WORKDIR /app
|
| 8 |
+
|
| 9 |
+
COPY --chown=user ./requirements.txt requirements.txt
|
| 10 |
+
RUN pip install --no-cache-dir --upgrade -r requirements.txt
|
| 11 |
+
|
| 12 |
+
COPY --chown=user . /app
|
| 13 |
+
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
|
agents/analysis/__init__.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .entities_analysis import EntitiesAnalysis
|
| 2 |
+
from .actors_analysis import ActorsAnalysis
|
| 3 |
+
from .sequence_analysis import SequenceAnalysis
|
| 4 |
+
from .state_analysis import StateAnalysis
|
| 5 |
+
from .architecture_analysis import ArchitectureAnalysis
|
| 6 |
+
from .deployment_analysis import DeploymentAnalysis
|
| 7 |
+
|
| 8 |
+
__all__ = [
|
| 9 |
+
'EntitiesAnalysis',
|
| 10 |
+
'ActorsAnalysis',
|
| 11 |
+
'SequenceAnalysis',
|
| 12 |
+
'StateAnalysis',
|
| 13 |
+
'ArchitectureAnalysis',
|
| 14 |
+
'DeploymentAnalysis'
|
| 15 |
+
]
|
agents/analysis/actors_analysis.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from base_agent import BaseAgent
|
| 2 |
+
from models import AgentState
|
| 3 |
+
from typing import AsyncGenerator
|
| 4 |
+
import json
|
| 5 |
+
|
| 6 |
+
class ActorsAnalysis(BaseAgent):
|
| 7 |
+
async def __call__(self, state: AgentState) -> AsyncGenerator[str, None]:
|
| 8 |
+
prompt_template = """
|
| 9 |
+
Conduct a thorough actor and use case analysis for the following project:
|
| 10 |
+
|
| 11 |
+
Project: {project_name}
|
| 12 |
+
Description: {project_description}
|
| 13 |
+
|
| 14 |
+
Analysis Requirements:
|
| 15 |
+
1. Identify all actor types:
|
| 16 |
+
- Primary actors (initiate interactions)
|
| 17 |
+
- Secondary actors (support the system)
|
| 18 |
+
- External systems (integrated services)
|
| 19 |
+
- Offstage actors (affected but don't interact)
|
| 20 |
+
2. For each actor, specify:
|
| 21 |
+
- Role/purpose
|
| 22 |
+
- Interaction frequency
|
| 23 |
+
- Special characteristics
|
| 24 |
+
3. Identify core use cases with:
|
| 25 |
+
- Clear actor-action-benefit format
|
| 26 |
+
- Preconditions and postconditions
|
| 27 |
+
- Main success scenarios
|
| 28 |
+
- Key variations/alternate flows
|
| 29 |
+
4. Highlight relationships between actors
|
| 30 |
+
5. Identify system boundaries
|
| 31 |
+
|
| 32 |
+
Format your response as follows:
|
| 33 |
+
|
| 34 |
+
### Actor Analysis
|
| 35 |
+
#### Primary Actors
|
| 36 |
+
- [Actor Name]
|
| 37 |
+
* Role: [description]
|
| 38 |
+
* Frequency: [constant/occasional/rare]
|
| 39 |
+
* Characteristics: [key attributes]
|
| 40 |
+
|
| 41 |
+
#### Supporting Actors
|
| 42 |
+
- [System/Service Name]
|
| 43 |
+
* Role: [description]
|
| 44 |
+
* Integration Type: [API/database/event]
|
| 45 |
+
|
| 46 |
+
### Use Case Analysis
|
| 47 |
+
#### Core Use Cases
|
| 48 |
+
1. "[Actor] performs [action] to [benefit]"
|
| 49 |
+
- Preconditions: [required state]
|
| 50 |
+
- Main Flow: [step-by-step]
|
| 51 |
+
- Postconditions: [system state after]
|
| 52 |
+
- Variations: [alternate paths]
|
| 53 |
+
|
| 54 |
+
#### Supporting Use Cases
|
| 55 |
+
1. "[System] provides [functionality] for [purpose]"
|
| 56 |
+
|
| 57 |
+
### System Boundaries
|
| 58 |
+
- Included: [what's in scope]
|
| 59 |
+
- Excluded: [out-of-scope items]
|
| 60 |
+
|
| 61 |
+
Example:
|
| 62 |
+
### Actor Analysis
|
| 63 |
+
#### Primary Actors
|
| 64 |
+
- Online Customer
|
| 65 |
+
* Role: Initiates purchases and manages account
|
| 66 |
+
* Frequency: Constant
|
| 67 |
+
* Characteristics: Authenticated, varying technical skill
|
| 68 |
+
|
| 69 |
+
### Use Case Analysis
|
| 70 |
+
#### Core Use Cases
|
| 71 |
+
1. "Customer places order for products"
|
| 72 |
+
- Preconditions: Customer is logged in, cart not empty
|
| 73 |
+
- Main Flow:
|
| 74 |
+
1. Customer selects checkout
|
| 75 |
+
2. System validates inventory
|
| 76 |
+
3. Customer enters payment
|
| 77 |
+
4. System confirms order
|
| 78 |
+
- Postconditions: Order is persisted, inventory updated
|
| 79 |
+
- Variations: Payment fails, inventory unavailable
|
| 80 |
+
"""
|
| 81 |
+
|
| 82 |
+
async for chunk in self._stream_process(
|
| 83 |
+
state=state,
|
| 84 |
+
prompt_template=prompt_template,
|
| 85 |
+
output_key="actors_use_cases",
|
| 86 |
+
step_name="extract_actors",
|
| 87 |
+
project_name=state["project_name"],
|
| 88 |
+
project_description=state["project_description"]
|
| 89 |
+
):
|
| 90 |
+
yield chunk
|
agents/analysis/architecture_analysis.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from base_agent import BaseAgent
|
| 2 |
+
from models import AgentState
|
| 3 |
+
from typing import AsyncGenerator
|
| 4 |
+
import json
|
| 5 |
+
|
| 6 |
+
class ArchitectureAnalysis(BaseAgent):
|
| 7 |
+
async def __call__(self, state: AgentState) -> AsyncGenerator[str, None]:
|
| 8 |
+
prompt_template = """
|
| 9 |
+
Analyze the following project and identify system components:
|
| 10 |
+
|
| 11 |
+
Project: {project_name}
|
| 12 |
+
Description: {project_description}
|
| 13 |
+
Entities: {entities_classes}
|
| 14 |
+
Use Cases: {actors_use_cases}
|
| 15 |
+
|
| 16 |
+
Instructions:
|
| 17 |
+
1. Identify major system components/modules
|
| 18 |
+
2. List their responsibilities
|
| 19 |
+
3. Describe how they interact
|
| 20 |
+
4. Format as a structured component hierarchy
|
| 21 |
+
"""
|
| 22 |
+
|
| 23 |
+
async for chunk in self._stream_process(
|
| 24 |
+
state=state,
|
| 25 |
+
prompt_template=prompt_template,
|
| 26 |
+
output_key="architecture_components",
|
| 27 |
+
step_name="extract_architecture",
|
| 28 |
+
project_name=state["project_name"],
|
| 29 |
+
project_description=state["project_description"],
|
| 30 |
+
entities_classes=state["entities_classes"],
|
| 31 |
+
actors_use_cases=state["actors_use_cases"]
|
| 32 |
+
):
|
| 33 |
+
yield chunk
|
agents/analysis/deployment_analysis.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from base_agent import BaseAgent
|
| 2 |
+
from models import AgentState
|
| 3 |
+
from typing import AsyncGenerator
|
| 4 |
+
import json
|
| 5 |
+
|
| 6 |
+
class DeploymentAnalysis(BaseAgent):
|
| 7 |
+
async def __call__(self, state: AgentState) -> AsyncGenerator[str, None]:
|
| 8 |
+
prompt_template = """
|
| 9 |
+
Analyze the following project and identify deployment context:
|
| 10 |
+
|
| 11 |
+
Project: {project_name}
|
| 12 |
+
Description: {project_description}
|
| 13 |
+
Architecture Components: {architecture_components}
|
| 14 |
+
|
| 15 |
+
Instructions:
|
| 16 |
+
1. Identify the deployment nodes (servers, clients, etc.)
|
| 17 |
+
2. List the artifacts to be deployed
|
| 18 |
+
3. Show the relationships between nodes
|
| 19 |
+
4. Format as a structured list of deployment elements
|
| 20 |
+
"""
|
| 21 |
+
|
| 22 |
+
async for chunk in self._stream_process(
|
| 23 |
+
state=state,
|
| 24 |
+
prompt_template=prompt_template,
|
| 25 |
+
output_key="deployment_context",
|
| 26 |
+
step_name="extract_deployment",
|
| 27 |
+
project_name=state["project_name"],
|
| 28 |
+
project_description=state["project_description"],
|
| 29 |
+
architecture_components=state.get("architecture_components", "")
|
| 30 |
+
):
|
| 31 |
+
yield chunk
|
agents/analysis/entities_analysis.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from base_agent import BaseAgent
|
| 2 |
+
from models import AgentState
|
| 3 |
+
from typing import AsyncGenerator
|
| 4 |
+
import json
|
| 5 |
+
|
| 6 |
+
class EntitiesAnalysis(BaseAgent):
|
| 7 |
+
async def __call__(self, state: AgentState) -> AsyncGenerator[str, None]:
|
| 8 |
+
prompt_template = """
|
| 9 |
+
Conduct a comprehensive domain analysis for the following project to identify:
|
| 10 |
+
- Core entities
|
| 11 |
+
- Their attributes (with data types)
|
| 12 |
+
- Key relationships
|
| 13 |
+
- Important methods/operations
|
| 14 |
+
- Business rules/constraints
|
| 15 |
+
|
| 16 |
+
Project: {project_name}
|
| 17 |
+
Description: {project_description}
|
| 18 |
+
|
| 19 |
+
Format your response using the following structure for each entity:
|
| 20 |
+
|
| 21 |
+
### [EntityName]
|
| 22 |
+
**Description**: [Brief purpose description]
|
| 23 |
+
**Attributes**:
|
| 24 |
+
- name: type (constraints/notes)
|
| 25 |
+
**Relationships**:
|
| 26 |
+
- [Cardinality] to [RelatedEntity] (description)
|
| 27 |
+
**Methods**:
|
| 28 |
+
- methodName(params): returnType (description)
|
| 29 |
+
**Business Rules**:
|
| 30 |
+
- [Rule description]
|
| 31 |
+
|
| 32 |
+
Example:
|
| 33 |
+
### User
|
| 34 |
+
**Description**: Represents system users with authentication
|
| 35 |
+
**Attributes**:
|
| 36 |
+
- id: UUID (primary key)
|
| 37 |
+
- username: string (unique, 3-20 chars)
|
| 38 |
+
- email: string (valid email format)
|
| 39 |
+
- passwordHash: string (encrypted)
|
| 40 |
+
**Relationships**:
|
| 41 |
+
- One-to-Many to Order (user places orders)
|
| 42 |
+
**Methods**:
|
| 43 |
+
- authenticate(password: string): boolean
|
| 44 |
+
- placeOrder(items: List[CartItem]): Order
|
| 45 |
+
**Business Rules**:
|
| 46 |
+
- Password must be at least 8 characters
|
| 47 |
+
- Email must be verified before ordering
|
| 48 |
+
|
| 49 |
+
Provide this analysis for all major domain entities.
|
| 50 |
+
"""
|
| 51 |
+
|
| 52 |
+
async for chunk in self._stream_process(
|
| 53 |
+
state=state,
|
| 54 |
+
prompt_template=prompt_template,
|
| 55 |
+
output_key="entities_classes",
|
| 56 |
+
step_name="extract_entities",
|
| 57 |
+
project_name=state["project_name"],
|
| 58 |
+
project_description=state["project_description"]
|
| 59 |
+
):
|
| 60 |
+
yield chunk
|
agents/analysis/sequence_analysis.py
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from base_agent import BaseAgent
|
| 2 |
+
from models import AgentState
|
| 3 |
+
from typing import AsyncGenerator
|
| 4 |
+
import json
|
| 5 |
+
|
| 6 |
+
class SequenceAnalysis(BaseAgent):
|
| 7 |
+
async def __call__(self, state: AgentState) -> AsyncGenerator[str, None]:
|
| 8 |
+
prompt_template = """
|
| 9 |
+
Conduct a comprehensive sequence analysis for the key workflow in:
|
| 10 |
+
|
| 11 |
+
Project: {project_name}
|
| 12 |
+
Description: {project_description}
|
| 13 |
+
Use Cases: {actors_use_cases}
|
| 14 |
+
|
| 15 |
+
Analysis Requirements:
|
| 16 |
+
1. Identify all participating components:
|
| 17 |
+
- Primary actors (initiators)
|
| 18 |
+
- Secondary systems/services
|
| 19 |
+
- Internal components
|
| 20 |
+
2. For each interaction step specify:
|
| 21 |
+
- Sender and receiver
|
| 22 |
+
- Message content/type
|
| 23 |
+
- Synchronization points
|
| 24 |
+
- Alternative/error flows
|
| 25 |
+
3. Include timing considerations where relevant
|
| 26 |
+
4. Note any parallel/concurrent activities
|
| 27 |
+
5. Highlight critical system boundaries
|
| 28 |
+
|
| 29 |
+
Format your response as:
|
| 30 |
+
|
| 31 |
+
### Workflow: [Workflow Name]
|
| 32 |
+
#### Components:
|
| 33 |
+
- [Component1] (type/role)
|
| 34 |
+
- [Component2] (type/role)
|
| 35 |
+
|
| 36 |
+
#### Main Flow:
|
| 37 |
+
1. [ComponentA] -> [ComponentB]: [Message/Purpose]
|
| 38 |
+
- Preconditions: [required state]
|
| 39 |
+
- Postconditions: [resulting state]
|
| 40 |
+
- Variations: [alternate paths]
|
| 41 |
+
|
| 42 |
+
2. [ComponentB] --> [ComponentA]: [Response]
|
| 43 |
+
...
|
| 44 |
+
|
| 45 |
+
#### Parallel Flows:
|
| 46 |
+
- Concurrent with step 2: [Description]
|
| 47 |
+
|
| 48 |
+
#### Error Handling:
|
| 49 |
+
- At step 3: [Error Condition] → [Recovery Path]
|
| 50 |
+
|
| 51 |
+
Example:
|
| 52 |
+
### Workflow: Order Processing
|
| 53 |
+
#### Components:
|
| 54 |
+
- Customer (primary actor)
|
| 55 |
+
- OrderService (core system)
|
| 56 |
+
- PaymentGateway (external service)
|
| 57 |
+
|
| 58 |
+
#### Main Flow:
|
| 59 |
+
1. Customer -> OrderService: SubmitOrder(cart)
|
| 60 |
+
- Preconditions: Cart not empty, user authenticated
|
| 61 |
+
- Postconditions: Order pending payment
|
| 62 |
+
- Variations: Invalid items → Rejection notice
|
| 63 |
+
|
| 64 |
+
2. OrderService -> PaymentGateway: ProcessPayment(details)
|
| 65 |
+
...
|
| 66 |
+
"""
|
| 67 |
+
|
| 68 |
+
async for chunk in self._stream_process(
|
| 69 |
+
state=state,
|
| 70 |
+
prompt_template=prompt_template,
|
| 71 |
+
output_key="sequence_interactions",
|
| 72 |
+
step_name="extract_sequence",
|
| 73 |
+
project_name=state["project_name"],
|
| 74 |
+
project_description=state["project_description"],
|
| 75 |
+
actors_use_cases=state["actors_use_cases"]
|
| 76 |
+
):
|
| 77 |
+
yield chunk
|
agents/analysis/state_analysis.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from base_agent import BaseAgent
|
| 2 |
+
from models import AgentState
|
| 3 |
+
from typing import AsyncGenerator
|
| 4 |
+
import json
|
| 5 |
+
|
| 6 |
+
class StateAnalysis(BaseAgent):
|
| 7 |
+
async def __call__(self, state: AgentState) -> AsyncGenerator[str, None]:
|
| 8 |
+
prompt_template = """
|
| 9 |
+
Analyze the following project and identify state transitions for key entities:
|
| 10 |
+
|
| 11 |
+
Project: {project_name}
|
| 12 |
+
Description: {project_description}
|
| 13 |
+
Entities: {entities_classes}
|
| 14 |
+
Sequence: {sequence_interactions}
|
| 15 |
+
|
| 16 |
+
Instructions:
|
| 17 |
+
1. For each key entity, identify its possible states
|
| 18 |
+
2. List the events that trigger state transitions
|
| 19 |
+
3. Format as a structured list of states and transitions
|
| 20 |
+
"""
|
| 21 |
+
|
| 22 |
+
async for chunk in self._stream_process(
|
| 23 |
+
state=state,
|
| 24 |
+
prompt_template=prompt_template,
|
| 25 |
+
output_key="state_transitions",
|
| 26 |
+
step_name="extract_state_transitions",
|
| 27 |
+
project_name=state["project_name"],
|
| 28 |
+
project_description=state["project_description"],
|
| 29 |
+
entities_classes=state["entities_classes"],
|
| 30 |
+
sequence_interactions=state["sequence_interactions"]
|
| 31 |
+
):
|
| 32 |
+
yield chunk
|
agents/code/__init__.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .class_diagram_generator import ClassDiagramGenerator
|
| 2 |
+
from .use_case_diagram_generator import UseCaseDiagramGenerator
|
| 3 |
+
from .sequence_diagram_generator import SequenceDiagramGenerator
|
| 4 |
+
from .activity_diagram_generator import ActivityDiagramGenerator
|
| 5 |
+
from .component_diagram_generator import ComponentDiagramGenerator
|
| 6 |
+
from .deployment_diagram_generator import DeploymentDiagramGenerator
|
| 7 |
+
from .state_diagram_generator import StateDiagramGenerator
|
| 8 |
+
from .timing_diagram_generator import TimingDiagramGenerator
|
| 9 |
+
from .object_diagram_generator import ObjectDiagramGenerator
|
| 10 |
+
|
| 11 |
+
__all__ = [
|
| 12 |
+
'ClassDiagramGenerator',
|
| 13 |
+
'UseCaseDiagramGenerator',
|
| 14 |
+
'SequenceDiagramGenerator',
|
| 15 |
+
'ActivityDiagramGenerator',
|
| 16 |
+
'ComponentDiagramGenerator',
|
| 17 |
+
'DeploymentDiagramGenerator',
|
| 18 |
+
'StateDiagramGenerator',
|
| 19 |
+
'TimingDiagramGenerator',
|
| 20 |
+
'ObjectDiagramGenerator'
|
| 21 |
+
]
|
agents/code/activity_diagram_generator.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from base_agent import BaseAgent
|
| 2 |
+
from models import AgentState
|
| 3 |
+
from typing import AsyncGenerator
|
| 4 |
+
import json
|
| 5 |
+
|
| 6 |
+
class ActivityDiagramGenerator(BaseAgent):
|
| 7 |
+
async def __call__(self, state: AgentState) -> AsyncGenerator[str, None]:
|
| 8 |
+
prompt_template = """
|
| 9 |
+
Generate PlantUML code for an activity diagram based on the following project:
|
| 10 |
+
|
| 11 |
+
Project: {project_name}
|
| 12 |
+
Description: {project_description}
|
| 13 |
+
Use Cases: {actors_use_cases}
|
| 14 |
+
|
| 15 |
+
Instructions:
|
| 16 |
+
1. Show the main workflows as activities
|
| 17 |
+
2. Include decision points and parallel activities where appropriate
|
| 18 |
+
3. Use standard PlantUML activity diagram syntax
|
| 19 |
+
4. Don't include any explanation, just the PlantUML code
|
| 20 |
+
|
| 21 |
+
Example format (DO NOT TREAT THIS AS VARIABLES):
|
| 22 |
+
'''plantuml
|
| 23 |
+
@startuml
|
| 24 |
+
start
|
| 25 |
+
:Main Activity;
|
| 26 |
+
if (Decision?) then (yes)
|
| 27 |
+
:Activity 1;
|
| 28 |
+
else (no)
|
| 29 |
+
:Activity 2;
|
| 30 |
+
endif
|
| 31 |
+
stop
|
| 32 |
+
@enduml
|
| 33 |
+
'''
|
| 34 |
+
"""
|
| 35 |
+
|
| 36 |
+
async for chunk in self._stream_process(
|
| 37 |
+
state=state,
|
| 38 |
+
prompt_template=prompt_template,
|
| 39 |
+
output_key="activity_diagram",
|
| 40 |
+
step_name="generate_activity_diagram",
|
| 41 |
+
project_name=state["project_name"],
|
| 42 |
+
project_description=state["project_description"],
|
| 43 |
+
actors_use_cases=state["actors_use_cases"]
|
| 44 |
+
):
|
| 45 |
+
yield chunk
|
agents/code/class_diagram_generator.py
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from base_agent import BaseAgent
|
| 2 |
+
from models import AgentState
|
| 3 |
+
from typing import AsyncGenerator
|
| 4 |
+
import json
|
| 5 |
+
|
| 6 |
+
class ClassDiagramGenerator(BaseAgent):
|
| 7 |
+
async def __call__(self, state: AgentState) -> AsyncGenerator[str, None]:
|
| 8 |
+
prompt_template = """
|
| 9 |
+
Generate comprehensive PlantUML code for a class diagram based on the following domain analysis:
|
| 10 |
+
|
| 11 |
+
{entities_classes}
|
| 12 |
+
|
| 13 |
+
Instructions:
|
| 14 |
+
1. Include all classes with their complete attributes (visibility, type, constraints)
|
| 15 |
+
2. Use proper UML relationships with correct cardinality and labels:
|
| 16 |
+
- Inheritance: <|--
|
| 17 |
+
- Implementation: <|..
|
| 18 |
+
- Composition: *--
|
| 19 |
+
- Aggregation: o--
|
| 20 |
+
- Dependency: -->
|
| 21 |
+
3. For complex relationships, use association classes when needed
|
| 22 |
+
4. Include proper visibility markers:
|
| 23 |
+
- Public: +
|
| 24 |
+
- Private: -
|
| 25 |
+
- Protected: #
|
| 26 |
+
- Package: ~
|
| 27 |
+
5. Use advanced PlantUML features where appropriate:
|
| 28 |
+
- Abstract classes/interfaces
|
| 29 |
+
- Enumerations
|
| 30 |
+
- Stereotypes
|
| 31 |
+
- Notes and constraints
|
| 32 |
+
- Method signatures
|
| 33 |
+
6. Organize classes into packages if the domain suggests logical groupings
|
| 34 |
+
7. Use proper formatting with alignment and spacing
|
| 35 |
+
8. Include only the raw PlantUML code without explanations
|
| 36 |
+
|
| 37 |
+
Example Structure (DO NOT TREAT AS VARIABLES):
|
| 38 |
+
@startuml
|
| 39 |
+
package "Domain Model" {{
|
| 40 |
+
abstract class AbstractEntity {{
|
| 41 |
+
+ id: UUID {{readonly}}
|
| 42 |
+
# createdAt: DateTime
|
| 43 |
+
+ equals(other: Object): boolean {{abstract}}
|
| 44 |
+
}}
|
| 45 |
+
|
| 46 |
+
class User {{
|
| 47 |
+
+ username: String {{unique}}
|
| 48 |
+
+ email: String
|
| 49 |
+
- passwordHash: String
|
| 50 |
+
+ authenticate(password: String): boolean
|
| 51 |
+
}}
|
| 52 |
+
|
| 53 |
+
enum UserRole {{
|
| 54 |
+
ADMIN
|
| 55 |
+
MEMBER
|
| 56 |
+
GUEST
|
| 57 |
+
}}
|
| 58 |
+
|
| 59 |
+
User "1" *-- "0..*" Order : places >
|
| 60 |
+
User o-- UserRole : has >
|
| 61 |
+
|
| 62 |
+
(User, Order) .. OrderAssociation
|
| 63 |
+
class OrderAssociation {{
|
| 64 |
+
+ orderDate: DateTime
|
| 65 |
+
+ status: OrderStatus
|
| 66 |
+
}}
|
| 67 |
+
}}
|
| 68 |
+
|
| 69 |
+
note top of User
|
| 70 |
+
User represents all system users
|
| 71 |
+
with authentication capabilities
|
| 72 |
+
end note
|
| 73 |
+
@enduml
|
| 74 |
+
"""
|
| 75 |
+
|
| 76 |
+
async for chunk in self._stream_process(
|
| 77 |
+
state=state,
|
| 78 |
+
prompt_template=prompt_template,
|
| 79 |
+
output_key="class_diagram",
|
| 80 |
+
step_name="generate_class_diagram",
|
| 81 |
+
entities_classes=state["entities_classes"]
|
| 82 |
+
):
|
| 83 |
+
yield chunk
|
agents/code/component_diagram_generator.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from base_agent import BaseAgent
|
| 2 |
+
from models import AgentState
|
| 3 |
+
from typing import AsyncGenerator
|
| 4 |
+
import json
|
| 5 |
+
|
| 6 |
+
class ComponentDiagramGenerator(BaseAgent):
|
| 7 |
+
async def __call__(self, state: AgentState) -> AsyncGenerator[str, None]:
|
| 8 |
+
prompt_template = """
|
| 9 |
+
Generate PlantUML code for a component diagram based on the following project:
|
| 10 |
+
|
| 11 |
+
Project: {project_name}
|
| 12 |
+
Description: {project_description}
|
| 13 |
+
Entities: {entities_classes}
|
| 14 |
+
|
| 15 |
+
Instructions:
|
| 16 |
+
1. Show the main system components and their interfaces
|
| 17 |
+
2. Include relationships between components
|
| 18 |
+
3. Use standard PlantUML component diagram syntax
|
| 19 |
+
4. Don't include any explanation, just the PlantUML code
|
| 20 |
+
|
| 21 |
+
Example format (DO NOT TREAT THIS AS VARIABLES):
|
| 22 |
+
'''plantuml
|
| 23 |
+
@startuml
|
| 24 |
+
component [Component1] as C1
|
| 25 |
+
component [Component2] as C2
|
| 26 |
+
C1 --> C2 : uses
|
| 27 |
+
@enduml
|
| 28 |
+
'''
|
| 29 |
+
"""
|
| 30 |
+
|
| 31 |
+
async for chunk in self._stream_process(
|
| 32 |
+
state=state,
|
| 33 |
+
prompt_template=prompt_template,
|
| 34 |
+
output_key="component_diagram",
|
| 35 |
+
step_name="generate_component_diagram",
|
| 36 |
+
project_name=state["project_name"],
|
| 37 |
+
project_description=state["project_description"],
|
| 38 |
+
entities_classes=state["entities_classes"]
|
| 39 |
+
):
|
| 40 |
+
yield chunk
|
agents/code/deployment_diagram_generator.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from base_agent import BaseAgent
|
| 2 |
+
from models import AgentState
|
| 3 |
+
from typing import AsyncGenerator
|
| 4 |
+
import json
|
| 5 |
+
|
| 6 |
+
class DeploymentDiagramGenerator(BaseAgent):
|
| 7 |
+
async def __call__(self, state: AgentState) -> AsyncGenerator[str, None]:
|
| 8 |
+
prompt_template = """
|
| 9 |
+
Generate PlantUML code for a deployment diagram based on the following project:
|
| 10 |
+
|
| 11 |
+
Project: {project_name}
|
| 12 |
+
Description: {project_description}
|
| 13 |
+
|
| 14 |
+
Instructions:
|
| 15 |
+
1. Show the physical deployment architecture
|
| 16 |
+
2. Include nodes, artifacts, and their relationships
|
| 17 |
+
3. Use standard PlantUML deployment diagram syntax
|
| 18 |
+
4. Don't include any explanation, just the PlantUML code
|
| 19 |
+
|
| 20 |
+
Example format (DO NOT TREAT THIS AS VARIABLES):
|
| 21 |
+
'''plantuml
|
| 22 |
+
@startuml
|
| 23 |
+
node "Server" as server {{
|
| 24 |
+
artifact "WebApp"
|
| 25 |
+
}}
|
| 26 |
+
node "Client" as client
|
| 27 |
+
client --> server : HTTP
|
| 28 |
+
@enduml
|
| 29 |
+
'''
|
| 30 |
+
"""
|
| 31 |
+
|
| 32 |
+
async for chunk in self._stream_process(
|
| 33 |
+
state=state,
|
| 34 |
+
prompt_template=prompt_template,
|
| 35 |
+
output_key="deployment_diagram",
|
| 36 |
+
step_name="generate_deployment_diagram",
|
| 37 |
+
project_name=state["project_name"],
|
| 38 |
+
project_description=state["project_description"]
|
| 39 |
+
):
|
| 40 |
+
yield chunk
|
agents/code/object_diagram_generator.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from base_agent import BaseAgent
|
| 2 |
+
from models import AgentState
|
| 3 |
+
from typing import AsyncGenerator
|
| 4 |
+
import json
|
| 5 |
+
|
| 6 |
+
class ObjectDiagramGenerator(BaseAgent):
|
| 7 |
+
async def __call__(self, state: AgentState) -> AsyncGenerator[str, None]:
|
| 8 |
+
prompt_template = """
|
| 9 |
+
Generate PlantUML code for an object diagram based on the following project:
|
| 10 |
+
|
| 11 |
+
Project: {project_name}
|
| 12 |
+
Description: {project_description}
|
| 13 |
+
Entities: {entities_classes}
|
| 14 |
+
|
| 15 |
+
Instructions:
|
| 16 |
+
1. Show object instances and their relationships
|
| 17 |
+
2. Include attribute values for a specific scenario
|
| 18 |
+
3. Use standard PlantUML object diagram syntax
|
| 19 |
+
4. Don't include any explanation, just the PlantUML code
|
| 20 |
+
|
| 21 |
+
Example format (DO NOT TREAT THIS AS VARIABLES):
|
| 22 |
+
'''plantuml
|
| 23 |
+
@startuml
|
| 24 |
+
object "Object1" as o1 {{
|
| 25 |
+
attribute1 = value1
|
| 26 |
+
attribute2 = value2
|
| 27 |
+
}}
|
| 28 |
+
object "Object2" as o2 {{
|
| 29 |
+
attribute3 = value3
|
| 30 |
+
}}
|
| 31 |
+
o1 --> o2 : relationship
|
| 32 |
+
@enduml
|
| 33 |
+
'''
|
| 34 |
+
"""
|
| 35 |
+
|
| 36 |
+
async for chunk in self._stream_process(
|
| 37 |
+
state=state,
|
| 38 |
+
prompt_template=prompt_template,
|
| 39 |
+
output_key="object_diagram",
|
| 40 |
+
step_name="generate_object_diagram",
|
| 41 |
+
project_name=state["project_name"],
|
| 42 |
+
project_description=state["project_description"],
|
| 43 |
+
entities_classes=state["entities_classes"]
|
| 44 |
+
):
|
| 45 |
+
yield chunk
|
agents/code/sequence_diagram_generator.py
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from base_agent import BaseAgent
|
| 2 |
+
from models import AgentState
|
| 3 |
+
from typing import AsyncGenerator
|
| 4 |
+
import json
|
| 5 |
+
|
| 6 |
+
class SequenceDiagramGenerator(BaseAgent):
|
| 7 |
+
async def __call__(self, state: AgentState) -> AsyncGenerator[str, None]:
|
| 8 |
+
prompt_template = """
|
| 9 |
+
Generate comprehensive PlantUML code for a sequence diagram based on:
|
| 10 |
+
|
| 11 |
+
Workflow Analysis: {sequence_interactions}
|
| 12 |
+
|
| 13 |
+
Instructions:
|
| 14 |
+
1. Use proper participant types:
|
| 15 |
+
- actor for human users
|
| 16 |
+
- boundary for UI elements
|
| 17 |
+
- control for controllers
|
| 18 |
+
- entity for domain objects
|
| 19 |
+
- database for storage
|
| 20 |
+
2. Include all message types:
|
| 21 |
+
- Synchronous: ->
|
| 22 |
+
- Asynchronous: ->>
|
| 23 |
+
- Return: -->
|
| 24 |
+
- Self: -> self
|
| 25 |
+
3. Add activation bars with:
|
| 26 |
+
- activate/deactivate
|
| 27 |
+
- Nested activations where needed
|
| 28 |
+
4. Use advanced features:
|
| 29 |
+
- Groups (alt/opt/loop/par)
|
| 30 |
+
- Notes
|
| 31 |
+
- Dividers (== Sections ==)
|
| 32 |
+
- Stereotypes <<component>>
|
| 33 |
+
- Arrows with:
|
| 34 |
+
* Lost messages (->x)
|
| 35 |
+
* Found messages (x->)
|
| 36 |
+
* Parallel (->||)
|
| 37 |
+
5. Format for readability:
|
| 38 |
+
- Proper indentation
|
| 39 |
+
- Section comments
|
| 40 |
+
- Color coding where helpful
|
| 41 |
+
|
| 42 |
+
Example Structure (DO NOT TREAT AS VARIABLES):
|
| 43 |
+
@startuml
|
| 44 |
+
skinparam style strictuml
|
| 45 |
+
skinparam lifelineStrategy solid
|
| 46 |
+
|
| 47 |
+
actor Customer <<Human>>
|
| 48 |
+
boundary "Web UI" as UI
|
| 49 |
+
control OrderController
|
| 50 |
+
entity Order
|
| 51 |
+
database OrderDB
|
| 52 |
+
|
| 53 |
+
== Order Submission ==
|
| 54 |
+
Customer -> UI: Submit Order
|
| 55 |
+
activate UI
|
| 56 |
+
UI -> OrderController: processOrder()
|
| 57 |
+
activate OrderController
|
| 58 |
+
|
| 59 |
+
group Payment Processing
|
| 60 |
+
OrderController -> PaymentService <<External>>: authorizePayment()
|
| 61 |
+
activate PaymentService
|
| 62 |
+
PaymentService --> OrderController: status
|
| 63 |
+
deactivate PaymentService
|
| 64 |
+
end
|
| 65 |
+
|
| 66 |
+
alt payment approved
|
| 67 |
+
OrderController -> OrderDB: persistOrder()
|
| 68 |
+
OrderController --> UI: confirmation
|
| 69 |
+
else payment declined
|
| 70 |
+
OrderController --> UI: error
|
| 71 |
+
end
|
| 72 |
+
|
| 73 |
+
deactivate OrderController
|
| 74 |
+
UI --> Customer: Order Status
|
| 75 |
+
deactivate UI
|
| 76 |
+
@enduml
|
| 77 |
+
"""
|
| 78 |
+
|
| 79 |
+
async for chunk in self._stream_process(
|
| 80 |
+
state=state,
|
| 81 |
+
prompt_template=prompt_template,
|
| 82 |
+
output_key="sequence_diagram",
|
| 83 |
+
step_name="generate_sequence_diagram",
|
| 84 |
+
sequence_interactions=state["sequence_interactions"]
|
| 85 |
+
):
|
| 86 |
+
yield chunk
|
agents/code/state_diagram_generator.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from base_agent import BaseAgent
|
| 2 |
+
from models import AgentState
|
| 3 |
+
from typing import AsyncGenerator
|
| 4 |
+
import json
|
| 5 |
+
|
| 6 |
+
class StateDiagramGenerator(BaseAgent):
|
| 7 |
+
async def __call__(self, state: AgentState) -> AsyncGenerator[str, None]:
|
| 8 |
+
prompt_template = """
|
| 9 |
+
Generate PlantUML code for a state diagram based on the following project:
|
| 10 |
+
|
| 11 |
+
Project: {project_name}
|
| 12 |
+
Description: {project_description}
|
| 13 |
+
Entities: {entities_classes}
|
| 14 |
+
|
| 15 |
+
Instructions:
|
| 16 |
+
1. Show the state transitions for one key entity
|
| 17 |
+
2. Include states, transitions, and events
|
| 18 |
+
3. Use standard PlantUML state diagram syntax
|
| 19 |
+
4. Don't include any explanation, just the PlantUML code
|
| 20 |
+
|
| 21 |
+
Example format (DO NOT TREAT THIS AS VARIABLES):
|
| 22 |
+
'''plantuml
|
| 23 |
+
@startuml
|
| 24 |
+
[*] --> State1
|
| 25 |
+
State1 --> State2 : Event1
|
| 26 |
+
State2 --> [*]
|
| 27 |
+
@enduml
|
| 28 |
+
'''
|
| 29 |
+
"""
|
| 30 |
+
|
| 31 |
+
async for chunk in self._stream_process(
|
| 32 |
+
state=state,
|
| 33 |
+
prompt_template=prompt_template,
|
| 34 |
+
output_key="state_diagram",
|
| 35 |
+
step_name="generate_state_diagram",
|
| 36 |
+
project_name=state["project_name"],
|
| 37 |
+
project_description=state["project_description"],
|
| 38 |
+
entities_classes=state["entities_classes"]
|
| 39 |
+
):
|
| 40 |
+
yield chunk
|
agents/code/timing_diagram_generator.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from base_agent import BaseAgent
|
| 2 |
+
from models import AgentState
|
| 3 |
+
from typing import AsyncGenerator
|
| 4 |
+
import json
|
| 5 |
+
|
| 6 |
+
class TimingDiagramGenerator(BaseAgent):
|
| 7 |
+
async def __call__(self, state: AgentState) -> AsyncGenerator[str, None]:
|
| 8 |
+
prompt_template = """
|
| 9 |
+
Generate PlantUML code for a timing diagram based on the following project:
|
| 10 |
+
|
| 11 |
+
Project: {project_name}
|
| 12 |
+
Description: {project_description}
|
| 13 |
+
Sequence: {sequence_interactions}
|
| 14 |
+
|
| 15 |
+
Instructions:
|
| 16 |
+
1. Show the timing constraints between components
|
| 17 |
+
2. Include lifelines and state changes over time
|
| 18 |
+
3. Use standard PlantUML timing diagram syntax
|
| 19 |
+
4. Don't include any explanation, just the PlantUML code
|
| 20 |
+
|
| 21 |
+
Example format (DO NOT TREAT THIS AS VARIABLES):
|
| 22 |
+
'''plantuml
|
| 23 |
+
@startuml
|
| 24 |
+
clock "Clock" as C with period 1
|
| 25 |
+
binary "Signal" as S
|
| 26 |
+
@0
|
| 27 |
+
S is low
|
| 28 |
+
@5
|
| 29 |
+
S is high
|
| 30 |
+
@10
|
| 31 |
+
S is low
|
| 32 |
+
@enduml
|
| 33 |
+
'''
|
| 34 |
+
"""
|
| 35 |
+
|
| 36 |
+
async for chunk in self._stream_process(
|
| 37 |
+
state=state,
|
| 38 |
+
prompt_template=prompt_template,
|
| 39 |
+
output_key="timing_diagram",
|
| 40 |
+
step_name="generate_timing_diagram",
|
| 41 |
+
project_name=state["project_name"],
|
| 42 |
+
project_description=state["project_description"],
|
| 43 |
+
sequence_interactions=state["sequence_interactions"]
|
| 44 |
+
):
|
| 45 |
+
yield chunk
|
agents/code/use_case_diagram_generator.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from base_agent import BaseAgent
|
| 2 |
+
from models import AgentState
|
| 3 |
+
from typing import AsyncGenerator
|
| 4 |
+
import json
|
| 5 |
+
|
| 6 |
+
class UseCaseDiagramGenerator(BaseAgent):
|
| 7 |
+
async def __call__(self, state: AgentState) -> AsyncGenerator[str, None]:
|
| 8 |
+
prompt_template = """
|
| 9 |
+
Generate comprehensive PlantUML code for a use case diagram based on:
|
| 10 |
+
|
| 11 |
+
Project: {project_name}
|
| 12 |
+
Actors and Use Cases: {actors_use_cases}
|
| 13 |
+
|
| 14 |
+
Instructions:
|
| 15 |
+
1. Include all actors with proper typing:
|
| 16 |
+
- Primary actors on left
|
| 17 |
+
- Supporting actors on right
|
| 18 |
+
- System actors if applicable
|
| 19 |
+
2. Organize use cases into logical packages/subsystems when appropriate
|
| 20 |
+
3. Use proper relationship types:
|
| 21 |
+
- Association: --> (actor to use case)
|
| 22 |
+
- Include: .> (base use case includes another)
|
| 23 |
+
- Extend: .> (use case extends another with conditions)
|
| 24 |
+
4. Apply stereotypes where helpful (<<system>>, <<external>>, etc.)
|
| 25 |
+
5. Include notes for complex relationships when needed
|
| 26 |
+
6. Use left-to-right direction for better readability
|
| 27 |
+
7. Format with clear alignment and spacing
|
| 28 |
+
8. Include only raw PlantUML code without explanations
|
| 29 |
+
|
| 30 |
+
Example Structure (DO NOT TREAT AS VARIABLES):
|
| 31 |
+
@startuml
|
| 32 |
+
left to right direction
|
| 33 |
+
skinparam packageStyle rectangle
|
| 34 |
+
|
| 35 |
+
actor "Primary Actor" <<Human>> as user
|
| 36 |
+
actor "Supporting System" <<System>> as legacy
|
| 37 |
+
|
| 38 |
+
rectangle "Order Processing" {{
|
| 39 |
+
usecase "Place Order" as UC1
|
| 40 |
+
usecase "Process Payment" as UC2
|
| 41 |
+
usecase "Validate Order" as UC3
|
| 42 |
+
|
| 43 |
+
UC1 .> UC2 : include
|
| 44 |
+
UC1 .> UC3 : include
|
| 45 |
+
}}
|
| 46 |
+
|
| 47 |
+
rectangle "Inventory" {{
|
| 48 |
+
usecase "Check Stock" as UC4
|
| 49 |
+
}}
|
| 50 |
+
|
| 51 |
+
user --> UC1
|
| 52 |
+
legacy --> UC3
|
| 53 |
+
|
| 54 |
+
note right of UC1
|
| 55 |
+
This use case initiates the
|
| 56 |
+
order fulfillment workflow
|
| 57 |
+
end note
|
| 58 |
+
@enduml
|
| 59 |
+
"""
|
| 60 |
+
|
| 61 |
+
async for chunk in self._stream_process(
|
| 62 |
+
state=state,
|
| 63 |
+
prompt_template=prompt_template,
|
| 64 |
+
output_key="use_case_diagram",
|
| 65 |
+
step_name="generate_use_case_diagram",
|
| 66 |
+
actors_use_cases=state["actors_use_cases"],
|
| 67 |
+
project_name=state["project_name"]
|
| 68 |
+
):
|
| 69 |
+
yield chunk
|
app.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from flask import Flask, jsonify, render_template, request, Response, stream_with_context
|
| 2 |
+
from research_generator import ResearchGenerator
|
| 3 |
+
import asyncio
|
| 4 |
+
import json
|
| 5 |
+
from langchain_core.output_parsers import StrOutputParser
|
| 6 |
+
from langchain_core.prompts import ChatPromptTemplate
|
| 7 |
+
|
| 8 |
+
app = Flask(__name__)
|
| 9 |
+
generator = ResearchGenerator()
|
| 10 |
+
|
| 11 |
+
@app.route('/')
|
| 12 |
+
def index():
|
| 13 |
+
return render_template('index.html')
|
| 14 |
+
|
| 15 |
+
async def process_state(state):
|
| 16 |
+
if "index" in state and state["index"]:
|
| 17 |
+
# If we have an index, process it with the LLM
|
| 18 |
+
prompt = ChatPromptTemplate.from_template("""
|
| 19 |
+
Process the following research state:
|
| 20 |
+
{state}
|
| 21 |
+
""")
|
| 22 |
+
chain = prompt | generator.llm | StrOutputParser()
|
| 23 |
+
|
| 24 |
+
async for chunk in chain.astream({"state": json.dumps(state)}):
|
| 25 |
+
yield f"data: {json.dumps({'chunk': chunk, 'state': state})}\n\n"
|
| 26 |
+
else:
|
| 27 |
+
# If we're generating the index, stream it directly
|
| 28 |
+
yield f"data: {json.dumps({'state': state})}\n\n"
|
| 29 |
+
|
| 30 |
+
@app.route('/generate', methods=['POST'])
|
| 31 |
+
def generate_research():
|
| 32 |
+
data = request.get_json()
|
| 33 |
+
subject = data.get('subject')
|
| 34 |
+
if not subject:
|
| 35 |
+
return jsonify({'error': 'Subject is required'}), 400
|
| 36 |
+
|
| 37 |
+
async def generate():
|
| 38 |
+
generator = ResearchGenerator()
|
| 39 |
+
async for state in generator.astream({
|
| 40 |
+
"subject": subject,
|
| 41 |
+
"index": [],
|
| 42 |
+
"content": {},
|
| 43 |
+
"current_step": 0
|
| 44 |
+
}):
|
| 45 |
+
async for chunk in process_state(state):
|
| 46 |
+
yield chunk
|
| 47 |
+
|
| 48 |
+
def sync_generate():
|
| 49 |
+
loop = asyncio.new_event_loop()
|
| 50 |
+
asyncio.set_event_loop(loop)
|
| 51 |
+
try:
|
| 52 |
+
async_gen = generate()
|
| 53 |
+
while True:
|
| 54 |
+
try:
|
| 55 |
+
chunk = loop.run_until_complete(async_gen.__anext__())
|
| 56 |
+
yield chunk
|
| 57 |
+
except StopAsyncIteration:
|
| 58 |
+
break
|
| 59 |
+
finally:
|
| 60 |
+
loop.close()
|
| 61 |
+
|
| 62 |
+
return Response(
|
| 63 |
+
stream_with_context(sync_generate()),
|
| 64 |
+
mimetype='text/event-stream'
|
| 65 |
+
)
|
| 66 |
+
|
| 67 |
+
if __name__ == '__main__':
|
| 68 |
+
app.run(debug=True)
|
base_agent.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
from typing import Dict, Generator, AsyncGenerator
|
| 3 |
+
from langchain_core.prompts import ChatPromptTemplate
|
| 4 |
+
from langchain_core.callbacks import StreamingStdOutCallbackHandler
|
| 5 |
+
from config import Config
|
| 6 |
+
from logger import Logger
|
| 7 |
+
from file_manager import FileManager
|
| 8 |
+
from models import AgentState
|
| 9 |
+
from langchain_ollama import OllamaLLM
|
| 10 |
+
from langchain_core.runnables import RunnableConfig
|
| 11 |
+
from langchain_core.output_parsers import StrOutputParser
|
| 12 |
+
|
| 13 |
+
class BaseAgent:
|
| 14 |
+
def __init__(self):
|
| 15 |
+
self.config = Config()
|
| 16 |
+
self.logger = Logger()
|
| 17 |
+
self.file_manager = FileManager()
|
| 18 |
+
|
| 19 |
+
# Set up callbacks based on streaming config
|
| 20 |
+
callbacks = [StreamingStdOutCallbackHandler()] if self.config.streaming else None
|
| 21 |
+
|
| 22 |
+
self.llm = OllamaLLM(
|
| 23 |
+
model=self.config.ollama_model,
|
| 24 |
+
base_url=self.config.ollama_base_url,
|
| 25 |
+
temperature=self.config.temperature,
|
| 26 |
+
top_p=self.config.top_p,
|
| 27 |
+
callbacks=callbacks,
|
| 28 |
+
|
| 29 |
+
)
|
| 30 |
+
|
| 31 |
+
async def _stream_process(self, state: AgentState, prompt_template: str, output_key: str, step_name: str, **kwargs) -> AsyncGenerator[str, None]:
|
| 32 |
+
prompt = ChatPromptTemplate.from_template(prompt_template)
|
| 33 |
+
chain = prompt | self.llm | StrOutputParser()
|
| 34 |
+
|
| 35 |
+
# Use stream method to get partial chunks
|
| 36 |
+
async for chunk in chain.astream(kwargs):
|
| 37 |
+
yield json.dumps({output_key: chunk})
|
| 38 |
+
|
| 39 |
+
async def _process(self, state: AgentState, prompt_template: str,
|
| 40 |
+
output_key: str, step_name: str, **kwargs) -> Dict:
|
| 41 |
+
prompt = ChatPromptTemplate.from_template(prompt_template)
|
| 42 |
+
chain = prompt | self.llm
|
| 43 |
+
|
| 44 |
+
result = await chain.ainvoke(kwargs)
|
| 45 |
+
|
| 46 |
+
return {output_key: result}
|
config.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from dotenv import load_dotenv
|
| 2 |
+
import os
|
| 3 |
+
from enum import Enum
|
| 4 |
+
|
| 5 |
+
load_dotenv()
|
| 6 |
+
|
| 7 |
+
class DiagramType(Enum):
|
| 8 |
+
CLASS = 1
|
| 9 |
+
USE_CASE = 2
|
| 10 |
+
SEQUENCE = 3
|
| 11 |
+
OBJECT = 4
|
| 12 |
+
ACTIVITY = 5
|
| 13 |
+
COMPONENT = 6
|
| 14 |
+
DEPLOYMENT = 7
|
| 15 |
+
STATE = 8
|
| 16 |
+
TIMING = 9
|
| 17 |
+
|
| 18 |
+
class Config:
|
| 19 |
+
def __init__(self):
|
| 20 |
+
self.ollama_model = os.getenv("OLLAMA_MODEL", "llama3.3:70b")
|
| 21 |
+
self.ollama_base_url = os.getenv("OLLAMA_BASE_URL", "http://localhost:11434")
|
| 22 |
+
self.temperature = float(os.getenv("TEMPERATURE", "0.7"))
|
| 23 |
+
self.top_p = float(os.getenv("TOP_P", "0.9"))
|
| 24 |
+
self.streaming = True#os.getenv("STREAMING", "false").lower() == "true"
|
| 25 |
+
self.callbacks = None
|
d.sh
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
git add .
|
| 2 |
+
git commit -m "Add application file"
|
| 3 |
+
git push
|
file_manager.py
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from datetime import datetime
|
| 3 |
+
from typing import Dict
|
| 4 |
+
import subprocess
|
| 5 |
+
|
| 6 |
+
class FileManager:
|
| 7 |
+
def __init__(self):
|
| 8 |
+
self.output_base = "output"
|
| 9 |
+
|
| 10 |
+
def create_output_dir(self, project_name: str, timestamp: str) -> str:
|
| 11 |
+
"""Create output directory structure for this run."""
|
| 12 |
+
dir_name = f"{project_name}_{timestamp}"
|
| 13 |
+
output_dir = os.path.join(self.output_base, dir_name)
|
| 14 |
+
|
| 15 |
+
# Create main directories
|
| 16 |
+
os.makedirs(output_dir, exist_ok=True)
|
| 17 |
+
os.makedirs(os.path.join(output_dir, "code"), exist_ok=True)
|
| 18 |
+
os.makedirs(os.path.join(output_dir, "analysis"), exist_ok=True)
|
| 19 |
+
os.makedirs(os.path.join(output_dir, "images"), exist_ok=True)
|
| 20 |
+
|
| 21 |
+
return output_dir
|
| 22 |
+
|
| 23 |
+
def save_step_result(self, output_dir: str, step_name: str, content: str):
|
| 24 |
+
"""Save intermediate step result with clean PlantUML code."""
|
| 25 |
+
ext = ".pu" if "diagram" in step_name else ".md"
|
| 26 |
+
|
| 27 |
+
# Determine target directory based on file type
|
| 28 |
+
if ext == ".pu":
|
| 29 |
+
target_dir = os.path.join(output_dir, "code")
|
| 30 |
+
# Render PlantUML diagram
|
| 31 |
+
self._render_plantuml(content, output_dir, step_name)
|
| 32 |
+
else:
|
| 33 |
+
target_dir = os.path.join(output_dir, "analysis")
|
| 34 |
+
|
| 35 |
+
filename = f"{step_name}{ext}"
|
| 36 |
+
filepath = os.path.join(target_dir, filename)
|
| 37 |
+
|
| 38 |
+
print(f"Saving {filename} to {target_dir}") # Add this line for debugging
|
| 39 |
+
|
| 40 |
+
# Clean PlantUML code by removing markdown code block markers
|
| 41 |
+
if ext == ".pu":
|
| 42 |
+
content = self._clean_plantuml_content(content)
|
| 43 |
+
|
| 44 |
+
with open(filepath, "w") as f:
|
| 45 |
+
f.write(content)
|
| 46 |
+
|
| 47 |
+
def _render_plantuml(self, content: str, output_dir: str, step_name: str):
|
| 48 |
+
"""Render PlantUML diagram to PNG."""
|
| 49 |
+
# Clean the content first
|
| 50 |
+
content = self._clean_plantuml_content(content)
|
| 51 |
+
|
| 52 |
+
# Create temporary .pu file
|
| 53 |
+
temp_pu = os.path.join(output_dir, "code", f"{step_name}.pu")
|
| 54 |
+
with open(temp_pu, "w") as f:
|
| 55 |
+
f.write(content)
|
| 56 |
+
|
| 57 |
+
# Render to PNG
|
| 58 |
+
output_png = os.path.join(output_dir, "images", f"{step_name}.png")
|
| 59 |
+
try:
|
| 60 |
+
subprocess.run([
|
| 61 |
+
"plantuml",
|
| 62 |
+
"-tpng",
|
| 63 |
+
temp_pu,
|
| 64 |
+
"-o", "images"
|
| 65 |
+
], check=True)
|
| 66 |
+
except subprocess.CalledProcessError as e:
|
| 67 |
+
print(f"Error rendering PlantUML diagram: {e}")
|
| 68 |
+
except FileNotFoundError:
|
| 69 |
+
print("PlantUML not found. Please install PlantUML to generate images.")
|
| 70 |
+
|
| 71 |
+
def _clean_plantuml_content(self, content: str) -> str:
|
| 72 |
+
"""Remove triple quotes and plantuml markers from the content."""
|
| 73 |
+
# Remove '''plantuml and ''' markers
|
| 74 |
+
content = content.replace("'''plantuml", "").replace("'''", "")
|
| 75 |
+
content = content.replace("```plantuml", "").replace("```", "")
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
# Remove any remaining leading/trailing whitespace
|
| 79 |
+
content = content.strip()
|
| 80 |
+
|
| 81 |
+
return content
|
| 82 |
+
|
| 83 |
+
def save_final_results(self, state: Dict):
|
| 84 |
+
"""Save all final results."""
|
| 85 |
+
output_dir = state["output_dir"]
|
| 86 |
+
|
| 87 |
+
# Save all analysis results and diagram codes
|
| 88 |
+
for key, value in state.items():
|
| 89 |
+
# Skip metadata fields
|
| 90 |
+
if key in ["project_name", "project_description", "output_dir", "selected_diagrams"]:
|
| 91 |
+
continue
|
| 92 |
+
|
| 93 |
+
if value: # Only save if there's content
|
| 94 |
+
self.save_step_result(output_dir, key, value)
|
| 95 |
+
|
| 96 |
+
# Save metadata
|
| 97 |
+
metadata = {
|
| 98 |
+
"project_name": state["project_name"],
|
| 99 |
+
"project_description": state["project_description"],
|
| 100 |
+
"generated_diagrams": state["selected_diagrams"],
|
| 101 |
+
"timestamp": datetime.now().isoformat()
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
import json
|
| 105 |
+
with open(os.path.join(output_dir, "metadata.json"), "w") as f:
|
| 106 |
+
json.dump(metadata, f, indent=2)
|
logger.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
class Logger:
|
| 2 |
+
def __init__(self):
|
| 3 |
+
self.verbose = True
|
| 4 |
+
|
| 5 |
+
def log(self, message: str):
|
| 6 |
+
if self.verbose:
|
| 7 |
+
print(message)
|
| 8 |
+
|
| 9 |
+
def log_title(self, title: str):
|
| 10 |
+
if self.verbose:
|
| 11 |
+
print(f"\n{'='*40}")
|
| 12 |
+
print(f"{title.center(40)}")
|
| 13 |
+
print(f"{'='*40}")
|
| 14 |
+
|
| 15 |
+
def log_step(self, step_name: str):
|
| 16 |
+
if self.verbose:
|
| 17 |
+
print(f"\n{'='*40}")
|
| 18 |
+
print(f"STEP: {step_name.upper().replace('_', ' ')}")
|
| 19 |
+
print(f"{'='*40}")
|
| 20 |
+
|
| 21 |
+
def log_success(self, message: str):
|
| 22 |
+
if self.verbose:
|
| 23 |
+
print(f"\n✅ {message}")
|
| 24 |
+
|
| 25 |
+
def log_error(self, message: str):
|
| 26 |
+
print(f"\n❌ ERROR: {message}")
|
main.py
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from logging import Logger
|
| 2 |
+
import os
|
| 3 |
+
from datetime import datetime
|
| 4 |
+
from typing import Dict, List, Optional
|
| 5 |
+
from dotenv import load_dotenv
|
| 6 |
+
from langchain_ollama import OllamaLLM
|
| 7 |
+
from langgraph.graph import END, StateGraph
|
| 8 |
+
from models import AgentState, DiagramType
|
| 9 |
+
from config import Config
|
| 10 |
+
from langchain_core.prompts import ChatPromptTemplate
|
| 11 |
+
from file_manager import FileManager
|
| 12 |
+
from agents.analysis import (
|
| 13 |
+
EntitiesAnalysis,
|
| 14 |
+
ActorsAnalysis,
|
| 15 |
+
SequenceAnalysis,
|
| 16 |
+
StateAnalysis,
|
| 17 |
+
ArchitectureAnalysis,
|
| 18 |
+
DeploymentAnalysis
|
| 19 |
+
)
|
| 20 |
+
from agents.code import (
|
| 21 |
+
ClassDiagramGenerator,
|
| 22 |
+
UseCaseDiagramGenerator,
|
| 23 |
+
SequenceDiagramGenerator,
|
| 24 |
+
ActivityDiagramGenerator,
|
| 25 |
+
ComponentDiagramGenerator,
|
| 26 |
+
DeploymentDiagramGenerator,
|
| 27 |
+
StateDiagramGenerator,
|
| 28 |
+
TimingDiagramGenerator,
|
| 29 |
+
ObjectDiagramGenerator
|
| 30 |
+
)
|
| 31 |
+
from fastapi import FastAPI, HTTPException
|
| 32 |
+
from fastapi.responses import StreamingResponse
|
| 33 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 34 |
+
from pydantic import BaseModel
|
| 35 |
+
import asyncio
|
| 36 |
+
from langchain_core.callbacks import StreamingStdOutCallbackHandler
|
| 37 |
+
import json
|
| 38 |
+
|
| 39 |
+
# Load environment variables
|
| 40 |
+
load_dotenv()
|
| 41 |
+
|
| 42 |
+
# Initialize FastAPI app
|
| 43 |
+
app = FastAPI()
|
| 44 |
+
|
| 45 |
+
# Add CORS middleware configuration
|
| 46 |
+
app.add_middleware(
|
| 47 |
+
CORSMiddleware,
|
| 48 |
+
allow_origins=["*"],
|
| 49 |
+
allow_credentials=True,
|
| 50 |
+
allow_methods=["*"],
|
| 51 |
+
allow_headers=["*"],
|
| 52 |
+
)
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
# Request model
|
| 56 |
+
class DiagramRequest(BaseModel):
|
| 57 |
+
project_name: str
|
| 58 |
+
project_description: str
|
| 59 |
+
selected_diagrams: List[int]
|
| 60 |
+
output_dir: Optional[str] = "output"
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
@app.post("/generate-diagrams")
|
| 66 |
+
async def generate_diagrams(request: DiagramRequest):
|
| 67 |
+
try:
|
| 68 |
+
initial_state = AgentState(
|
| 69 |
+
project_name=request.project_name,
|
| 70 |
+
project_description=request.project_description,
|
| 71 |
+
output_dir=request.output_dir,
|
| 72 |
+
selected_diagrams=request.selected_diagrams,
|
| 73 |
+
entities_classes="",
|
| 74 |
+
actors_use_cases="",
|
| 75 |
+
sequence_interactions="",
|
| 76 |
+
class_diagram="",
|
| 77 |
+
use_case_diagram="",
|
| 78 |
+
sequence_diagram="",
|
| 79 |
+
activity_diagram="",
|
| 80 |
+
component_diagram="",
|
| 81 |
+
deployment_diagram="",
|
| 82 |
+
state_diagram="",
|
| 83 |
+
timing_diagram="",
|
| 84 |
+
object_diagram=""
|
| 85 |
+
)
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
diagram_requirements = {
|
| 89 |
+
DiagramType.CLASS: ["extract_entities"],
|
| 90 |
+
DiagramType.USE_CASE: ["extract_actors"],
|
| 91 |
+
DiagramType.SEQUENCE: ["extract_actors", "extract_sequence"],
|
| 92 |
+
DiagramType.OBJECT: ["extract_entities"],
|
| 93 |
+
DiagramType.ACTIVITY: ["extract_actors"],
|
| 94 |
+
DiagramType.COMPONENT: ["extract_entities", "extract_architecture"],
|
| 95 |
+
DiagramType.DEPLOYMENT: ["extract_architecture", "extract_deployment"],
|
| 96 |
+
DiagramType.STATE: ["extract_entities", "extract_sequence", "extract_states"],
|
| 97 |
+
DiagramType.TIMING: ["extract_sequence"]
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
analysis_functions = {
|
| 102 |
+
"extract_entities": EntitiesAnalysis(),
|
| 103 |
+
"extract_actors": ActorsAnalysis(),
|
| 104 |
+
"extract_sequence": SequenceAnalysis(),
|
| 105 |
+
"extract_states": StateAnalysis(),
|
| 106 |
+
"extract_architecture": ArchitectureAnalysis(),
|
| 107 |
+
"extract_deployment": DeploymentAnalysis()
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
generation_functions = {
|
| 111 |
+
DiagramType.CLASS: ("generate_class", ClassDiagramGenerator()),
|
| 112 |
+
DiagramType.USE_CASE: ("generate_use_case", UseCaseDiagramGenerator()),
|
| 113 |
+
DiagramType.SEQUENCE: ("generate_sequence", SequenceDiagramGenerator()),
|
| 114 |
+
DiagramType.ACTIVITY: ("generate_activity", ActivityDiagramGenerator()),
|
| 115 |
+
DiagramType.COMPONENT: ("generate_component", ComponentDiagramGenerator()),
|
| 116 |
+
DiagramType.DEPLOYMENT: ("generate_deployment", DeploymentDiagramGenerator()),
|
| 117 |
+
DiagramType.STATE: ("generate_state", StateDiagramGenerator()),
|
| 118 |
+
DiagramType.TIMING: ("generate_timing", TimingDiagramGenerator()),
|
| 119 |
+
DiagramType.OBJECT: ("generate_object", ObjectDiagramGenerator())
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
selected_types = [DiagramType(d) for d in request.selected_diagrams]
|
| 124 |
+
added_analysis_steps = set()
|
| 125 |
+
steps = []
|
| 126 |
+
|
| 127 |
+
for diagram_type in selected_types:
|
| 128 |
+
required_analysis = diagram_requirements.get(diagram_type, [])
|
| 129 |
+
for step in required_analysis:
|
| 130 |
+
if step not in added_analysis_steps:
|
| 131 |
+
steps.append((step, analysis_functions[step]))
|
| 132 |
+
added_analysis_steps.add(step)
|
| 133 |
+
|
| 134 |
+
if diagram_type in generation_functions:
|
| 135 |
+
steps.append(generation_functions[diagram_type])
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
async def event_stream():
|
| 139 |
+
for step_name, step_func in steps:
|
| 140 |
+
# Determine if it's an analysis step or generation step
|
| 141 |
+
step_type = "analysis" if step_name in analysis_functions else "generation"
|
| 142 |
+
# Determine file extension based on step type
|
| 143 |
+
file_ext = "pu" if step_type == "generation" else "md"
|
| 144 |
+
yield f'data: {{"type": "step", "step_type": "{step_type}", "file_name": "{step_name}.{file_ext}"}}\n\n'
|
| 145 |
+
async for chunk in step_func(initial_state):
|
| 146 |
+
yield f"data: {chunk}\n\n"
|
| 147 |
+
|
| 148 |
+
return StreamingResponse(event_stream(), media_type="text/event-stream")
|
| 149 |
+
|
| 150 |
+
except Exception as e:
|
| 151 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 152 |
+
|
| 153 |
+
|
models.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import TypedDict, List
|
| 2 |
+
from enum import Enum
|
| 3 |
+
|
| 4 |
+
class DiagramType(Enum):
|
| 5 |
+
CLASS = 1
|
| 6 |
+
USE_CASE = 2
|
| 7 |
+
SEQUENCE = 3
|
| 8 |
+
OBJECT = 4
|
| 9 |
+
ACTIVITY = 5
|
| 10 |
+
COMPONENT = 6
|
| 11 |
+
DEPLOYMENT = 7
|
| 12 |
+
STATE = 8
|
| 13 |
+
TIMING = 9
|
| 14 |
+
|
| 15 |
+
class AgentState(TypedDict):
|
| 16 |
+
project_name: str
|
| 17 |
+
project_description: str
|
| 18 |
+
output_dir: str
|
| 19 |
+
selected_diagrams: List[int]
|
| 20 |
+
entities_classes: str
|
| 21 |
+
actors_use_cases: str
|
| 22 |
+
sequence_interactions: str
|
| 23 |
+
class_diagram: str
|
| 24 |
+
use_case_diagram: str
|
| 25 |
+
sequence_diagram: str
|
| 26 |
+
activity_diagram: str
|
| 27 |
+
component_diagram: str
|
| 28 |
+
deployment_diagram: str
|
| 29 |
+
state_diagram: str
|
| 30 |
+
timing_diagram: str
|
| 31 |
+
object_diagram: str
|
requirements.txt
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
langgraph
|
| 2 |
+
langchain-community
|
| 3 |
+
langchain-core
|
| 4 |
+
pydantic
|
| 5 |
+
python-dotenv
|
| 6 |
+
langchain-ollama
|
| 7 |
+
fastapi
|
| 8 |
+
uvicorn
|
research_generator.py
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Dict, List, TypedDict
|
| 2 |
+
from langgraph.graph import StateGraph, END
|
| 3 |
+
from base_agent import BaseAgent
|
| 4 |
+
from models import AgentState
|
| 5 |
+
import os
|
| 6 |
+
from datetime import datetime
|
| 7 |
+
import json
|
| 8 |
+
|
| 9 |
+
class ResearchState(TypedDict):
|
| 10 |
+
subject: str
|
| 11 |
+
index: List[str]
|
| 12 |
+
content: Dict[str, str]
|
| 13 |
+
current_step: int
|
| 14 |
+
|
| 15 |
+
class ResearchGenerator(BaseAgent):
|
| 16 |
+
def __init__(self):
|
| 17 |
+
super().__init__()
|
| 18 |
+
self.workflow = self._create_workflow()
|
| 19 |
+
self.output_dir = "research_output"
|
| 20 |
+
os.makedirs(self.output_dir, exist_ok=True)
|
| 21 |
+
self.timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
| 22 |
+
self.research_dir = f"{self.output_dir}/research_{self.timestamp}"
|
| 23 |
+
os.makedirs(self.research_dir, exist_ok=True)
|
| 24 |
+
|
| 25 |
+
def _create_workflow(self):
|
| 26 |
+
# Create the graph
|
| 27 |
+
workflow = StateGraph(ResearchState)
|
| 28 |
+
|
| 29 |
+
# Add nodes
|
| 30 |
+
workflow.add_node("generate_index", self._generate_index)
|
| 31 |
+
workflow.add_node("generate_content", self._generate_content)
|
| 32 |
+
workflow.add_node("check_completion", self._check_completion)
|
| 33 |
+
|
| 34 |
+
# Add edges
|
| 35 |
+
workflow.add_edge("generate_index", "generate_content")
|
| 36 |
+
workflow.add_edge("generate_content", "check_completion")
|
| 37 |
+
workflow.add_conditional_edges(
|
| 38 |
+
"check_completion",
|
| 39 |
+
self._should_continue,
|
| 40 |
+
{
|
| 41 |
+
True: "generate_content",
|
| 42 |
+
False: END
|
| 43 |
+
}
|
| 44 |
+
)
|
| 45 |
+
|
| 46 |
+
# Set entry point
|
| 47 |
+
workflow.set_entry_point("generate_index")
|
| 48 |
+
|
| 49 |
+
return workflow.compile()
|
| 50 |
+
|
| 51 |
+
def _save_step(self, state: ResearchState, step_name: str, content: str):
|
| 52 |
+
"""Save individual step content to a file"""
|
| 53 |
+
filename = f"{self.research_dir}/{step_name}.md"
|
| 54 |
+
with open(filename, 'w', encoding='utf-8') as f:
|
| 55 |
+
f.write(content)
|
| 56 |
+
print(f"Saved {step_name} to: {filename}")
|
| 57 |
+
|
| 58 |
+
def _save_final_markdown(self, state: ResearchState):
|
| 59 |
+
"""Save the complete research in a single markdown file"""
|
| 60 |
+
filename = f"{self.research_dir}/complete_research.md"
|
| 61 |
+
|
| 62 |
+
with open(filename, 'w', encoding='utf-8') as f:
|
| 63 |
+
# Write title
|
| 64 |
+
f.write(f"# {state['subject']}\n\n")
|
| 65 |
+
|
| 66 |
+
# Write index
|
| 67 |
+
f.write("## الفهرس\n\n")
|
| 68 |
+
for item in state['index']:
|
| 69 |
+
f.write(f"{item}\n")
|
| 70 |
+
f.write("\n")
|
| 71 |
+
|
| 72 |
+
# Write content
|
| 73 |
+
f.write("## المحتوى\n\n")
|
| 74 |
+
for topic, content in state['content'].items():
|
| 75 |
+
f.write(f"### {topic}\n\n")
|
| 76 |
+
f.write(f"{content}\n\n")
|
| 77 |
+
f.write("---\n\n")
|
| 78 |
+
|
| 79 |
+
return filename
|
| 80 |
+
|
| 81 |
+
async def _generate_index(self, state: ResearchState) -> Dict:
|
| 82 |
+
prompt_template = """
|
| 83 |
+
Create a detailed research index for the following subject:
|
| 84 |
+
|
| 85 |
+
Subject: {subject}
|
| 86 |
+
|
| 87 |
+
Instructions:
|
| 88 |
+
1. Create a comprehensive list of topics and subtopics
|
| 89 |
+
2. Organize them in a logical order
|
| 90 |
+
3. Include main sections and subsections
|
| 91 |
+
4. Format as a numbered list
|
| 92 |
+
|
| 93 |
+
Example format:
|
| 94 |
+
1. Main Topic 1
|
| 95 |
+
1.1 Subtopic 1.1
|
| 96 |
+
1.2 Subtopic 1.2
|
| 97 |
+
2. Main Topic 2
|
| 98 |
+
2.1 Subtopic 2.1
|
| 99 |
+
2.2 Subtopic 2.2
|
| 100 |
+
"""
|
| 101 |
+
|
| 102 |
+
result = await self._process(
|
| 103 |
+
state=state,
|
| 104 |
+
prompt_template=prompt_template,
|
| 105 |
+
output_key="index",
|
| 106 |
+
step_name="research index",
|
| 107 |
+
subject=state["subject"]
|
| 108 |
+
)
|
| 109 |
+
|
| 110 |
+
# Save index immediately
|
| 111 |
+
index_content = f"# {state['subject']}\n\n## الفهرس\n\n{result['index']}"
|
| 112 |
+
self._save_step(state, "index", index_content)
|
| 113 |
+
|
| 114 |
+
return {"index": result["index"].split("\n"), "current_step": 0}
|
| 115 |
+
|
| 116 |
+
async def _generate_content(self, state: ResearchState) -> Dict:
|
| 117 |
+
current_topic = state["index"][state["current_step"]]
|
| 118 |
+
|
| 119 |
+
prompt_template = """
|
| 120 |
+
Generate detailed content for the following research topic:
|
| 121 |
+
|
| 122 |
+
Subject: {subject}
|
| 123 |
+
Topic: {current_topic}
|
| 124 |
+
|
| 125 |
+
Instructions:
|
| 126 |
+
1. Provide comprehensive information about the topic
|
| 127 |
+
2. Include relevant examples and explanations
|
| 128 |
+
3. Use clear and academic language
|
| 129 |
+
4. Structure the content with proper paragraphs
|
| 130 |
+
5. Include key points and supporting details
|
| 131 |
+
"""
|
| 132 |
+
|
| 133 |
+
result = await self._process(
|
| 134 |
+
state=state,
|
| 135 |
+
prompt_template=prompt_template,
|
| 136 |
+
output_key="content",
|
| 137 |
+
step_name=f"content for {current_topic}",
|
| 138 |
+
subject=state["subject"],
|
| 139 |
+
current_topic=current_topic
|
| 140 |
+
)
|
| 141 |
+
|
| 142 |
+
# Update content dictionary
|
| 143 |
+
content = state.get("content", {})
|
| 144 |
+
content[current_topic] = result["content"]
|
| 145 |
+
|
| 146 |
+
# Save this topic's content immediately
|
| 147 |
+
topic_content = f"### {current_topic}\n\n{result['content']}"
|
| 148 |
+
self._save_step(state, f"topic_{state['current_step']}", topic_content)
|
| 149 |
+
|
| 150 |
+
return {
|
| 151 |
+
"content": content,
|
| 152 |
+
"current_step": state["current_step"] + 1
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
def _check_completion(self, state: ResearchState) -> Dict:
|
| 156 |
+
return state
|
| 157 |
+
|
| 158 |
+
def _should_continue(self, state: ResearchState) -> bool:
|
| 159 |
+
return state["current_step"] < len(state["index"])
|
| 160 |
+
|
| 161 |
+
async def generate_research(self, subject: str) -> Dict:
|
| 162 |
+
initial_state = {
|
| 163 |
+
"subject": subject,
|
| 164 |
+
"index": [],
|
| 165 |
+
"content": {},
|
| 166 |
+
"current_step": 0
|
| 167 |
+
}
|
| 168 |
+
|
| 169 |
+
result = await self.workflow.ainvoke(initial_state)
|
| 170 |
+
|
| 171 |
+
# Save final complete research
|
| 172 |
+
filename = self._save_final_markdown(result)
|
| 173 |
+
print(f"\nComplete research saved to: {filename}")
|
| 174 |
+
|
| 175 |
+
return result
|
| 176 |
+
|
| 177 |
+
async def astream(self, initial_state: ResearchState):
|
| 178 |
+
"""Stream the workflow execution state."""
|
| 179 |
+
async for state in self.workflow.astream(initial_state):
|
| 180 |
+
yield state
|
| 181 |
+
|
| 182 |
+
# Example usage
|
| 183 |
+
if __name__ == "__main__":
|
| 184 |
+
import asyncio
|
| 185 |
+
|
| 186 |
+
async def main():
|
| 187 |
+
generator = ResearchGenerator()
|
| 188 |
+
research = await generator.generate_research("الذكاء الاصطناعي والاستثمار")
|
| 189 |
+
|
| 190 |
+
# Print summary
|
| 191 |
+
print("\n=== البحث المولد ===\n")
|
| 192 |
+
print("\n=== الفهرس ===")
|
| 193 |
+
for item in research["index"]:
|
| 194 |
+
print(item.strip())
|
| 195 |
+
|
| 196 |
+
print("\n=== المحتوى ===")
|
| 197 |
+
for topic, content in research["content"].items():
|
| 198 |
+
print(f"\n{topic.strip()}:")
|
| 199 |
+
print(content.strip())
|
| 200 |
+
print("-" * 50)
|
| 201 |
+
|
| 202 |
+
asyncio.run(main())
|