Skip to content

Commit 5941b76

Browse files
Copilotphrocker
andcommitted
Implement properties-based configuration system similar to Java agent
Co-authored-by: phrocker <1781585+phrocker@users.noreply.github.com>
1 parent 6dff466 commit 5941b76

11 files changed

Lines changed: 657 additions & 72 deletions

File tree

python-agent/README.md

Lines changed: 115 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,55 @@ The Python agent mirrors the Java agent architecture with these key components:
2727

2828
## Configuration
2929

30-
### YAML Configuration
30+
The Python agent supports multiple configuration approaches, similar to the Java agent:
31+
32+
### Properties-based Configuration (Recommended)
33+
34+
Similar to the Java agent's `application.properties`, you can use a properties file that references agent-specific YAML files:
35+
36+
**application.properties:**
37+
```properties
38+
# Keycloak Configuration
39+
keycloak.realm=sentrius
40+
keycloak.base-url=${KEYCLOAK_BASE_URL:http://localhost:8180}
41+
keycloak.client-id=${KEYCLOAK_CLIENT_ID:python-agents}
42+
keycloak.client-secret=${KEYCLOAK_CLIENT_SECRET:your-client-secret}
43+
44+
# Agent Configuration
45+
agent.name.prefix=python-agent
46+
agent.type=python
47+
agent.callback.url=${AGENT_CALLBACK_URL:http://localhost:8093}
48+
agent.api.url=${AGENT_API_URL:http://localhost:8080/}
49+
50+
# Agent Definitions - these reference YAML files that define agent behavior
51+
agent.chat.helper.config=chat-helper.yaml
52+
agent.chat.helper.enabled=true
53+
54+
agent.data.analyst.config=data-analyst.yaml
55+
agent.data.analyst.enabled=false
56+
```
57+
58+
**chat-helper.yaml:**
59+
```yaml
60+
description: "Agent that handles chat interactions and provides helpful responses via OpenAI integration."
61+
context: |
62+
You are a helpful agent that is responding to users via a chat interface. Please respond to the user in a friendly and helpful manner.
63+
Return responses in the following format:
64+
{
65+
"previousOperation": "<previousOperation>",
66+
"nextOperation": "<nextOperation>",
67+
"terminalSummaryForLLM": "<Summary of the terminal thus far for the next LLM assessment>",
68+
"responseForUser": "<Response to the user>"
69+
}
70+
```
71+
72+
### Environment Variable Substitution
73+
74+
Properties support environment variable substitution using the `${VARIABLE:default}` syntax:
75+
- `${KEYCLOAK_BASE_URL:http://localhost:8180}` - Uses `KEYCLOAK_BASE_URL` env var or defaults to `http://localhost:8180`
76+
- `${KEYCLOAK_CLIENT_ID:python-agents}` - Uses `KEYCLOAK_CLIENT_ID` env var or defaults to `python-agents`
77+
78+
### Legacy YAML Configuration
3179
```yaml
3280
keycloak:
3381
server_url: "http://localhost:8080"
@@ -64,6 +112,35 @@ AGENT_HEARTBEAT_INTERVAL=30
64112

65113
## Usage
66114

115+
### Running Agents
116+
117+
#### With Properties Configuration
118+
```bash
119+
# Run chat helper agent using default application.properties
120+
python main.py chat-helper
121+
122+
# Run with custom configuration file
123+
python main.py chat-helper --config my-app.properties
124+
125+
# Run with task data
126+
python main.py chat-helper --task-data '{"message": "Hello, how can you help?"}'
127+
128+
# Test mode (no external services)
129+
TEST_MODE=true python main.py chat-helper
130+
```
131+
132+
#### Environment Variable Configuration
133+
```bash
134+
# Set environment variables
135+
export KEYCLOAK_BASE_URL=http://localhost:8180
136+
export KEYCLOAK_CLIENT_ID=python-agents
137+
export KEYCLOAK_CLIENT_SECRET=your-secret
138+
export TEST_MODE=false
139+
140+
# Run agent
141+
python main.py chat-helper
142+
```
143+
67144
### Agent Framework
68145
The Python agent provides a framework for creating custom agents that integrate with the Sentrius platform. All agents interact through APIs using JWT authentication, working with DTOs from the API and the LLM proxy.
69146

@@ -72,31 +149,54 @@ The Python agent provides a framework for creating custom agents that integrate
72149
from agents.base import BaseAgent
73150
74151
class MyCustomAgent(BaseAgent):
75-
def __init__(self, config_path=None):
76-
super().__init__("My Custom Agent", config_path=config_path)
152+
def __init__(self, config_manager):
153+
super().__init__(config_manager, name="my-custom-agent")
154+
# Load agent-specific configuration
155+
self.agent_definition = config_manager.get_agent_definition('my.custom')
77156
78-
def execute_task(self):
157+
def execute_task(self, task_data=None):
79158
# Your custom agent logic here
80159
# Note: All data access is through Sentrius APIs, not direct database connections
81160
self.submit_provenance(
82161
event_type="CUSTOM_TASK",
83-
details={"task": "custom_operation"}
162+
details={"task": "custom_operation", "data": task_data}
84163
)
85164
86-
def run(self):
87-
# Start the agent lifecycle
88-
self.start()
89-
self.execute_task()
90-
self.stop()
165+
# Return structured response
166+
return {
167+
"status": "completed",
168+
"result": "Custom task executed successfully"
169+
}
170+
```
171+
172+
**my-custom.yaml:**
173+
```yaml
174+
description: "Custom agent that performs specialized tasks"
175+
context: |
176+
You are a custom agent designed to handle specific business logic.
177+
Process requests according to your specialized capabilities.
178+
```
179+
180+
**Add to application.properties:**
181+
```properties
182+
agent.my.custom.config=my-custom.yaml
183+
agent.my.custom.enabled=true
91184
```
92185

