elric-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.
- elric_cli/__init__.py +1 -0
- elric_cli/app.py +24 -0
- elric_cli/commands/.gitkeep +0 -0
- elric_cli/commands/__init__.py +0 -0
- elric_cli/commands/apikey.py +134 -0
- elric_cli/commands/make.py +266 -0
- elric_cli/commands/migrate.py +69 -0
- elric_cli/commands/project.py +72 -0
- elric_cli/commands/route.py +49 -0
- elric_cli/commands/serve.py +33 -0
- elric_cli/install.sh +92 -0
- elric_cli/stubs/.gitkeep +0 -0
- elric_cli/stubs/agent.stub.py +23 -0
- elric_cli/stubs/agent_chat.stub.py +62 -0
- elric_cli/stubs/agent_planner.stub.py +82 -0
- elric_cli/stubs/agent_react.stub.py +83 -0
- elric_cli/stubs/agent_simple.stub.py +32 -0
- elric_cli/stubs/agent_streaming.stub.py +59 -0
- elric_cli/stubs/agent_tool.stub.py +63 -0
- elric_cli/stubs/chain.stub.py +23 -0
- elric_cli/stubs/controller.stub.py +30 -0
- elric_cli/stubs/event.stub.py +18 -0
- elric_cli/stubs/exception.stub.py +21 -0
- elric_cli/stubs/job.stub.py +24 -0
- elric_cli/stubs/listener.stub.py +23 -0
- elric_cli/stubs/middleware.stub.py +32 -0
- elric_cli/stubs/migration.stub.py +26 -0
- elric_cli/stubs/model.stub.py +18 -0
- elric_cli/stubs/route.stub.py +33 -0
- elric_cli/stubs/schema.stub.py +32 -0
- elric_cli/stubs/test.stub.py +21 -0
- elric_cli/stubs/tool.stub.py +24 -0
- elric_cli/uninstall.sh +64 -0
- elric_cli/utils.py +181 -0
- elric_cli-1.0.0.dist-info/METADATA +98 -0
- elric_cli-1.0.0.dist-info/RECORD +39 -0
- elric_cli-1.0.0.dist-info/WHEEL +4 -0
- elric_cli-1.0.0.dist-info/entry_points.txt +2 -0
- elric_cli-1.0.0.dist-info/licenses/LICENSE +21 -0
elric_cli/install.sh
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Elric Framework - Installation Script
|
|
4
|
+
# This script installs the 'elric' command globally on your system
|
|
5
|
+
|
|
6
|
+
set -e
|
|
7
|
+
|
|
8
|
+
# Colors for output
|
|
9
|
+
RED='\033[0;31m'
|
|
10
|
+
GREEN='\033[0;32m'
|
|
11
|
+
YELLOW='\033[1;33m'
|
|
12
|
+
BLUE='\033[0;34m'
|
|
13
|
+
NC='\033[0m' # No Color
|
|
14
|
+
|
|
15
|
+
# Get the absolute path of the CLI repository root
|
|
16
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
17
|
+
PROJECT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
|
18
|
+
|
|
19
|
+
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
20
|
+
echo -e "${BLUE} Elric Framework - Installation${NC}"
|
|
21
|
+
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
22
|
+
echo ""
|
|
23
|
+
|
|
24
|
+
# Check if uv is installed
|
|
25
|
+
if ! command -v uv &> /dev/null; then
|
|
26
|
+
echo -e "${RED}✗ Error: 'uv' is not installed${NC}"
|
|
27
|
+
echo -e "${YELLOW} Please install uv first: https://github.com/astral-sh/uv${NC}"
|
|
28
|
+
echo -e "${YELLOW} Quick install: curl -LsSf https://astral.sh/uv/install.sh | sh${NC}"
|
|
29
|
+
exit 1
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
echo -e "${GREEN}✓ Found uv package manager${NC}"
|
|
33
|
+
|
|
34
|
+
# Determine installation directory
|
|
35
|
+
if [ -w "/usr/local/bin" ]; then
|
|
36
|
+
INSTALL_DIR="/usr/local/bin"
|
|
37
|
+
elif [ -w "$HOME/.local/bin" ]; then
|
|
38
|
+
INSTALL_DIR="$HOME/.local/bin"
|
|
39
|
+
mkdir -p "$INSTALL_DIR"
|
|
40
|
+
else
|
|
41
|
+
INSTALL_DIR="$HOME/.local/bin"
|
|
42
|
+
mkdir -p "$INSTALL_DIR"
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
echo -e "${BLUE}→ Installation directory: ${INSTALL_DIR}${NC}"
|
|
46
|
+
|
|
47
|
+
# Create wrapper script
|
|
48
|
+
WRAPPER_SCRIPT="${INSTALL_DIR}/elric"
|
|
49
|
+
|
|
50
|
+
echo -e "${BLUE}→ Creating wrapper script...${NC}"
|
|
51
|
+
|
|
52
|
+
cat > "$WRAPPER_SCRIPT" << EOF
|
|
53
|
+
#!/bin/bash
|
|
54
|
+
# Elric Framework CLI Wrapper
|
|
55
|
+
# Auto-generated by install.sh
|
|
56
|
+
|
|
57
|
+
PROJECT_DIR="${PROJECT_DIR}"
|
|
58
|
+
|
|
59
|
+
# Run elric from the CLI project while preserving current working directory
|
|
60
|
+
uv run --project "\${PROJECT_DIR}" elric "\$@"
|
|
61
|
+
EOF
|
|
62
|
+
|
|
63
|
+
# Make wrapper executable
|
|
64
|
+
chmod +x "$WRAPPER_SCRIPT"
|
|
65
|
+
|
|
66
|
+
echo -e "${GREEN}✓ Wrapper script created${NC}"
|
|
67
|
+
|
|
68
|
+
# Check if installation directory is in PATH
|
|
69
|
+
if [[ ":$PATH:" != *":${INSTALL_DIR}:"* ]]; then
|
|
70
|
+
echo ""
|
|
71
|
+
echo -e "${YELLOW}⚠ Warning: ${INSTALL_DIR} is not in your PATH${NC}"
|
|
72
|
+
echo -e "${YELLOW} Add this line to your ~/.zshrc or ~/.bashrc:${NC}"
|
|
73
|
+
echo -e "${BLUE} export PATH=\"${INSTALL_DIR}:\$PATH\"${NC}"
|
|
74
|
+
echo ""
|
|
75
|
+
echo -e "${YELLOW} Then reload your shell:${NC}"
|
|
76
|
+
echo -e "${BLUE} source ~/.zshrc # or source ~/.bashrc${NC}"
|
|
77
|
+
echo ""
|
|
78
|
+
fi
|
|
79
|
+
|
|
80
|
+
echo ""
|
|
81
|
+
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
82
|
+
echo -e "${GREEN} ✓ Installation completed successfully!${NC}"
|
|
83
|
+
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
84
|
+
echo ""
|
|
85
|
+
echo -e "${BLUE} You can now use the 'elric' command from anywhere:${NC}"
|
|
86
|
+
echo -e "${GREEN} elric --help${NC}"
|
|
87
|
+
echo -e "${GREEN} elric make agent MyAgent${NC}"
|
|
88
|
+
echo -e "${GREEN} elric serve${NC}"
|
|
89
|
+
echo ""
|
|
90
|
+
echo -e "${BLUE} Project directory: ${PROJECT_DIR}${NC}"
|
|
91
|
+
echo -e "${BLUE} Wrapper installed: ${WRAPPER_SCRIPT}${NC}"
|
|
92
|
+
echo ""
|
elric_cli/stubs/.gitkeep
ADDED
|
File without changes
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from app.ai.agents.base_agent import BaseAgent
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class {{ class_name }}(BaseAgent):
|
|
7
|
+
"""{{ class_name }} agent implementation."""
|
|
8
|
+
|
|
9
|
+
def __init__(self):
|
|
10
|
+
super().__init__(name="{{ snake_name }}")
|
|
11
|
+
|
|
12
|
+
async def run(self, input_data: dict[str, Any]) -> dict[str, Any]:
|
|
13
|
+
"""
|
|
14
|
+
Execute the agent logic.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
input_data: Input data for the agent
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
dict: Agent execution result
|
|
21
|
+
"""
|
|
22
|
+
# TODO: Implement agent logic here
|
|
23
|
+
return {"status": "success", "message": "{{ class_name }} executed"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage
|
|
4
|
+
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
|
|
5
|
+
|
|
6
|
+
from app.ai.agents.base_agent import BaseAgent
|
|
7
|
+
from {{ llm_import }}
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class {{ class_name }}(BaseAgent):
|
|
11
|
+
"""{{ class_name }} - Chat agent with conversation memory."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, model: str = "{{ model_name }}", system_prompt: str = "You are a helpful AI assistant."):
|
|
14
|
+
super().__init__(name="{{ snake_name }}")
|
|
15
|
+
self.llm = {{ llm_class }}(model=model)
|
|
16
|
+
self.system_prompt = system_prompt
|
|
17
|
+
self.chat_history: list = []
|
|
18
|
+
|
|
19
|
+
self.prompt = ChatPromptTemplate.from_messages([
|
|
20
|
+
("system", "{system_prompt}"),
|
|
21
|
+
MessagesPlaceholder(variable_name="chat_history"),
|
|
22
|
+
("human", "{input}")
|
|
23
|
+
])
|
|
24
|
+
|
|
25
|
+
self.chain = self.prompt | self.llm
|
|
26
|
+
|
|
27
|
+
async def run(self, input_data: dict[str, Any]) -> dict[str, Any]:
|
|
28
|
+
"""
|
|
29
|
+
Execute the chat agent logic with conversation memory.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
input_data: Input data containing 'message' and optional 'clear_history'
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
dict: Agent execution result with response and chat history
|
|
36
|
+
"""
|
|
37
|
+
message = input_data.get("message", "")
|
|
38
|
+
clear_history = input_data.get("clear_history", False)
|
|
39
|
+
|
|
40
|
+
if clear_history:
|
|
41
|
+
self.chat_history = []
|
|
42
|
+
|
|
43
|
+
response = await self.chain.ainvoke({
|
|
44
|
+
"system_prompt": self.system_prompt,
|
|
45
|
+
"chat_history": self.chat_history,
|
|
46
|
+
"input": message
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
# Update chat history
|
|
50
|
+
self.chat_history.append(HumanMessage(content=message))
|
|
51
|
+
self.chat_history.append(AIMessage(content=response.content))
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
"status": "success",
|
|
55
|
+
"response": response.content,
|
|
56
|
+
"model": self.llm.model_name,
|
|
57
|
+
"history_length": len(self.chat_history)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
def clear_history(self):
|
|
61
|
+
"""Clear the conversation history."""
|
|
62
|
+
self.chat_history = []
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from langchain.chains import LLMChain
|
|
4
|
+
from langchain_core.prompts import ChatPromptTemplate
|
|
5
|
+
|
|
6
|
+
from app.ai.agents.base_agent import BaseAgent
|
|
7
|
+
from {{ llm_import }}
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class {{ class_name }}(BaseAgent):
|
|
11
|
+
"""{{ class_name }} - Planner agent for complex task decomposition."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, model: str = "{{ model_name }}"):
|
|
14
|
+
super().__init__(name="{{ snake_name }}")
|
|
15
|
+
self.llm = {{ llm_class }}(model=model, temperature=0.7)
|
|
16
|
+
|
|
17
|
+
# Planning prompt
|
|
18
|
+
self.planning_prompt = ChatPromptTemplate.from_messages([
|
|
19
|
+
("system", """You are an expert task planner. Break down complex tasks into clear, actionable steps.
|
|
20
|
+
For each step, provide:
|
|
21
|
+
1. Step number
|
|
22
|
+
2. Action to take
|
|
23
|
+
3. Expected outcome
|
|
24
|
+
4. Dependencies (if any)
|
|
25
|
+
|
|
26
|
+
Format your response as a structured plan."""),
|
|
27
|
+
("human", "Task: {task}\n\nCreate a detailed plan to accomplish this task.")
|
|
28
|
+
])
|
|
29
|
+
|
|
30
|
+
# Execution prompt
|
|
31
|
+
self.execution_prompt = ChatPromptTemplate.from_messages([
|
|
32
|
+
("system", "You are executing step {step_number} of a plan: {step_description}"),
|
|
33
|
+
("human", "{input}")
|
|
34
|
+
])
|
|
35
|
+
|
|
36
|
+
self.planning_chain = self.planning_prompt | self.llm
|
|
37
|
+
self.execution_chain = self.execution_prompt | self.llm
|
|
38
|
+
|
|
39
|
+
async def run(self, input_data: dict[str, Any]) -> dict[str, Any]:
|
|
40
|
+
"""
|
|
41
|
+
Execute the planner agent logic.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
input_data: Input data containing 'task' and optional 'execute_plan'
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
dict: Agent execution result with plan and optional execution results
|
|
48
|
+
"""
|
|
49
|
+
task = input_data.get("task", "")
|
|
50
|
+
execute_plan = input_data.get("execute_plan", False)
|
|
51
|
+
|
|
52
|
+
# Generate plan
|
|
53
|
+
plan_response = await self.planning_chain.ainvoke({"task": task})
|
|
54
|
+
plan = plan_response.content
|
|
55
|
+
|
|
56
|
+
result = {
|
|
57
|
+
"status": "success",
|
|
58
|
+
"plan": plan,
|
|
59
|
+
"model": self.llm.model_name,
|
|
60
|
+
"task": task
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
# Optionally execute the plan
|
|
64
|
+
if execute_plan:
|
|
65
|
+
execution_results = []
|
|
66
|
+
# TODO: Parse plan and execute each step
|
|
67
|
+
# This is a placeholder for plan execution logic
|
|
68
|
+
result["execution_results"] = execution_results
|
|
69
|
+
result["executed"] = True
|
|
70
|
+
else:
|
|
71
|
+
result["executed"] = False
|
|
72
|
+
|
|
73
|
+
return result
|
|
74
|
+
|
|
75
|
+
async def execute_step(self, step_number: int, step_description: str, input_data: str) -> str:
|
|
76
|
+
"""Execute a single step of the plan."""
|
|
77
|
+
response = await self.execution_chain.ainvoke({
|
|
78
|
+
"step_number": step_number,
|
|
79
|
+
"step_description": step_description,
|
|
80
|
+
"input": input_data
|
|
81
|
+
})
|
|
82
|
+
return response.content
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from langchain.agents import AgentExecutor, create_react_agent
|
|
4
|
+
from langchain_core.prompts import PromptTemplate
|
|
5
|
+
|
|
6
|
+
from app.ai.agents.base_agent import BaseAgent
|
|
7
|
+
from {{ llm_import }}
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class {{ class_name }}(BaseAgent):
|
|
11
|
+
"""{{ class_name }} - ReAct (Reasoning + Acting) agent implementation."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, model: str = "{{ model_name }}", tools: list = None):
|
|
14
|
+
super().__init__(name="{{ snake_name }}")
|
|
15
|
+
self.llm = {{ llm_class }}(model=model)
|
|
16
|
+
self.tools = tools or []
|
|
17
|
+
|
|
18
|
+
# ReAct prompt template
|
|
19
|
+
self.prompt = PromptTemplate.from_template(
|
|
20
|
+
"""Answer the following questions as best you can. You have access to the following tools:
|
|
21
|
+
|
|
22
|
+
{tools}
|
|
23
|
+
|
|
24
|
+
Use the following format:
|
|
25
|
+
|
|
26
|
+
Question: the input question you must answer
|
|
27
|
+
Thought: you should always think about what to do
|
|
28
|
+
Action: the action to take, should be one of [{tool_names}]
|
|
29
|
+
Action Input: the input to the action
|
|
30
|
+
Observation: the result of the action
|
|
31
|
+
... (this Thought/Action/Action Input/Observation can repeat N times)
|
|
32
|
+
Thought: I now know the final answer
|
|
33
|
+
Final Answer: the final answer to the original input question
|
|
34
|
+
|
|
35
|
+
Begin!
|
|
36
|
+
|
|
37
|
+
Question: {input}
|
|
38
|
+
Thought: {agent_scratchpad}"""
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
if self.tools:
|
|
42
|
+
agent = create_react_agent(self.llm, self.tools, self.prompt)
|
|
43
|
+
self.agent_executor = AgentExecutor(
|
|
44
|
+
agent=agent,
|
|
45
|
+
tools=self.tools,
|
|
46
|
+
verbose=True,
|
|
47
|
+
max_iterations=5,
|
|
48
|
+
handle_parsing_errors=True
|
|
49
|
+
)
|
|
50
|
+
else:
|
|
51
|
+
self.agent_executor = None
|
|
52
|
+
|
|
53
|
+
async def run(self, input_data: dict[str, Any]) -> dict[str, Any]:
|
|
54
|
+
"""
|
|
55
|
+
Execute the ReAct agent logic with reasoning and acting.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
input_data: Input data containing 'input' and optional 'max_iterations'
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
dict: Agent execution result with reasoning steps
|
|
62
|
+
"""
|
|
63
|
+
user_input = input_data.get("input", "")
|
|
64
|
+
max_iterations = input_data.get("max_iterations", 5)
|
|
65
|
+
|
|
66
|
+
if not self.agent_executor:
|
|
67
|
+
return {
|
|
68
|
+
"status": "error",
|
|
69
|
+
"message": "No tools configured for this ReAct agent",
|
|
70
|
+
"model": self.llm.model_name
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
self.agent_executor.max_iterations = max_iterations
|
|
74
|
+
|
|
75
|
+
result = await self.agent_executor.ainvoke({"input": user_input})
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
"status": "success",
|
|
79
|
+
"response": result.get("output", ""),
|
|
80
|
+
"model": self.llm.model_name,
|
|
81
|
+
"tools_available": len(self.tools),
|
|
82
|
+
"reasoning_steps": result.get("intermediate_steps", [])
|
|
83
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from app.ai.agents.base_agent import BaseAgent
|
|
4
|
+
from {{ llm_import }}
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class {{ class_name }}(BaseAgent):
|
|
8
|
+
"""{{ class_name }} - Simple agent implementation."""
|
|
9
|
+
|
|
10
|
+
def __init__(self, model: str = "{{ model_name }}"):
|
|
11
|
+
super().__init__(name="{{ snake_name }}")
|
|
12
|
+
self.llm = {{ llm_class }}(model=model)
|
|
13
|
+
|
|
14
|
+
async def run(self, input_data: dict[str, Any]) -> dict[str, Any]:
|
|
15
|
+
"""
|
|
16
|
+
Execute the agent logic.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
input_data: Input data for the agent
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
dict: Agent execution result
|
|
23
|
+
"""
|
|
24
|
+
# TODO: Implement agent logic here
|
|
25
|
+
prompt = input_data.get("prompt", "")
|
|
26
|
+
response = await self.llm.ainvoke(prompt)
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
"status": "success",
|
|
30
|
+
"response": response.content,
|
|
31
|
+
"model": self.llm.model_name
|
|
32
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from typing import Any, AsyncIterator
|
|
2
|
+
|
|
3
|
+
from langchain_core.prompts import ChatPromptTemplate
|
|
4
|
+
|
|
5
|
+
from app.ai.agents.base_agent import BaseAgent
|
|
6
|
+
from {{ llm_import }}
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class {{ class_name }}(BaseAgent):
|
|
10
|
+
"""{{ class_name }} - Streaming agent for real-time responses."""
|
|
11
|
+
|
|
12
|
+
def __init__(self, model: str = "{{ model_name }}"):
|
|
13
|
+
super().__init__(name="{{ snake_name }}")
|
|
14
|
+
self.llm = {{ llm_class }}(model=model, streaming=True)
|
|
15
|
+
|
|
16
|
+
self.prompt = ChatPromptTemplate.from_messages([
|
|
17
|
+
("system", "You are a helpful AI assistant."),
|
|
18
|
+
("human", "{input}")
|
|
19
|
+
])
|
|
20
|
+
|
|
21
|
+
self.chain = self.prompt | self.llm
|
|
22
|
+
|
|
23
|
+
async def run(self, input_data: dict[str, Any]) -> dict[str, Any]:
|
|
24
|
+
"""
|
|
25
|
+
Execute the agent logic with streaming disabled (use stream() for streaming).
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
input_data: Input data for the agent
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
dict: Agent execution result
|
|
32
|
+
"""
|
|
33
|
+
user_input = input_data.get("input", "")
|
|
34
|
+
|
|
35
|
+
response = await self.chain.ainvoke({"input": user_input})
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
"status": "success",
|
|
39
|
+
"response": response.content,
|
|
40
|
+
"model": self.llm.model_name
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async def stream(self, input_data: dict[str, Any]) -> AsyncIterator[str]:
|
|
44
|
+
"""
|
|
45
|
+
Stream the agent response in real-time.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
input_data: Input data containing 'input'
|
|
49
|
+
|
|
50
|
+
Yields:
|
|
51
|
+
str: Chunks of the response as they are generated
|
|
52
|
+
"""
|
|
53
|
+
user_input = input_data.get("input", "")
|
|
54
|
+
|
|
55
|
+
async for chunk in self.chain.astream({"input": user_input}):
|
|
56
|
+
if hasattr(chunk, 'content'):
|
|
57
|
+
yield chunk.content
|
|
58
|
+
else:
|
|
59
|
+
yield str(chunk)
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from langchain.agents import AgentExecutor, create_tool_calling_agent
|
|
4
|
+
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
|
|
5
|
+
|
|
6
|
+
from app.ai.agents.base_agent import BaseAgent
|
|
7
|
+
from {{ llm_import }}
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class {{ class_name }}(BaseAgent):
|
|
11
|
+
"""{{ class_name }} - Tool-using agent implementation."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, model: str = "{{ model_name }}", tools: list = None):
|
|
14
|
+
super().__init__(name="{{ snake_name }}")
|
|
15
|
+
self.llm = {{ llm_class }}(model=model)
|
|
16
|
+
self.tools = tools or []
|
|
17
|
+
|
|
18
|
+
self.prompt = ChatPromptTemplate.from_messages([
|
|
19
|
+
("system", "You are a helpful AI assistant with access to tools. Use them when needed."),
|
|
20
|
+
("human", "{input}"),
|
|
21
|
+
MessagesPlaceholder(variable_name="agent_scratchpad"),
|
|
22
|
+
])
|
|
23
|
+
|
|
24
|
+
if self.tools:
|
|
25
|
+
agent = create_tool_calling_agent(self.llm, self.tools, self.prompt)
|
|
26
|
+
self.agent_executor = AgentExecutor(agent=agent, tools=self.tools, verbose=True)
|
|
27
|
+
else:
|
|
28
|
+
self.agent_executor = None
|
|
29
|
+
|
|
30
|
+
async def run(self, input_data: dict[str, Any]) -> dict[str, Any]:
|
|
31
|
+
"""
|
|
32
|
+
Execute the tool agent logic.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
input_data: Input data containing 'input' and optional 'tools'
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
dict: Agent execution result with tool usage information
|
|
39
|
+
"""
|
|
40
|
+
user_input = input_data.get("input", "")
|
|
41
|
+
|
|
42
|
+
if not self.agent_executor:
|
|
43
|
+
return {
|
|
44
|
+
"status": "error",
|
|
45
|
+
"message": "No tools configured for this agent",
|
|
46
|
+
"model": self.llm.model_name
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
result = await self.agent_executor.ainvoke({"input": user_input})
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
"status": "success",
|
|
53
|
+
"response": result.get("output", ""),
|
|
54
|
+
"model": self.llm.model_name,
|
|
55
|
+
"tools_used": len(self.tools)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
def add_tool(self, tool):
|
|
59
|
+
"""Add a tool to the agent."""
|
|
60
|
+
self.tools.append(tool)
|
|
61
|
+
# Recreate agent executor with new tools
|
|
62
|
+
agent = create_tool_calling_agent(self.llm, self.tools, self.prompt)
|
|
63
|
+
self.agent_executor = AgentExecutor(agent=agent, tools=self.tools, verbose=True)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from app.ai.chains.base_chain import BaseChain
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class {{ class_name }}(BaseChain):
|
|
7
|
+
"""{{ class_name }} chain implementation."""
|
|
8
|
+
|
|
9
|
+
def __init__(self):
|
|
10
|
+
super().__init__(name="{{ snake_name }}")
|
|
11
|
+
|
|
12
|
+
async def run(self, input_data: dict[str, Any]) -> dict[str, Any]:
|
|
13
|
+
"""
|
|
14
|
+
Execute the chain logic.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
input_data: Input data for the chain
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
dict: Chain execution result
|
|
21
|
+
"""
|
|
22
|
+
# TODO: Implement chain logic here
|
|
23
|
+
return {"status": "success", "message": "{{ class_name }} executed"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class {{ class_name }}:
|
|
5
|
+
"""{{ class_name }} controller for business logic."""
|
|
6
|
+
|
|
7
|
+
async def index(self) -> dict[str, Any]:
|
|
8
|
+
"""List all items."""
|
|
9
|
+
# TODO: Implement list logic
|
|
10
|
+
return {"data": [], "total": 0}
|
|
11
|
+
|
|
12
|
+
async def show(self, id: str) -> dict[str, Any]:
|
|
13
|
+
"""Get a specific item by ID."""
|
|
14
|
+
# TODO: Implement get logic
|
|
15
|
+
return {"id": id, "data": {}}
|
|
16
|
+
|
|
17
|
+
async def create(self, data: dict[str, Any]) -> dict[str, Any]:
|
|
18
|
+
"""Create a new item."""
|
|
19
|
+
# TODO: Implement create logic
|
|
20
|
+
return {"id": "new_id", "data": data}
|
|
21
|
+
|
|
22
|
+
async def update(self, id: str, data: dict[str, Any]) -> dict[str, Any]:
|
|
23
|
+
"""Update an item by ID."""
|
|
24
|
+
# TODO: Implement update logic
|
|
25
|
+
return {"id": id, "data": data}
|
|
26
|
+
|
|
27
|
+
async def delete(self, id: str) -> dict[str, Any]:
|
|
28
|
+
"""Delete an item by ID."""
|
|
29
|
+
# TODO: Implement delete logic
|
|
30
|
+
return {"id": id, "deleted": True}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class {{ class_name }}:
|
|
6
|
+
"""{{ class_name }} event."""
|
|
7
|
+
|
|
8
|
+
def __init__(self, data: dict[str, Any]):
|
|
9
|
+
self.data = data
|
|
10
|
+
self.timestamp = datetime.utcnow()
|
|
11
|
+
|
|
12
|
+
def to_dict(self) -> dict[str, Any]:
|
|
13
|
+
"""Convert event to dictionary."""
|
|
14
|
+
return {
|
|
15
|
+
"event": "{{ snake_name }}",
|
|
16
|
+
"data": self.data,
|
|
17
|
+
"timestamp": self.timestamp.isoformat(),
|
|
18
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from typing import Any, Optional
|
|
2
|
+
|
|
3
|
+
from app.exceptions.base import ElricException
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class {{ class_name }}(ElricException):
|
|
7
|
+
"""{{ class_name }} exception."""
|
|
8
|
+
|
|
9
|
+
def __init__(
|
|
10
|
+
self,
|
|
11
|
+
message: str = "{{ class_name }} error occurred",
|
|
12
|
+
error_code: str = "{{ snake_name }}_error",
|
|
13
|
+
status_code: int = 500,
|
|
14
|
+
details: Optional[dict[str, Any]] = None,
|
|
15
|
+
):
|
|
16
|
+
super().__init__(
|
|
17
|
+
message=message,
|
|
18
|
+
status_code=status_code,
|
|
19
|
+
error_code=error_code,
|
|
20
|
+
details=details,
|
|
21
|
+
)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import structlog
|
|
2
|
+
|
|
3
|
+
logger = structlog.get_logger()
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class {{ class_name }}:
|
|
7
|
+
"""{{ class_name }} background job."""
|
|
8
|
+
|
|
9
|
+
async def execute(self, *args, **kwargs):
|
|
10
|
+
"""
|
|
11
|
+
Execute the job logic.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
*args: Positional arguments
|
|
15
|
+
**kwargs: Keyword arguments
|
|
16
|
+
"""
|
|
17
|
+
logger.info("job.started", job="{{ snake_name }}")
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
# TODO: Implement job logic here
|
|
21
|
+
logger.info("job.completed", job="{{ snake_name }}")
|
|
22
|
+
except Exception as e:
|
|
23
|
+
logger.error("job.failed", job="{{ snake_name }}", error=str(e))
|
|
24
|
+
raise
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import structlog
|
|
2
|
+
|
|
3
|
+
logger = structlog.get_logger()
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class {{ class_name }}:
|
|
7
|
+
"""{{ class_name }} event listener."""
|
|
8
|
+
|
|
9
|
+
async def handle(self, event):
|
|
10
|
+
"""
|
|
11
|
+
Handle the event.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
event: The event to handle
|
|
15
|
+
"""
|
|
16
|
+
logger.info("listener.handling", listener="{{ snake_name }}", event=event.to_dict())
|
|
17
|
+
|
|
18
|
+
try:
|
|
19
|
+
# TODO: Implement event handling logic here
|
|
20
|
+
logger.info("listener.handled", listener="{{ snake_name }}")
|
|
21
|
+
except Exception as e:
|
|
22
|
+
logger.error("listener.failed", listener="{{ snake_name }}", error=str(e))
|
|
23
|
+
raise
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import structlog
|
|
2
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
|
3
|
+
from starlette.requests import Request
|
|
4
|
+
|
|
5
|
+
logger = structlog.get_logger()
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class {{ class_name }}(BaseHTTPMiddleware):
|
|
9
|
+
"""{{ class_name }} middleware."""
|
|
10
|
+
|
|
11
|
+
async def dispatch(self, request: Request, call_next):
|
|
12
|
+
"""
|
|
13
|
+
Process the request.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
request: The incoming request
|
|
17
|
+
call_next: The next middleware/route handler
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
Response from the next handler
|
|
21
|
+
"""
|
|
22
|
+
logger.debug("middleware.before", middleware="{{ snake_name }}", path=request.url.path)
|
|
23
|
+
|
|
24
|
+
# TODO: Add pre-processing logic here
|
|
25
|
+
|
|
26
|
+
response = await call_next(request)
|
|
27
|
+
|
|
28
|
+
# TODO: Add post-processing logic here
|
|
29
|
+
|
|
30
|
+
logger.debug("middleware.after", middleware="{{ snake_name }}", status_code=response.status_code)
|
|
31
|
+
|
|
32
|
+
return response
|