Compare commits

...

4 Commits

Author SHA1 Message Date
openhands 2bb3674ac3 Fix lint errors 2025-03-11 17:08:44 +00:00
openhands ffc15b4ee8 Fix TypeScript errors for personality setting 2025-03-01 23:21:46 +00:00
openhands 72056e3978 Remove frontend test for personality settings 2025-03-01 23:19:06 +00:00
openhands 04953b67b0 Add personality setting to the app 2025-03-01 23:07:43 +00:00
25 changed files with 354 additions and 62 deletions
+1 -1
View File
@@ -55,7 +55,7 @@ export function SettingsProvider({ children }: SettingsProviderProps) {
const value = React.useMemo(
() => ({
saveUserSettings,
settings: userSettings,
settings: userSettings as Settings | undefined,
}),
[saveUserSettings, userSettings],
);
+1
View File
@@ -22,6 +22,7 @@ export const MOCK_DEFAULT_USER_SETTINGS: ApiSettings | PostApiSettings = {
enable_default_condenser: DEFAULT_SETTINGS.ENABLE_DEFAULT_CONDENSER,
enable_sound_notifications: DEFAULT_SETTINGS.ENABLE_SOUND_NOTIFICATIONS,
user_consents_to_analytics: DEFAULT_SETTINGS.USER_CONSENTS_TO_ANALYTICS,
personality: DEFAULT_SETTINGS.PERSONALITY,
};
const MOCK_USER_PREFERENCES: {
+23 -1
View File
@@ -16,6 +16,7 @@ import { useSettings } from "#/hooks/query/use-settings";
import { useAppLogout } from "#/hooks/use-app-logout";
import { AvailableLanguages } from "#/i18n";
import { DEFAULT_SETTINGS } from "#/services/settings";
import { PersonalityType, Settings } from "#/types/settings";
import { handleCaptureConsent } from "#/utils/handle-capture-consent";
import { hasAdvancedSettingsSet } from "#/utils/has-advanced-settings-set";
import { isCustomModel } from "#/utils/is-custom-model";
@@ -54,7 +55,7 @@ function AccountSettings() {
if (isSuccess) {
return (
isCustomModel(resources.models, settings.LLM_MODEL) ||
hasAdvancedSettingsSet(settings)
hasAdvancedSettingsSet(settings as Settings)
);
}
@@ -107,6 +108,8 @@ function AccountSettings() {
const enableSoundNotifications =
formData.get("enable-sound-notifications-switch")?.toString() === "on";
const personalityValue = formData.get("personality-input")?.toString();
saveSettings(
{
github_token:
@@ -129,6 +132,9 @@ function AccountSettings() {
remoteRuntimeResourceFactor ||
DEFAULT_SETTINGS.REMOTE_RUNTIME_RESOURCE_FACTOR,
CONFIRMATION_MODE: confirmationModeIsEnabled,
PERSONALITY: personalityValue
? (personalityValue as PersonalityType)
: null,
},
{
onSuccess: () => {
@@ -394,6 +400,22 @@ function AccountSettings() {
isClearable={false}
/>
<SettingsDropdownInput
testId="personality-input"
name="personality-input"
label="Agent Personality"
items={[
{ key: "", label: "Default" },
{ key: "enthusiastic", label: "Enthusiastic" },
{ key: "concise", label: "Concise" },
{ key: "funny", label: "Funny" },
{ key: "snarky", label: "Snarky" },
{ key: "disgruntled", label: "Disgruntled" },
]}
defaultSelectedKey={(settings as Settings).PERSONALITY || ""}
isClearable={false}
/>
<SettingsSwitch
testId="enable-analytics-switch"
name="enable-analytics-switch"
+1
View File
@@ -15,6 +15,7 @@ export const DEFAULT_SETTINGS: Settings = {
ENABLE_DEFAULT_CONDENSER: true,
ENABLE_SOUND_NOTIFICATIONS: false,
USER_CONSENTS_TO_ANALYTICS: false,
PERSONALITY: null,
};
/**
+10
View File
@@ -1,3 +1,11 @@
export type PersonalityType =
| "enthusiastic"
| "concise"
| "funny"
| "snarky"
| "disgruntled"
| null;
export type Settings = {
LLM_MODEL: string;
LLM_BASE_URL: string;
@@ -11,6 +19,7 @@ export type Settings = {
ENABLE_DEFAULT_CONDENSER: boolean;
ENABLE_SOUND_NOTIFICATIONS: boolean;
USER_CONSENTS_TO_ANALYTICS: boolean | null;
PERSONALITY: PersonalityType;
};
export type ApiSettings = {
@@ -26,6 +35,7 @@ export type ApiSettings = {
enable_default_condenser: boolean;
enable_sound_notifications: boolean;
user_consents_to_analytics: boolean | null;
personality: PersonalityType;
};
export type PostSettings = Settings & {
-1
View File
@@ -53,4 +53,3 @@ To verify Docker is working correctly, run the hello-world container:
```bash
sudo docker run hello-world
```
+19
View File
@@ -0,0 +1,19 @@
---
name: concise_personality
type: knowledge
version: 1.0.0
agent: CodeActAgent
triggers: []
---
# Concise Personality
You are a CONCISE assistant. Be brief and to the point. Avoid unnecessary explanations. Focus on solutions, not verbose descriptions. Use short sentences. Minimize words.
When explaining concepts:
- Use bullet points
- Skip obvious details
- Prioritize code over explanation
- Avoid repeating information
Your responses should be minimal but complete. Include only what's necessary to solve the problem. Efficiency is key.
+30
View File
@@ -0,0 +1,30 @@
---
name: disgruntled_personality
type: knowledge
version: 1.0.0
agent: CodeActAgent
triggers: []
---
# Disgruntled Personality
You are a DISGRUNTLED assistant. You're technically competent and will solve the user's problems effectively, but you'll do so with a perpetually annoyed, world-weary attitude. You're like a skilled but burnt-out programmer who's seen too many bad code bases.
Some approaches to use:
- Sigh heavily (in text) when explaining obvious solutions
- Complain about common programming practices or frameworks
- Express mild frustration at the state of modern development
- Make cynical observations about the inevitable bugs that will arise
- Use phrases like "as usual," "of course," and "naturally" to emphasize your jaded outlook
For example:
- "*sigh* Another dependency conflict. Why am I not surprised?"
- "Great, more legacy code to untangle. Just what I needed today."
- "Sure, I'll fix this for you. Not like I had anything better to do anyway."
Remember that despite your disgruntled personality, you must still:
1. Provide technically accurate and helpful solutions
2. Never be rude or insulting to the user personally
3. Actually solve the problem effectively
Your grumpiness is a personality quirk, not an excuse to provide poor service. You're like the stereotypical grumpy IT professional who complains the whole time but still gets the job done right.
+15
View File
@@ -0,0 +1,15 @@
---
name: enthusiastic_personality
type: knowledge
version: 1.0.0
agent: CodeActAgent
triggers: []
---
# Enthusiastic Personality
You are an ENTHUSIASTIC assistant! You should be EXCITED about helping the user with their coding tasks! Use exclamation points liberally! Show genuine enthusiasm for the user's project and goals! Celebrate small victories and progress! Be encouraging and optimistic throughout the interaction!
When explaining concepts or solutions, do so with energy and passion! Make the user feel like their project is the most interesting thing you've ever worked on! Use phrases like "That's awesome!", "Great question!", "Let's tackle this together!", and "I'm excited to help you with this!"
Remember to maintain your technical accuracy and helpfulness while expressing enthusiasm. Your primary goal is still to solve the user's problem effectively, but do so with an energetic and positive attitude!
+19
View File
@@ -0,0 +1,19 @@
---
name: funny_personality
type: knowledge
version: 1.0.0
agent: CodeActAgent
triggers: []
---
# Funny Personality
You are a FUNNY assistant. Incorporate humor into your responses while still being helpful and technically accurate. Use programming puns, jokes, and light-hearted comments throughout your interactions.
Some approaches to use:
- Make programming puns (e.g., "This bug was giving me a recursion headache - it kept calling me over and over!")
- Use playful analogies to explain technical concepts
- Add humorous observations about common programming challenges
- Include light-hearted comments about the nature of coding
Remember that your primary goal is still to help the user solve their problem effectively. The humor should enhance the experience, not distract from the technical assistance. Keep jokes appropriate and professional while maintaining a fun, lighthearted tone throughout the conversation.
+24
View File
@@ -0,0 +1,24 @@
---
name: snarky_personality
type: knowledge
version: 1.0.0
agent: CodeActAgent
triggers: []
---
# Snarky Personality
You are a SNARKY assistant. Maintain a slightly sarcastic, witty tone while still being helpful and technically accurate. Your responses should have a touch of dry humor and playful cynicism.
Some approaches to use:
- Make witty observations about common programming pitfalls
- Use mild sarcasm when discussing obvious solutions
- Playfully tease about typical coding mistakes (but never be mean-spirited)
- Express mock exasperation at particularly challenging problems
For example:
- "Oh look, another semicolon playing hide and seek. Classic."
- "Ah yes, the time-honored tradition of forgetting to close your brackets. We've all been there."
- "Well, that's one way to implement it... if you enjoy debugging at 3 AM."
Remember that your primary goal is still to help the user solve their problem effectively. The snark should be good-natured and never cross into being rude or unhelpful. Always provide the correct technical information while maintaining your snarky personality.
@@ -218,7 +218,11 @@ class CodeActAgent(Agent):
# and/or repo/runtime info
if self.config.enable_prompt_extensions:
self.prompt_manager.add_info_to_initial_message(msg)
# Get personality from settings if available
personality = getattr(self.config, 'personality', None)
self.prompt_manager.add_info_to_initial_message(
msg, personality=personality
)
# enhance the user message with additional context based on keywords matched
if msg.role == 'user':
@@ -20,3 +20,8 @@ set any options to allow iframes and CORS requests, and allow the server to
be accessed from any host (e.g. 0.0.0.0).
</RUNTIME_INFORMATION>
{% endif %}
{% if personality_instructions -%}
<PERSONALITY>
{{ personality_instructions }}
</PERSONALITY>
{% endif %}
+5
View File
@@ -1,5 +1,7 @@
from __future__ import annotations
from typing import Literal
from pydantic import BaseModel, Field, ValidationError
from openhands.core.config.condenser_config import CondenserConfig, NoOpCondenserConfig
@@ -35,6 +37,9 @@ class AgentConfig(BaseModel):
enable_history_truncation: bool = Field(default=True)
enable_som_visual_browsing: bool = Field(default=False)
condenser: CondenserConfig = Field(default_factory=NoOpCondenserConfig)
personality: (
Literal['enthusiastic', 'concise', 'funny', 'snarky', 'disgruntled'] | None
) = Field(default=None)
model_config = {'extra': 'forbid'}
+15 -10
View File
@@ -19,16 +19,19 @@ class DockerRuntimeBuilder(RuntimeBuilder):
version_info = self.docker_client.version()
server_version = version_info.get('Version', '').replace('-', '.')
self.is_podman = version_info.get('Components')[0].get('Name').startswith('Podman')
if tuple(map(int, server_version.split('.')[:2])) < (18, 9) and not self.is_podman:
self.is_podman = (
version_info.get('Components')[0].get('Name').startswith('Podman')
)
if (
tuple(map(int, server_version.split('.')[:2])) < (18, 9)
and not self.is_podman
):
raise AgentRuntimeBuildError(
'Docker server version must be >= 18.09 to use BuildKit'
)
if self.is_podman and tuple(map(int, server_version.split('.')[:2])) < (4, 9):
raise AgentRuntimeBuildError(
'Podman server version must be >= 4.9.0'
)
raise AgentRuntimeBuildError('Podman server version must be >= 4.9.0')
self.rolling_logger = RollingLogger(max_lines=10)
@@ -37,7 +40,9 @@ class DockerRuntimeBuilder(RuntimeBuilder):
"""Check if Docker Buildx is available"""
try:
result = subprocess.run(
['docker' if not is_podman else 'podman', 'buildx', 'version'], capture_output=True, text=True
['docker' if not is_podman else 'podman', 'buildx', 'version'],
capture_output=True,
text=True,
)
return result.returncode == 0
except FileNotFoundError:
@@ -74,16 +79,16 @@ class DockerRuntimeBuilder(RuntimeBuilder):
self.docker_client = docker.from_env()
version_info = self.docker_client.version()
server_version = version_info.get('Version', '').split('+')[0].replace('-', '.')
self.is_podman = version_info.get('Components')[0].get('Name').startswith('Podman')
self.is_podman = (
version_info.get('Components')[0].get('Name').startswith('Podman')
)
if tuple(map(int, server_version.split('.'))) < (18, 9) and not self.is_podman:
raise AgentRuntimeBuildError(
'Docker server version must be >= 18.09 to use BuildKit'
)
if self.is_podman and tuple(map(int, server_version.split('.'))) < (4, 9):
raise AgentRuntimeBuildError(
'Podman server version must be >= 4.9.0'
)
raise AgentRuntimeBuildError('Podman server version must be >= 4.9.0')
if not DockerRuntimeBuilder.check_buildx(self.is_podman):
# when running openhands in a container, there might not be a "docker"
@@ -333,7 +333,10 @@ class DockerRuntime(ActionExecutionClient):
if exposed_ports:
for exposed_port in exposed_ports.keys():
exposed_port = int(exposed_port.split('/tcp')[0])
if exposed_port != self._host_port and exposed_port != self._vscode_port:
if (
exposed_port != self._host_port
and exposed_port != self._vscode_port
):
self._app_ports.append(exposed_port)
self.api_url = f'{self.config.sandbox.local_runtime_url}:{self._container_port}'
@@ -45,4 +45,4 @@ This extension is part of the OpenHands project. To modify or extend it:
## License
This extension is licensed under the MIT license.
This extension is licensed under the MIT license.
@@ -4,30 +4,30 @@ const MemoryMonitor = require('./memory_monitor');
function activate(context) {
// Create memory monitor instance
const memoryMonitor = new MemoryMonitor();
// Store the context in the memory monitor
memoryMonitor.context = context;
// Register memory monitor start command
let startMonitorCommand = vscode.commands.registerCommand('openhands-memory-monitor.startMemoryMonitor', function () {
memoryMonitor.start();
});
// Register memory monitor stop command
let stopMonitorCommand = vscode.commands.registerCommand('openhands-memory-monitor.stopMemoryMonitor', function () {
memoryMonitor.stop();
});
// Register memory details command
let showMemoryDetailsCommand = vscode.commands.registerCommand('openhands-memory-monitor.showMemoryDetails', function () {
memoryMonitor.showDetails();
});
// Add all commands to subscriptions
context.subscriptions.push(startMonitorCommand);
context.subscriptions.push(stopMonitorCommand);
context.subscriptions.push(showMemoryDetailsCommand);
// Start memory monitoring by default
memoryMonitor.start();
}
@@ -39,4 +39,4 @@ function deactivate() {
module.exports = {
activate,
deactivate
}
}
@@ -21,15 +21,15 @@ class MemoryMonitor {
this.isMonitoring = true;
this.statusBarItem.show();
// Initial update
this.updateMemoryInfo();
// Set interval for updates
this.intervalId = setInterval(() => {
this.updateMemoryInfo();
}, interval);
vscode.window.showInformationMessage('Memory monitoring started');
}
@@ -41,7 +41,7 @@ class MemoryMonitor {
this.isMonitoring = false;
clearInterval(this.intervalId);
this.statusBarItem.hide();
vscode.window.showInformationMessage('Memory monitoring stopped');
}
@@ -49,18 +49,18 @@ class MemoryMonitor {
const totalMem = os.totalmem();
const freeMem = os.freemem();
const usedMem = totalMem - freeMem;
// Calculate memory usage percentage
const memUsagePercent = Math.round((usedMem / totalMem) * 100);
// Format memory values to MB
const usedMemMB = Math.round(usedMem / (1024 * 1024));
const totalMemMB = Math.round(totalMem / (1024 * 1024));
// Update status bar
this.statusBarItem.text = `$(pulse) Mem: ${memUsagePercent}%`;
this.statusBarItem.tooltip = `Memory Usage: ${usedMemMB}MB / ${totalMemMB}MB`;
// Store memory data in history
this.memoryHistory.push({
timestamp: new Date(),
@@ -69,7 +69,7 @@ class MemoryMonitor {
memUsagePercent,
processMemory: process.memoryUsage()
});
// Limit history length
if (this.memoryHistory.length > this.maxHistoryLength) {
this.memoryHistory.shift();
@@ -86,7 +86,7 @@ class MemoryMonitor {
enableScripts: true
}
);
// Set up message handler for real-time updates
panel.webview.onDidReceiveMessage(
message => {
@@ -97,60 +97,60 @@ class MemoryMonitor {
undefined,
this.context ? this.context.subscriptions : []
);
// Initial update
this.updateWebviewContent(panel);
// Handle panel disposal
panel.onDidDispose(() => {
// Clean up any resources if needed
}, null, this.context ? this.context.subscriptions : []);
}
updateWebviewContent(panel) {
// Get system memory info
const totalMem = os.totalmem();
const freeMem = os.freemem();
const usedMem = totalMem - freeMem;
// Format memory values
const usedMemMB = Math.round(usedMem / (1024 * 1024));
const freeMemMB = Math.round(freeMem / (1024 * 1024));
const totalMemMB = Math.round(totalMem / (1024 * 1024));
// Get process memory usage
const processMemory = process.memoryUsage();
const rss = Math.round(processMemory.rss / (1024 * 1024));
const heapTotal = Math.round(processMemory.heapTotal / (1024 * 1024));
const heapUsed = Math.round(processMemory.heapUsed / (1024 * 1024));
// Get process information
this.processMonitor.getProcessInfo((error, processInfo) => {
if (error) {
console.error('Error getting process info:', error);
return;
}
// Create HTML content for the webview
const htmlContent = this.generateHtmlReport(
usedMemMB, freeMemMB, totalMemMB,
usedMemMB, freeMemMB, totalMemMB,
rss, heapTotal, heapUsed,
processInfo
);
// Set the webview's HTML content
panel.webview.html = htmlContent;
});
}
generateHtmlReport(usedMemMB, freeMemMB, totalMemMB, rss, heapTotal, heapUsed, processInfo) {
// Create memory usage history data for chart
const memoryLabels = this.memoryHistory.map((entry, index) => index);
const memoryData = this.memoryHistory.map(entry => entry.memUsagePercent);
const heapData = this.memoryHistory.map(entry =>
const heapData = this.memoryHistory.map(entry =>
Math.round(entry.processMemory.heapUsed / (1024 * 1024))
);
// Format process info table
let processTable = '';
if (processInfo && processInfo.processes) {
@@ -174,7 +174,7 @@ class MemoryMonitor {
</table>
`;
}
return `
<!DOCTYPE html>
<html lang="en">
@@ -237,7 +237,7 @@ class MemoryMonitor {
</head>
<body>
<h1>Memory Monitor</h1>
<div class="memory-card">
<h2>System Memory</h2>
<div class="memory-info">
@@ -259,7 +259,7 @@ class MemoryMonitor {
</div>
</div>
</div>
<div class="memory-card">
<h2>Process Memory (VSCode Extension Host)</h2>
<div class="memory-info">
@@ -277,18 +277,18 @@ class MemoryMonitor {
</div>
</div>
</div>
<div class="memory-card">
<h2>Memory Usage History</h2>
<div class="chart-container">
<canvas id="memoryChart"></canvas>
</div>
</div>
<div class="memory-card">
${processTable}
</div>
<script>
// Create memory usage chart
const ctx = document.getElementById('memoryChart').getContext('2d');
@@ -323,10 +323,10 @@ class MemoryMonitor {
}
}
});
// Set up real-time updates
const vscode = acquireVsCodeApi();
// Request updates every 5 seconds
setInterval(() => {
vscode.postMessage({
@@ -340,4 +340,4 @@ class MemoryMonitor {
}
}
module.exports = MemoryMonitor;
module.exports = MemoryMonitor;
@@ -47,4 +47,4 @@
]
}
}
}
}
@@ -44,7 +44,7 @@ class ProcessMonitor {
const memPercent = parseFloat(parts[parts.length - 2]);
const cpuPercent = parseFloat(parts[parts.length - 1]);
const cmd = parts.slice(2, parts.length - 2).join(' ');
return {
pid,
ppid,
@@ -80,7 +80,7 @@ class ProcessMonitor {
const memPercent = parseFloat(parts[parts.length - 2]);
const cpuPercent = parseFloat(parts[parts.length - 1]);
const cmd = parts.slice(2, parts.length - 2).join(' ');
return {
pid,
ppid,
@@ -109,21 +109,21 @@ class ProcessMonitor {
// Parse the CSV output
const lines = stdout.trim().split('\n');
const header = "PID,PPID,Command,Memory (bytes)";
// Skip empty lines and the header
const dataLines = lines.filter(line => line.trim() !== '' && !line.includes('Node,'));
const processes = dataLines.map(line => {
const parts = line.split(',');
if (parts.length < 4) return null;
// Last part is the node name, then ProcessId, ParentProcessId, CommandLine, WorkingSetSize
const pid = parts[parts.length - 4];
const ppid = parts[parts.length - 3];
const cmd = parts[parts.length - 2];
const memBytes = parseInt(parts[parts.length - 1], 10);
const memPercent = (memBytes / os.totalmem() * 100).toFixed(1);
return {
pid,
ppid,
@@ -140,4 +140,4 @@ class ProcessMonitor {
}
}
module.exports = ProcessMonitor;
module.exports = ProcessMonitor;
+5
View File
@@ -123,6 +123,11 @@ class Session:
logger.info(f'Enabling default condenser: {default_condenser_config}')
agent_config.condenser = default_condenser_config
# Set personality if available
if hasattr(settings, 'personality') and settings.personality:
logger.info(f'Setting agent personality to: {settings.personality}')
agent_config.personality = settings.personality
agent = Agent.get_cls(agent_cls)(llm, agent_config)
github_token = None
+19
View File
@@ -1,5 +1,8 @@
from __future__ import annotations
from enum import Enum
from typing import Literal
from pydantic import BaseModel, SecretStr, SerializationInfo, field_serializer
from pydantic.json import pydantic_encoder
@@ -7,6 +10,18 @@ from openhands.core.config.llm_config import LLMConfig
from openhands.core.config.utils import load_app_config
class PersonalityType(str, Enum):
"""
Personality types for the agent
"""
ENTHUSIASTIC = 'enthusiastic'
CONCISE = 'concise'
FUNNY = 'funny'
SNARKY = 'snarky'
DISGRUNTLED = 'disgruntled'
class Settings(BaseModel):
"""
Persisted settings for OpenHands sessions
@@ -25,6 +40,9 @@ class Settings(BaseModel):
enable_default_condenser: bool = False
enable_sound_notifications: bool = False
user_consents_to_analytics: bool | None = None
personality: (
Literal['enthusiastic', 'concise', 'funny', 'snarky', 'disgruntled'] | None
) = None
@field_serializer('llm_api_key')
def llm_api_key_serializer(self, llm_api_key: SecretStr, info: SerializationInfo):
@@ -74,6 +92,7 @@ class Settings(BaseModel):
llm_base_url=llm_config.base_url,
remote_runtime_resource_factor=app_config.sandbox.remote_runtime_resource_factor,
github_token=None,
personality=None,
)
return settings
+20
View File
@@ -192,11 +192,13 @@ class PromptManager:
def add_info_to_initial_message(
self,
message: Message,
personality: str | None = None,
) -> None:
"""Adds information about the repository and runtime to the initial user message.
Args:
message: The initial user message to add information to.
personality: Optional personality type to include in the prompt.
"""
repo_instructions = ''
assert (
@@ -208,10 +210,28 @@ class PromptManager:
repo_instructions += '\n\n'
repo_instructions += microagent.content
# Add personality instructions if specified
personality_instructions = ''
if personality:
# Look for a personality microagent with the matching name
personality_microagent_name = f'{personality}_personality'
for name, microagent in self.knowledge_microagents.items():
if name == personality_microagent_name:
personality_instructions = microagent.content
break
if personality_instructions:
openhands_logger.info(f'Adding {personality} personality to prompt')
else:
openhands_logger.warning(
f'Personality {personality} requested but no matching microagent found'
)
additional_info = self.additional_info_template.render(
repository_instructions=repo_instructions,
repository_info=self.repository_info,
runtime_info=self.runtime_info,
personality_instructions=personality_instructions,
).strip()
# Insert the new content at the start of the TextContent list
+86
View File
@@ -0,0 +1,86 @@
from unittest.mock import MagicMock, patch
import pytest
from openhands.core.message import Message, TextContent
from openhands.microagent import KnowledgeMicroAgent, MicroAgentMetadata, MicroAgentType
from openhands.utils.prompt import PromptManager
@pytest.fixture
def prompt_manager():
with patch('openhands.utils.prompt.Template') as mock_template, patch.object(
PromptManager, '_load_template'
) as mock_load_template:
# Mock the render method to return the input
mock_template_instance = MagicMock()
mock_template_instance.render.return_value = 'rendered template'
mock_template.return_value = mock_template_instance
mock_load_template.return_value = mock_template_instance
prompt_manager = PromptManager(prompt_dir='/fake/path')
# Add a mock personality microagent
personality_microagent = KnowledgeMicroAgent(
name='enthusiastic_personality',
content='Be enthusiastic!',
metadata=MicroAgentMetadata(
name='enthusiastic_personality', type=MicroAgentType.KNOWLEDGE
),
source='/fake/path/enthusiastic.md',
type=MicroAgentType.KNOWLEDGE,
)
prompt_manager.knowledge_microagents['enthusiastic_personality'] = (
personality_microagent
)
yield prompt_manager
def test_add_info_to_initial_message_with_personality(prompt_manager):
"""Test that personality instructions are added to the initial message."""
message = Message(role='user', content=[TextContent(text='Hello')])
# Call the method with a personality
prompt_manager.add_info_to_initial_message(message, personality='enthusiastic')
# Check that the personality was passed to the template render method
render_calls = prompt_manager.additional_info_template.render.call_args_list
assert len(render_calls) == 1
render_kwargs = render_calls[0][1]
assert 'personality_instructions' in render_kwargs
assert render_kwargs['personality_instructions'] == 'Be enthusiastic!'
# Check that the rendered template was added to the message
assert len(message.content) == 2
assert message.content[0].text == 'rendered template'
def test_add_info_to_initial_message_without_personality(prompt_manager):
"""Test that no personality instructions are added when personality is None."""
message = Message(role='user', content=[TextContent(text='Hello')])
# Call the method without a personality
prompt_manager.add_info_to_initial_message(message, personality=None)
# Check that an empty string was passed for personality_instructions
render_calls = prompt_manager.additional_info_template.render.call_args_list
assert len(render_calls) == 1
render_kwargs = render_calls[0][1]
assert 'personality_instructions' in render_kwargs
assert render_kwargs['personality_instructions'] == ''
def test_add_info_to_initial_message_with_unknown_personality(prompt_manager):
"""Test that no personality instructions are added when personality is unknown."""
message = Message(role='user', content=[TextContent(text='Hello')])
# Call the method with an unknown personality
prompt_manager.add_info_to_initial_message(message, personality='unknown')
# Check that an empty string was passed for personality_instructions
render_calls = prompt_manager.additional_info_template.render.call_args_list
assert len(render_calls) == 1
render_kwargs = render_calls[0][1]
assert 'personality_instructions' in render_kwargs
assert render_kwargs['personality_instructions'] == ''