appgenerator-cli 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. appgenerator_cli-1.0.0.dist-info/METADATA +213 -0
  2. appgenerator_cli-1.0.0.dist-info/RECORD +48 -0
  3. appgenerator_cli-1.0.0.dist-info/WHEEL +4 -0
  4. appgenerator_cli-1.0.0.dist-info/entry_points.txt +4 -0
  5. pyforge/__init__.py +10 -0
  6. pyforge/commands/__init__.py +1 -0
  7. pyforge/commands/create.py +60 -0
  8. pyforge/generator.py +248 -0
  9. pyforge/main.py +32 -0
  10. pyforge/templates/ai/.env.example +29 -0
  11. pyforge/templates/ai/.gitignore +47 -0
  12. pyforge/templates/ai/Dockerfile +22 -0
  13. pyforge/templates/ai/README.md +97 -0
  14. pyforge/templates/ai/app/__init__.py +1 -0
  15. pyforge/templates/ai/app/agents/__init__.py +1 -0
  16. pyforge/templates/ai/app/agents/assistant.py +100 -0
  17. pyforge/templates/ai/app/chains/__init__.py +1 -0
  18. pyforge/templates/ai/app/chains/rag.py +50 -0
  19. pyforge/templates/ai/app/config.py +47 -0
  20. pyforge/templates/ai/app/tools/__init__.py +1 -0
  21. pyforge/templates/ai/app/tools/registry.py +19 -0
  22. pyforge/templates/ai/app/tools/search.py +34 -0
  23. pyforge/templates/ai/docker-compose.yml +39 -0
  24. pyforge/templates/ai/main.py +40 -0
  25. pyforge/templates/ai/pyproject.toml +28 -0
  26. pyforge/templates/ai/tests/__init__.py +1 -0
  27. pyforge/templates/ai/tests/conftest.py +21 -0
  28. pyforge/templates/ai/tests/test_agent.py +53 -0
  29. pyforge/templates/fastapi/.env.example +17 -0
  30. pyforge/templates/fastapi/.gitignore +42 -0
  31. pyforge/templates/fastapi/Dockerfile +27 -0
  32. pyforge/templates/fastapi/README.md +68 -0
  33. pyforge/templates/fastapi/app/__init__.py +1 -0
  34. pyforge/templates/fastapi/app/api/__init__.py +1 -0
  35. pyforge/templates/fastapi/app/api/v1/__init__.py +1 -0
  36. pyforge/templates/fastapi/app/api/v1/health.py +25 -0
  37. pyforge/templates/fastapi/app/config.py +45 -0
  38. pyforge/templates/fastapi/app/db/__init__.py +1 -0
  39. pyforge/templates/fastapi/app/db/session.py +35 -0
  40. pyforge/templates/fastapi/app/dependencies.py +12 -0
  41. pyforge/templates/fastapi/app/main.py +58 -0
  42. pyforge/templates/fastapi/app/models/__init__.py +4 -0
  43. pyforge/templates/fastapi/app/models/base.py +23 -0
  44. pyforge/templates/fastapi/docker-compose.yml +39 -0
  45. pyforge/templates/fastapi/pyproject.toml +29 -0
  46. pyforge/templates/fastapi/tests/__init__.py +1 -0
  47. pyforge/templates/fastapi/tests/conftest.py +46 -0
  48. pyforge/templates/fastapi/tests/test_health.py +13 -0