93186
### Running Custom Agents
94-
```bash
95-
# With configuration file
96-
python -c "from my_agent import MyCustomAgent; agent = MyCustomAgent('config.yaml'); agent.run()"
187+
```python
188+
# Add to main.py AVAILABLE_AGENTS dict
189+
from agents.my_custom.my_custom_agent import MyCustomAgent
97190
98-
# With environment variables
99-
python -c "from my_agent import MyCustomAgent; agent = MyCustomAgent(); agent.run()"
191+
AVAILABLE_AGENTS = {
192+
'chat-helper': ChatHelperAgent,
193+
'my-custom': MyCustomAgent, # Add your agent here
194+
}
195+
```
196+
197+
```bash
198+
# Run your custom agent
199+
python main.py my-custom --task-data '{"operation": "process_data"}'
100200
```
101201

102202
## API Operations

python-agent/agents/base.py

Lines changed: 80 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -11,74 +11,103 @@
1111
class BaseAgent(ABC):
1212
"""Abstract base class for all agents with Sentrius API integration."""
1313

14-
def __init__(self, name: str, config_path: Optional[str] = None, config: Optional[SentriusAgentConfig] = None):
15-
self.name = name
14+
def __init__(self, config_manager, name: Optional[str] = None):
15+
self.config_manager = config_manager
16+
self.name = name or self.__class__.__name__.lower().replace('agent', '')
1617

17-
# Load configuration
18-
if config:
19-
self.config = config
20-
elif config_path:
21-
self.config = SentriusAgentConfig.from_yaml(config_path)
22-
else:
23-
self.config = SentriusAgentConfig.from_env()
18+
# Check if we're in test mode
19+
self.test_mode = config_manager.get_property('test.mode', 'false').lower() == 'true'
2420

25-
# Initialize Sentrius agent
26-
self.sentrius_agent = SentriusAgent(self.config)
21+
if not self.test_mode:
22+
# Load configuration for Sentrius integration
23+
agent_config = config_manager.get_agent_config()
24+
keycloak_config = config_manager.get_keycloak_config()
25+
26+
# Create SentriusAgentConfig from the loaded configuration
27+
self.config = SentriusAgentConfig(
28+
keycloak_server_url=keycloak_config['server_url'],
29+
keycloak_realm=keycloak_config['realm'],
30+
keycloak_client_id=keycloak_config['client_id'],
31+
keycloak_client_secret=keycloak_config['client_secret'],
32+
agent_name_prefix=agent_config['name_prefix'],
33+
agent_type=agent_config['agent_type'],
34+
agent_callback_url=agent_config['callback_url'],
35+
api_url=agent_config['api_url'],
36+
heartbeat_interval=agent_config['heartbeat_interval']
37+
)
38+
39+
# Initialize Sentrius agent
40+
self.sentrius_agent = SentriusAgent(self.config)
41+
else:
42+
logger.info("Running in test mode - external services disabled")
43+
self.sentrius_agent = None
2744

28-
logger.info(f"Initialized {self.__class__.__name__}: {self.name}")
45+
logger.info(f"Initialized {self.__class__.__name__}: {config_manager}")
2946

3047
@abstractmethod
31-
def execute_task(self):
48+
def execute_task(self, task_data: Optional[Dict[str, Any]] = None):
3249
"""Method to execute the agent's specific task."""
3350
pass
3451

35-
def run(self):
52+
def run(self, task_data: Optional[Dict[str, Any]] = None):
3653
"""Main run method that handles agent lifecycle."""
3754
try:
38-
with self.sentrius_agent:
39-
logger.info(f"Starting {self.name} agent")
40-
41-
# Submit start event
42-
self.sentrius_agent.submit_provenance_event(
43-
event_type="AGENT_START",
44-
details={
45-
"agent_name": self.name,
46-
"agent_class": self.__class__.__name__
47-
}
48-
)
49-
50-
# Execute the specific task
51-
self.execute_task()
52-
53-
# Submit completion event
54-
self.sentrius_agent.submit_provenance_event(
55-
event_type="AGENT_COMPLETE",
56-
details={
57-
"agent_name": self.name,
58-
"status": "completed"
59-
}
60-
)
61-
62-
logger.info(f"{self.name} agent completed successfully")
55+
if self.sentrius_agent and not self.test_mode:
56+
with self.sentrius_agent:
57+
logger.info(f"Starting {self.name} agent")
58+
59+
# Submit start event
60+
self.sentrius_agent.submit_provenance_event(
61+
event_type="AGENT_START",
62+
details={
63+
"agent_name": self.name,
64+
"agent_class": self.__class__.__name__
65+
}
66+
)
67+
68+
# Execute the specific task
69+
result = self.execute_task(task_data)
70+
71+
# Submit completion event
72+
self.sentrius_agent.submit_provenance_event(
73+
event_type="AGENT_COMPLETE",
74+
details={
75+
"agent_name": self.name,
76+
"status": "completed"
77+
}
78+
)
79+
80+
logger.info(f"{self.name} agent completed successfully")
81+
return result
82+
else:
83+
# Test mode - just execute the task
84+
logger.info(f"Starting {self.name} agent (test mode)")
85+
result = self.execute_task(task_data)
86+
logger.info(f"{self.name} agent completed successfully (test mode)")
87+
return result
6388

6489
except Exception as e:
6590
logger.error(f"{self.name} agent failed: {e}")
6691

6792
# Submit error event
68-
try:
69-
self.sentrius_agent.submit_provenance_event(
70-
event_type="AGENT_ERROR",
71-
details={
72-
"agent_name": self.name,
73-
"error": str(e),
74-
"error_type": type(e).__name__
75-
}
76-
)
77-
except:
78-
pass # Don't fail if we can't submit error event
93+
if self.sentrius_agent and not self.test_mode:
94+
try:
95+
self.sentrius_agent.submit_provenance_event(
96+
event_type="AGENT_ERROR",
97+
details={
98+
"agent_name": self.name,
99+
"error": str(e),
100+
"error_type": type(e).__name__
101+
}
102+
)
103+
except:
104+
pass # Don't fail if we can't submit error event
79105

80106
raise
81107

82108
def submit_provenance(self, event_type: str, details: Dict[str, Any]):
83109
"""Submit a provenance event."""
84-
self.sentrius_agent.submit_provenance_event(event_type, details)
110+
if self.sentrius_agent and not self.test_mode:
111+
self.sentrius_agent.submit_provenance_event(event_type, details)
112+
else:
113+
logger.info(f"Test mode - would submit provenance: {event_type} - {details}")

python-agent/agents/chat_helper/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)