Skip to content

Commit 205f2af

Browse files
authored
Remove prompt manager and instead using simpler jinja util function (#63)
1 parent 2f35354 commit 205f2af

File tree

10 files changed

+107
-130
lines changed

10 files changed

+107
-130
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,7 @@ logs
236236
/_test_workspace
237237
/debug
238238
cache
239+
.jinja_cache/
239240

240241
# configuration
241242
config.toml

openhands/core/agent/base.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import os
2+
import sys
13
from abc import ABC, abstractmethod
24
from types import MappingProxyType
35

@@ -36,6 +38,15 @@ def __init__(
3638
_tools_map[tool.name] = tool
3739
self._tools = MappingProxyType(_tools_map)
3840

41+
@property
42+
def prompt_dir(self) -> str:
43+
"""Returns the directory where this class's module file is located."""
44+
module = sys.modules[self.__class__.__module__]
45+
module_file = module.__file__ # e.g. ".../mypackage/mymodule.py"
46+
if module_file is None:
47+
raise ValueError(f"Module file for {module} is None")
48+
return os.path.join(os.path.dirname(module_file), "prompts")
49+
3950
@property
4051
def name(self) -> str:
4152
"""Returns the name of the Agent."""

openhands/core/agent/codeact_agent/codeact_agent.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import json
2-
import os
32
from typing import cast
43

54
from litellm.types.utils import (
@@ -10,7 +9,7 @@
109
)
1110
from pydantic import ValidationError
1211

13-
from openhands.core.context import EnvContext, PromptManager
12+
from openhands.core.context import EnvContext, render_system_message
1413
from openhands.core.conversation import ConversationCallbackType, ConversationState
1514
from openhands.core.event import ActionEvent, AgentErrorEvent, LLMConvertibleEvent, MessageEvent, ObservationEvent, SystemPromptEvent
1615
from openhands.core.llm import LLM, Message, TextContent, get_llm_metadata
@@ -35,11 +34,14 @@ def __init__(
3534
for tool in BUILT_IN_TOOLS:
3635
assert tool not in tools, f"{tool} is automatically included and should not be provided."
3736
super().__init__(llm=llm, tools=tools + BUILT_IN_TOOLS, env_context=env_context)
38-
self.prompt_manager = PromptManager(
39-
prompt_dir=os.path.join(os.path.dirname(__file__), "prompts"),
37+
38+
self.system_message: TextContent = TextContent(
39+
text=render_system_message(
40+
prompt_dir=self.prompt_dir,
4041
system_prompt_filename=system_prompt_filename,
41-
)
42-
self.system_message: TextContent = self.prompt_manager.get_system_message(cli_mode=cli_mode)
42+
cli_mode=cli_mode
43+
))
44+
4345
self.max_iterations: int = 10
4446

4547
def init_state(

openhands/core/context/__init__.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
RepoMicroagent,
1515
load_microagents_from_dir,
1616
)
17-
from .prompt import PromptManager
17+
from .utils import render_additional_info, render_initial_user_message, render_microagent_info, render_system_message
1818

1919

2020
__all__ = [
@@ -23,12 +23,15 @@
2323
"RuntimeInfo",
2424
"ConversationInstructions",
2525
"MessageContext",
26-
"PromptManager",
2726
"BaseMicroagent",
2827
"KnowledgeMicroagent",
2928
"RepoMicroagent",
3029
"MicroagentMetadata",
3130
"MicroagentType",
3231
"MicroagentKnowledge",
3332
"load_microagents_from_dir",
33+
"render_system_message",
34+
"render_initial_user_message",
35+
"render_additional_info",
36+
"render_microagent_info"
3437
]

openhands/core/context/env_context.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
1-
from typing import TYPE_CHECKING
21

32
from pydantic import BaseModel, Field
43

4+
from openhands.core.context.utils.prompt import render_microagent_info
55
from openhands.core.llm.message import TextContent
66

77
from .microagents import MicroagentKnowledge
8-
9-
10-
if TYPE_CHECKING:
11-
from .prompt import PromptManager
8+
from .utils import render_additional_info
129

1310

1411
class RuntimeInfo(BaseModel):
@@ -69,12 +66,13 @@ class EnvContext(BaseModel):
6966
description="List of microagents that have been activated based on the user's input",
7067
)
7168

72-
def render(self, prompt_manager: "PromptManager") -> list[TextContent]:
69+
def render(self, prompt_dir: str) -> list[TextContent]:
7370
"""Renders the environment context into a string using the provided PromptManager."""
7471
message_content = []
7572
# Build the workspace context information
7673
if self.repository_info or self.runtime_info or self.repository_instructions or self.conversation_instructions:
77-
formatted_workspace_text = prompt_manager.build_workspace_context(
74+
formatted_workspace_text = render_additional_info(
75+
prompt_dir=prompt_dir,
7876
repository_info=self.repository_info,
7977
runtime_info=self.runtime_info,
8078
conversation_instructions=self.conversation_instructions,
@@ -84,7 +82,8 @@ def render(self, prompt_manager: "PromptManager") -> list[TextContent]:
8482

8583
# Add microagent knowledge if present
8684
if self.activated_microagents:
87-
formatted_microagent_text = prompt_manager.build_microagent_info(
85+
formatted_microagent_text = render_microagent_info(
86+
prompt_dir=prompt_dir,
8887
triggered_agents=self.activated_microagents,
8988
)
9089
message_content.append(TextContent(text=formatted_microagent_text))

openhands/core/context/message_context.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from openhands.core.llm.message import Message, TextContent
44

55
from .microagents import MicroagentKnowledge
6-
from .prompt import PromptManager
6+
from .utils import render_microagent_info
77

88

99
class MessageContext(BaseModel):
@@ -17,9 +17,10 @@ class MessageContext(BaseModel):
1717
description="List of microagents that have been activated based on the user's input",
1818
)
1919

20-
def render(self, prompt_manager: PromptManager) -> list[Message]:
20+
def render(self, prompt_dir: str) -> list[Message]:
2121
"""Renders the environment context into a string using the provided PromptManager."""
22-
formatted_text = prompt_manager.build_microagent_info(
22+
formatted_text = render_microagent_info(
23+
prompt_dir=prompt_dir,
2324
triggered_agents=self.activated_microagents,
2425
)
2526
return [Message(role="user", content=[TextContent(text=formatted_text)])]

openhands/core/context/prompt.py

Lines changed: 0 additions & 110 deletions
This file was deleted.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from .prompt import (
2+
render_additional_info,
3+
render_initial_user_message,
4+
render_microagent_info,
5+
render_system_message,
6+
)
7+
8+
9+
__all__ = [
10+
"render_system_message",
11+
"render_initial_user_message",
12+
"render_additional_info",
13+
"render_microagent_info",
14+
]
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# prompt_utils.py
2+
import os
3+
import re
4+
import sys
5+
from functools import lru_cache
6+
7+
from jinja2 import Environment, FileSystemBytecodeCache, FileSystemLoader, Template
8+
9+
10+
def refine(text: str) -> str:
11+
if sys.platform == "win32":
12+
text = re.sub(r"\bexecute_bash\b", "execute_powershell", text, flags=re.IGNORECASE)
13+
text = re.sub(r"(?<!execute_)(?<!_)\bbash\b", "powershell", text, flags=re.IGNORECASE)
14+
return text
15+
16+
@lru_cache(maxsize=64)
17+
def _get_env(prompt_dir: str) -> Environment:
18+
if not prompt_dir:
19+
raise ValueError("prompt_dir is required")
20+
# BytecodeCache avoids reparsing templates across processes
21+
cache_folder = os.path.join(prompt_dir, ".jinja_cache")
22+
os.makedirs(cache_folder, exist_ok=True)
23+
bcc = FileSystemBytecodeCache(directory=cache_folder)
24+
env = Environment(
25+
loader=FileSystemLoader(prompt_dir),
26+
bytecode_cache=bcc,
27+
autoescape=False,
28+
)
29+
# Optional: expose refine as a filter so templates can use {{ text|refine }}
30+
env.filters["refine"] = refine
31+
return env
32+
33+
@lru_cache(maxsize=256)
34+
def _get_template(prompt_dir: str, template_name: str) -> Template:
35+
env = _get_env(prompt_dir)
36+
try:
37+
return env.get_template(template_name)
38+
except Exception:
39+
raise FileNotFoundError(f"Prompt file {os.path.join(prompt_dir, template_name)} not found")
40+
41+
def render_template(prompt_dir: str, template_name: str, **ctx) -> str:
42+
tpl = _get_template(prompt_dir, template_name)
43+
return refine(tpl.render(**ctx).strip())
44+
45+
# Convenience wrappers keeping old names/semantics
46+
def render_system_message(prompt_dir: str, system_prompt_filename: str = "system_prompt.j2", **ctx) -> str:
47+
return render_template(prompt_dir, system_prompt_filename, **ctx)
48+
49+
def render_initial_user_message(prompt_dir: str, **ctx) -> str:
50+
return render_template(prompt_dir, "user_prompt.j2", **ctx)
51+
52+
def render_additional_info(prompt_dir: str, **ctx) -> str:
53+
return render_template(prompt_dir, "additional_info.j2", **ctx)
54+
55+
def render_microagent_info(prompt_dir: str, **ctx) -> str:
56+
return render_template(prompt_dir, "microagent_info.j2", **ctx)

openhands/core/conversation/conversation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ def send_message(self, message: Message) -> None:
6262
# to *any* action execution runtime information
6363
if self.env_context:
6464
# TODO: the prompt manager here is a hack, will systematically fix it with LLMContextManager design
65-
initial_env_context: list[TextContent] = self.env_context.render(self.agent.prompt_manager) # type: ignore
65+
initial_env_context: list[TextContent] = self.env_context.render(self.agent.prompt_dir) # type: ignore
6666
message.content += initial_env_context
6767
if self.env_context.activated_microagents:
6868
activated_microagents = [microagent.name for microagent in self.env_context.activated_microagents]

0 commit comments

Comments
 (0)