@@ -0,0 +1,47 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.so
5
+ *.egg
6
+ *.egg-info/
7
+ dist/
8
+ build/
9
+ .eggs/
10
+
11
+ # Virtual environments
12
+ .venv/
13
+ venv/
14
+ env/
15
+
16
+ # uv
17
+ .uv/
18
+
19
+ # Environment
20
+ .env
21
+ !.env.example
22
+
23
+ # IDE
24
+ .vscode/
25
+ .idea/
26
+ *.swp
27
+
28
+ # Testing
29
+ .pytest_cache/
30
+ .coverage
31
+ htmlcov/
32
+
33
+ # Type checking
34
+ .mypy_cache/
35
+
36
+ # Vector DB / embeddings
37
+ chroma_db/
38
+ *.faiss
39
+ *.pkl
40
+
41
+ # LangSmith / LangChain cache
42
+ .langchain.db
43
+ langchain_cache/
44
+
45
+ # Logs
46
+ *.log
47
+ logs/
@@ -0,0 +1,22 @@
1
+ # ── Build stage ────────────────────────────────────────────────────────────────
2
+ FROM python:3.12-slim AS builder
3
+
4
+ WORKDIR /app
5
+
6
+ COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
7
+
8
+ COPY pyproject.toml uv.lock* ./
9
+ RUN uv sync --frozen --no-dev
10
+
11
+ # ── Runtime stage ──────────────────────────────────────────────────────────────
12
+ FROM python:3.12-slim AS runtime
13
+
14
+ WORKDIR /app
15
+
16
+ COPY --from=builder /app/.venv /app/.venv
17
+ ENV PATH="/app/.venv/bin:$PATH"
18
+
19
+ COPY app ./app
20
+ COPY main.py ./main.py
21
+
22
+ CMD ["python", "main.py"]
@@ -0,0 +1,97 @@
1
+ # {{ project_name }}
2
+
3
+ A production-ready LangChain / LangGraph AI application, scaffolded by [AppGenerator](https://github.com/yourname/appgenerator-cli).
4
+
5
+ ## Tech Stack
6
+
7
+ - **[LangChain](https://python.langchain.com/)** — LLM orchestration framework
8
+ - **[LangGraph](https://langchain-ai.github.io/langgraph/)** — stateful agent graphs
9
+ - **[LangChain-OpenAI](https://python.langchain.com/docs/integrations/chat/openai)** — OpenAI chat models
10
+ - **[LangChain-Ollama](https://python.langchain.com/docs/integrations/chat/ollama)** — local Ollama models
11
+ - **[ChromaDB](https://docs.trychroma.com/)** — local vector store for RAG
12
+ - **[Pydantic Settings](https://docs.pydantic.dev/latest/concepts/pydantic_settings/)** — typed config from env
13
+ - **[uv](https://docs.astral.sh/uv/)** — blazing-fast package management
14
+ {% if postgres %}- **pgvector** — PostgreSQL vector extension
15
+ {% endif %}{% if redis %}- **Redis** — semantic caching layer
16
+ {% endif %}{% if docker %}- **Docker** — containerisation
17
+ {% endif %}
18
+
19
+ ## Project Structure
20
+
21
+ ```
22
+ {{ project_name }}/
23
+ ├── main.py # Interactive REPL entry point
24
+ ├── app/
25
+ │ ├── config.py # Typed settings (pydantic-settings)
26
+ │ ├── agents/
27
+ │ │ ├── __init__.py
28
+ │ │ └── assistant.py # LangGraph ReAct agent
29
+ │ ├── chains/
30
+ │ │ ├── __init__.py
31
+ │ │ └── rag.py # RAG chain example
32
+ │ ├── tools/
33
+ │ │ ├── __init__.py
34
+ │ │ ├── registry.py # Tool registry (add tools here)
35
+ │ │ └── search.py # Web search tool stub
36
+ ├── tests/
37
+ │ ├── conftest.py
38
+ │ ├── test_agent.py
39
+ │ └── __init__.py
40
+ ├── .env.example
41
+ ├── .gitignore
42
+ └── pyproject.toml
43
+ ```
44
+
45
+ ## Getting Started
46
+
47
+ ```bash
48
+ # 1. Copy and configure your secrets
49
+ cp .env.example .env
50
+ # Edit .env — add your OPENAI_API_KEY
51
+
52
+ # 2. Run the interactive assistant
53
+ uv run python main.py
54
+ ```
55
+
56
+ ## Using Ollama (Local Models)
57
+
58
+ ```bash
59
+ # Install Ollama: https://ollama.com
60
+ ollama pull llama3.2
61
+
62
+ # Update .env
63
+ OLLAMA_MODEL=llama3.2
64
+ ```
65
+
66
+ Then swap the LLM in `app/agents/assistant.py`:
67
+
68
+ ```python
69
+ from langchain_ollama import ChatOllama
70
+ self._llm = ChatOllama(model=settings.ollama_model, base_url=settings.ollama_base_url)
71
+ ```
72
+
73
+ ## Adding Tools
74
+
75
+ Edit `app/tools/registry.py` and add your tools to `get_tools()`:
76
+
77
+ ```python
78
+ from app.tools.my_tool import my_custom_tool
79
+
80
+ def get_tools():
81
+ return [web_search_tool, my_custom_tool]
82
+ ```
83
+
84
+ ## Running Tests
85
+
86
+ ```bash
87
+ uv run pytest
88
+ ```
89
+
90
+ ## LangSmith Tracing (optional)
91
+
92
+ ```bash
93
+ # .env
94
+ LANGCHAIN_TRACING_V2=true
95
+ LANGCHAIN_API_KEY=ls__your_key
96
+ LANGCHAIN_PROJECT={{ project_name }}
97
+ ```
@@ -0,0 +1 @@
1
+ """{{ project_name }} AI application package."""
@@ -0,0 +1 @@
1
+ """Agent definitions."""
@@ -0,0 +1,100 @@
1
+ """
2
+ AssistantAgent — a simple LangGraph ReAct agent wired to the configured LLM.
3
+
4
+ Extend this class to add memory, tools, and custom graph nodes.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ from typing import Any
9
+
10
+ from langchain_core.messages import HumanMessage, SystemMessage
11
+ from langchain_openai import ChatOpenAI
12
+ from langgraph.graph import END, START, MessagesState, StateGraph
13
+
14
+ from app.config import settings
15
+ from app.tools.registry import get_tools
16
+
17
+
18
+ class AssistantAgent:
19
+ """A LangGraph-powered conversational agent."""
20
+
21
+ SYSTEM_PROMPT = (
22
+ "You are a helpful, concise, and accurate assistant. "
23
+ "Use the available tools when appropriate. "
24
+ "If you don't know something, say so honestly."
25
+ )
26
+
27
+ def __init__(self) -> None:
28
+ self._llm = ChatOpenAI(
29
+ model=settings.openai_model,
30
+ api_key=settings.openai_api_key,
31
+ temperature=0.2,
32
+ )
33
+ self._tools = get_tools()
34
+ self._llm_with_tools = self._llm.bind_tools(self._tools)
35
+ self._graph = self._build_graph()
36
+
37
+ # ------------------------------------------------------------------ #
38
+ # Public
39
+ # ------------------------------------------------------------------ #
40
+
41
+ async def invoke(self, user_message: str, history: list[dict[str, str]] | None = None) -> str:
42
+ """Run the agent and return the assistant's reply."""
43
+ messages = [SystemMessage(content=self.SYSTEM_PROMPT)]
44
+
45
+ for msg in (history or []):
46
+ if msg["role"] == "user":
47
+ messages.append(HumanMessage(content=msg["content"]))
48
+ # assistant messages are added by LangGraph internally
49
+
50
+ messages.append(HumanMessage(content=user_message))
51
+
52
+ result = await self._graph.ainvoke({"messages": messages})
53
+ return result["messages"][-1].content # type: ignore[index]
54
+
55
+ # ------------------------------------------------------------------ #
56
+ # Graph construction
57
+ # ------------------------------------------------------------------ #
58
+
59
+ def _build_graph(self) -> Any:
60
+ """Build a minimal ReAct graph: call model → maybe call tools → end."""
61
+ builder = StateGraph(MessagesState)
62
+
63
+ builder.add_node("agent", self._call_model)
64
+ builder.add_node("tools", self._call_tools)
65
+
66
+ builder.add_edge(START, "agent")
67
+ builder.add_conditional_edges("agent", self._should_use_tools)
68
+ builder.add_edge("tools", "agent")
69
+
70
+ return builder.compile()
71
+
72
+ async def _call_model(self, state: MessagesState) -> dict[str, Any]:
73
+ response = await self._llm_with_tools.ainvoke(state["messages"])
74
+ return {"messages": [response]}
75
+
76
+ async def _call_tools(self, state: MessagesState) -> dict[str, Any]:
77
+ from langchain_core.messages import ToolMessage
78
+
79
+ last_message = state["messages"][-1]
80
+ tool_map = {t.name: t for t in self._tools}
81
+ results = []
82
+
83
+ for tool_call in last_message.tool_calls: # type: ignore[attr-defined]
84
+ tool = tool_map.get(tool_call["name"])
85
+ if tool is None:
86
+ output = f"Error: unknown tool '{tool_call['name']}'"
87
+ else:
88
+ output = await tool.ainvoke(tool_call["args"])
89
+ results.append(
90
+ ToolMessage(content=str(output), tool_call_id=tool_call["id"])
91
+ )
92
+
93
+ return {"messages": results}
94
+
95
+ @staticmethod
96
+ def _should_use_tools(state: MessagesState) -> str:
97
+ last = state["messages"][-1]
98
+ if hasattr(last, "tool_calls") and last.tool_calls: # type: ignore[attr-defined]
99
+ return "tools"
100
+ return END
@@ -0,0 +1 @@
1
+ """Reusable LangChain chain definitions."""
@@ -0,0 +1,50 @@
1
+ """
2
+ Retrieval-Augmented Generation (RAG) chain.
3
+
4
+ Usage:
5
+ from app.chains.rag import build_rag_chain
6
+ chain = build_rag_chain(vectorstore)
7
+ answer = await chain.ainvoke({"question": "What is LangGraph?"})
8
+ """
9
+ from __future__ import annotations
10
+
11
+ from langchain_core.output_parsers import StrOutputParser
12
+ from langchain_core.prompts import ChatPromptTemplate
13
+ from langchain_core.runnables import RunnablePassthrough
14
+ from langchain_core.vectorstores import VectorStore
15
+ from langchain_openai import ChatOpenAI
16
+
17
+ from app.config import settings
18
+
19
+ RAG_PROMPT = ChatPromptTemplate.from_template(
20
+ """You are a helpful assistant. Answer the question using ONLY the provided context.
21
+ If the answer is not in the context, say "I don't know".
22
+
23
+ Context:
24
+ {context}
25
+
26
+ Question: {question}
27
+
28
+ Answer:"""
29
+ )
30
+
31
+
32
+ def build_rag_chain(vectorstore: VectorStore):
33
+ """Build a RAG chain from a pre-loaded vector store."""
34
+ retriever = vectorstore.as_retriever(search_kwargs={"k": 4})
35
+ llm = ChatOpenAI(
36
+ model=settings.openai_model,
37
+ api_key=settings.openai_api_key,
38
+ temperature=0,
39
+ )
40
+
41
+ def format_docs(docs: list) -> str:
42
+ return "\n\n".join(doc.page_content for doc in docs)
43
+
44
+ chain = (
45
+ {"context": retriever | format_docs, "question": RunnablePassthrough()}
46
+ | RAG_PROMPT
47
+ | llm
48
+ | StrOutputParser()
49
+ )
50
+ return chain
@@ -0,0 +1,47 @@
1
+ """
2
+ Typed settings for the AI application, loaded from .env.
3
+ """
4
+ from functools import lru_cache
5
+
6
+ from pydantic_settings import BaseSettings, SettingsConfigDict
7
+
8
+
9
+ class Settings(BaseSettings):
10
+ model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8", extra="ignore")
11
+
12
+ # App
13
+ app_name: str = "{{ project_name }}"
14
+ app_env: str = "development"
15
+ debug: bool = True
16
+
17
+ # LLM — OpenAI
18
+ openai_api_key: str = ""
19
+ openai_model: str = "gpt-4o-mini"
20
+
21
+ # LLM — Ollama (local)
22
+ ollama_base_url: str = "http://localhost:11434"
23
+ ollama_model: str = "llama3.2"
24
+
25
+ # Vector store
26
+ chroma_persist_dir: str = "./chroma_db"
27
+ {% if postgres %}
28
+ # pgvector
29
+ pgvector_url: str = ""
30
+ {% endif %}
31
+ {% if redis %}
32
+ # Redis
33
+ redis_url: str = "redis://localhost:6379/0"
34
+ {% endif %}
35
+
36
+ # LangSmith tracing (set via env vars)
37
+ langchain_tracing_v2: bool = False
38
+ langchain_api_key: str = ""
39
+ langchain_project: str = "{{ project_name }}"
40
+
41
+
42
+ @lru_cache
43
+ def get_settings() -> Settings:
44
+ return Settings()
45
+
46
+
47
+ settings = get_settings()
@@ -0,0 +1 @@
1
+ """Custom LangChain tools."""
@@ -0,0 +1,19 @@
1
+ """
2
+ Tool registry — add your custom tools here and they are automatically
3
+ bound to the AssistantAgent.
4
+ """
5
+ from __future__ import annotations
6
+
7
+ from langchain_core.tools import BaseTool
8
+
9
+ from app.tools.search import web_search_tool
10
+
11
+
12
+ def get_tools() -> list[BaseTool]:
13
+ """Return all tools available to the agent."""
14
+ return [
15
+ web_search_tool,
16
+ # Add more tools here, e.g.:
17
+ # calculator_tool,
18
+ # retriever_tool,
19
+ ]
@@ -0,0 +1,34 @@
1
+ """
2
+ Example tool: simple web search stub.
3
+
4
+ Replace the implementation with a real provider such as:
5
+ - Tavily: `pip install langchain-tavily`
6
+ - SerpAPI: `pip install google-search-results`
7
+ - DuckDuckGo: `pip install duckduckgo-search`
8
+ """
9
+ from langchain_core.tools import tool
10
+
11
+
12
+ @tool
13
+ async def web_search_tool(query: str) -> str:
14
+ """Search the web for up-to-date information on a given query.
15
+
16
+ Args:
17
+ query: The search query string.
18
+
19
+ Returns:
20
+ A string summarising the top search results.
21
+ """
22
+ # ── Stub — replace with a real search provider ──────────────────────
23
+ # Example using Tavily (recommended for LangChain agents):
24
+ #
25
+ # from langchain_community.tools.tavily_search import TavilySearchResults
26
+ # search = TavilySearchResults(max_results=3)
27
+ # results = await search.ainvoke({"query": query})
28
+ # return "\n".join(r["content"] for r in results)
29
+ #
30
+ # ────────────────────────────────────────────────────────────────────
31
+ return (
32
+ f"[web_search stub] No live results for '{query}'. "
33
+ "Configure a real search provider in app/tools/search.py."
34
+ )
@@ -0,0 +1,39 @@
1
+ version: "3.9"
2
+
3
+ services:
4
+ app:
5
+ build: .
6
+ env_file: .env
7
+ volumes:
8
+ - ./chroma_db:/app/chroma_db
9
+ {% if redis or postgres %}depends_on:{% endif %}
10
+ {% if postgres %}- postgres{% endif %}
11
+ {% if redis %}- redis{% endif %}
12
+ restart: unless-stopped
13
+
14
+ {% if postgres %}
15
+ postgres:
16
+ image: pgvector/pgvector:pg16
17
+ environment:
18
+ POSTGRES_DB: {{ package_name }}_vectors
19
+ POSTGRES_USER: postgres
20
+ POSTGRES_PASSWORD: postgres
21
+ ports:
22
+ - "5432:5432"
23
+ volumes:
24
+ - pg_data:/var/lib/postgresql/data
25
+ restart: unless-stopped
26
+ {% endif %}
27
+
28
+ {% if redis %}
29
+ redis:
30
+ image: redis:7-alpine
31
+ ports:
32
+ - "6379:6379"
33
+ restart: unless-stopped
34
+ {% endif %}
35
+
36
+ {% if postgres %}
37
+ volumes:
38
+ pg_data:
39
+ {% endif %}
@@ -0,0 +1,40 @@
1
+ """
2
+ {{ project_name }} — LangChain / LangGraph application entry point.
3
+
4
+ Run:
5
+ uv run python main.py
6
+ """
7
+ import asyncio
8
+
9
+ from app.agents.assistant import AssistantAgent
10
+ from app.config import settings
11
+
12
+
13
+ async def main() -> None:
14
+ print(f"🤖 {settings.app_name} starting up …\n")
15
+
16
+ agent = AssistantAgent()
17
+
18
+ # Interactive REPL
19
+ print("Type your message (Ctrl-C to quit):\n")
20
+ history: list[dict[str, str]] = []
21
+
22
+ while True:
23
+ try:
24
+ user_input = input("You: ").strip()
25
+ except (KeyboardInterrupt, EOFError):
26
+ print("\nGoodbye!")
27
+ break
28
+
29
+ if not user_input:
30
+ continue
31
+
32
+ response = await agent.invoke(user_input, history=history)
33
+ print(f"AI: {response}\n")
34
+
35
+ history.append({"role": "user", "content": user_input})
36
+ history.append({"role": "assistant", "content": response})
37
+
38
+
39
+ if __name__ == "__main__":
40
+ asyncio.run(main())
@@ -0,0 +1,28 @@
1
+ [build-system]
2
+ requires = ["uv_build>=0.11.2,<0.12"]
3
+ build-backend = "uv_build"
4
+
5
+ [project]
6
+ name = "{{ project_name }}"
7
+ version = "0.1.0"
8
+ description = "A LangChain / LangGraph AI application"
9
+ requires-python = ">=3.10"
10
+ dependencies = []
11
+
12
+ [tool.uv.build-backend]
13
+ module-name = "app"
14
+ module-root = ""
15
+
16
+ [tool.ruff]
17
+ line-length = 100
18
+ target-version = "py310"
19
+ select = ["E", "F", "I", "N", "UP"]
20
+
21
+ [tool.mypy]
22
+ python_version = "3.10"
23
+ strict = true
24
+ ignore_missing_imports = true
25
+
26
+ [tool.pytest.ini_options]
27
+ testpaths = ["tests"]
28
+ asyncio_mode = "auto"
@@ -0,0 +1 @@
1
+ """Test suite for {{ project_name }}."""
@@ -0,0 +1,21 @@
1
+ """Shared test fixtures for the AI application."""
2
+ from unittest.mock import AsyncMock, patch
3
+
4
+ import pytest
5
+
6
+
7
+ @pytest.fixture
8
+ def mock_openai():
9
+ """Patch ChatOpenAI so tests never hit the real API."""
10
+ with patch("langchain_openai.ChatOpenAI") as mock_cls:
11
+ instance = mock_cls.return_value
12
+ instance.ainvoke = AsyncMock(return_value=_fake_message("Test response"))
13
+ instance.bind_tools = lambda tools: instance
14
+ yield instance
15
+
16
+
17
+ def _fake_message(content: str):
18
+ from langchain_core.messages import AIMessage
19
+ msg = AIMessage(content=content)
20
+ msg.tool_calls = []
21
+ return msg
@@ -0,0 +1,53 @@
1
+ """Tests for the AssistantAgent."""
2
+ from unittest.mock import AsyncMock, MagicMock, patch
3
+
4
+ import pytest
5
+ from langchain_core.messages import AIMessage
6
+
7
+
8
+ @pytest.mark.asyncio
9
+ async def test_agent_returns_string_response():
10
+ """Agent.invoke() should return a plain string."""
11
+ fake_ai_msg = AIMessage(content="Hello, I am your assistant!")
12
+ fake_ai_msg.tool_calls = [] # no tool calls → goes straight to END
13
+
14
+ with (
15
+ patch("langchain_openai.ChatOpenAI") as mock_llm_cls,
16
+ patch("app.tools.registry.get_tools", return_value=[]),
17
+ ):
18
+ mock_llm = MagicMock()
19
+ mock_llm.bind_tools.return_value = mock_llm
20
+ mock_llm.ainvoke = AsyncMock(return_value=fake_ai_msg)
21
+ mock_llm_cls.return_value = mock_llm
22
+
23
+ from app.agents.assistant import AssistantAgent
24
+
25
+ agent = AssistantAgent()
26
+ result = await agent.invoke("Hello")
27
+
28
+ assert isinstance(result, str)
29
+ assert len(result) > 0
30
+
31
+
32
+ @pytest.mark.asyncio
33
+ async def test_agent_passes_history():
34
+ """Agent should include prior history in the prompt."""
35
+ fake_ai_msg = AIMessage(content="I remember!")
36
+ fake_ai_msg.tool_calls = []
37
+
38
+ with (
39
+ patch("langchain_openai.ChatOpenAI") as mock_llm_cls,
40
+ patch("app.tools.registry.get_tools", return_value=[]),
41
+ ):
42
+ mock_llm = MagicMock()
43
+ mock_llm.bind_tools.return_value = mock_llm
44
+ mock_llm.ainvoke = AsyncMock(return_value=fake_ai_msg)
45
+ mock_llm_cls.return_value = mock_llm
46
+
47
+ from app.agents.assistant import AssistantAgent
48
+
49
+ agent = AssistantAgent()
50
+ history = [{"role": "user", "content": "My name is Alice"}]
51
+ result = await agent.invoke("What is my name?", history=history)
52
+
53
+ assert isinstance(result, str)
@@ -0,0 +1,17 @@
1
+ # ── Application ──────────────────────────────────────────────
2
+ APP_NAME="{{ project_name }}"
3
+ APP_ENV=development # development | staging | production
4
+ DEBUG=true
5
+ SECRET_KEY=change-me-to-a-random-secret
6
+
7
+ # ── Server ────────────────────────────────────────────────────
8
+ HOST=0.0.0.0
9
+ PORT=8000
10
+
11
+ # ── Database ──────────────────────────────────────────────────
12
+ {% if postgres %}DATABASE_URL=postgresql+asyncpg://postgres:postgres@localhost:5432/{{ package_name }}_db
13
+ {% else %}DATABASE_URL=sqlite+aiosqlite:///./{{ package_name }}.db
14
+ {% endif %}
15
+ {% if redis %}# ── Redis ────────────────────────────────────────────────────
16
+ REDIS_URL=redis://localhost:6379/0
17
+ {% endif %}
@@ -0,0 +1,42 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.so
5
+ *.egg
6
+ *.egg-info/
7
+ dist/
8
+ build/
9
+ .eggs/
10
+
11
+ # Virtual environments
12
+ .venv/
13
+ venv/
14
+ env/
15
+
16
+ # uv
17
+ .uv/
18
+
19
+ # Environment
20
+ .env
21
+ !.env.example
22
+
23
+ # IDE
24
+ .vscode/
25
+ .idea/
26
+ *.swp
27
+
28
+ # Testing
29
+ .pytest_cache/
30
+ .coverage
31
+ htmlcov/
32
+
33
+ # Type checking
34
+ .mypy_cache/
35
+
36
+ # DB
37
+ *.db
38
+ *.sqlite3
39
+
40
+ # Logs
41
+ *.log
42
+ logs/