Skip to content
79 changes: 49 additions & 30 deletions statek/agents/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import re
from typing import Any, List, Callable, Dict, Optional, Sequence, Union
import dbzero as db0
from statek.utils import block_comment, get_current_agent, _get_class_name
from statek.utils import block_comment, get_current_agent, get_current_job, _get_class_name
from statek.system import tool
from statek.docstring import parse_tool_docstring, format_docstring
from statek.utils import CodeBlock
Expand All @@ -29,6 +29,7 @@
)
from statek.executors.post_processor import effective_post_processing
from statek.provider_config import ProviderConfig
from statek.extra_resources import ExtraResources
from statek.prompt_config import (
SystemPrompt,
SystemPromptData,
Expand All @@ -50,9 +51,7 @@ def list_of_examples(start_index: int = 0, limit: int = 10, **kwargs): # pylint
limit: Maximum number of examples to show (default: 10).
"""
from statek.agents.list_of_examples import list_of_examples as _impl # pylint: disable=import-outside-toplevel
agent = get_current_agent()
agent_name = agent.role if agent else None
_impl(agent_name, start_index, limit)
_impl(_get_resource_agent_names(), start_index, limit)


@tool(system=True)
Expand All @@ -69,10 +68,11 @@ def list_of_documents(topic=None, start_index: int = 0, limit: int = 25, **kwarg
"""
from statek.agents.list_of_documents import list_of_documents as _impl # pylint: disable=import-outside-toplevel
from statek.settings import get_statek_settings # pylint: disable=import-outside-toplevel
agent = get_current_agent()
agent_name = agent.role if agent else None
documents_dir = get_statek_settings().documents_dir
_impl(agent_name, documents_dir, topic=topic, start_index=start_index, limit=limit)
_impl(
_get_resource_agent_names(), documents_dir,
topic=topic, start_index=start_index, limit=limit,
)


@tool(system=True)
Expand All @@ -89,10 +89,11 @@ def show_document(key, topic=None, start_from: int = 0, limit: int = 50, **kwarg
"""
from statek.agents.list_of_documents import show_document as _impl # pylint: disable=import-outside-toplevel
from statek.settings import get_statek_settings # pylint: disable=import-outside-toplevel
agent = get_current_agent()
agent_name = agent.role if agent else None
documents_dir = get_statek_settings().documents_dir
_impl(agent_name, documents_dir, key=key, topic=topic, start_from=start_from, limit=limit)
_impl(
_get_resource_agent_names(), documents_dir,
key=key, topic=topic, start_from=start_from, limit=limit,
)


@tool(system=True)
Expand All @@ -106,8 +107,18 @@ def show_example(example_id: Optional[int] = None, **kwargs): # pylint: disable
If not provided, uses default_example_id from the local context.
"""
from statek.agents.list_of_examples import show_example as _impl # pylint: disable=import-outside-toplevel
_impl(_get_resource_agent_names(), example_id)


def _get_resource_agent_names() -> List[str]:
"""Return current receiver and active donor roles for resource lookup."""
agent = get_current_agent()
_impl(agent.role if agent else None, example_id)
if agent is None:
return []
job = get_current_job()
if job is None:
return [agent.role]
return [resource_agent.role for resource_agent in job.get_resource_agents()]


@db0.memo
Expand Down Expand Up @@ -209,7 +220,9 @@ def update_description(self, new_description: str) -> bool:
self._description = new_description
return True

def _expand_tool_placeholders(self, text: str) -> Optional[str]:
def _expand_tool_placeholders(
self, text: str, available_tools: Optional[Sequence[Callable]] = None,
) -> Optional[str]:
"""Expand {tools}, {brief_tools}, {detailed_tools} placeholders in text.

Lines starting with '#' before a placeholder are embedded as a block comment.
Expand All @@ -224,17 +237,18 @@ def _expand_tool_placeholders(self, text: str) -> Optional[str]:
for name, brief, py_syntax in placeholders:
pattern = re.compile(rf'^(\s*#\s*)\{{{name}\}}', re.MULTILINE)
if pattern.search(text):
tools_str = self._format_tools(brief, py_syntax)
tools_str = self._format_tools(brief, py_syntax, available_tools)
text = pattern.sub(block_comment(tools_str), text)
elif f'{{{name}}}' in text:
tools_str = self._format_tools(brief, py_syntax)
tools_str = self._format_tools(brief, py_syntax, available_tools)
text = text.replace(f'{{{name}}}', tools_str)
return text

def system_prompt(
self,
task_difficulty: TaskDifficulty,
job_params: Dict = None,
available_tools: Optional[Sequence[Callable]] = None,
**kwargs,
) -> str:
"""
Expand Down Expand Up @@ -262,7 +276,9 @@ def system_prompt(
result = format_system_prompt(
self._system_prompt,
task_difficulty,
prompt_part_formatter=self._expand_tool_placeholders,
prompt_part_formatter=lambda text: self._expand_tool_placeholders(
text, available_tools,
),
)
format_ctx = {}
if job_params:
Expand All @@ -277,29 +293,23 @@ def system_prompt(
return result.format_map(format_ctx)
return result

def _format_tools(self, brief: bool, py_syntax: bool) -> str:
def _format_tools(
self, brief: bool, py_syntax: bool,
available_tools: Optional[Sequence[Callable]] = None,
) -> str:
"""Format all tools with the specified settings."""
agent_name = _get_class_name(self)
formatted = []
def inner_format_tool(fn: Callable) -> str:
parsed = parse_tool_docstring(fn)
return format_docstring(parsed, brief=brief, py_syntax=py_syntax,
agent=agent_name)

tools = self.tools if available_tools is None else available_tools
formatted = [
inner_format_tool(fn)
for fn in self._tools
for fn in tools
if not getattr(fn, "tool_hidden", False)
]
# also process tools specified by name
if self._tools_by_name:
for tool_name in self._tools_by_name:
fn = self.context.get(tool_name)
if fn is None:
raise ValueError(f'Missing tool defined by name "{tool_name}"')
if getattr(fn, "tool_hidden", False):
continue
formatted.append(inner_format_tool(fn))

return '\n\n'.join(formatted)

Expand Down Expand Up @@ -327,14 +337,20 @@ def get_adapter(self, name: str) -> Optional[Any]:
return None

@property
def all_tools(self) -> List[Callable]:
"""Return all tools assigned to this agent (both regular and internal)."""
def tools(self) -> List[Callable]:
"""Return the agent's LLM-visible direct and name-registered tools."""
result = list(self._tools)
if self._tools_by_name:
for tool_name in self._tools_by_name:
fn = self.context.get(tool_name)
if fn is not None:
result.append(fn)
return result

@property
def all_tools(self) -> List[Callable]:
"""Return all tools assigned to this agent (both regular and internal)."""
result = self.tools
if self._internal_tools:
result.extend(self._internal_tools)
if self._internal_tools_by_name:
Expand Down Expand Up @@ -486,14 +502,15 @@ def update_warmup_def(self, unparsed_warmup_def: Optional[str]) -> bool:
self._X__ref_locals = None
return True

def create_job_def(
def create_job_def( # pylint: disable=too-many-arguments,too-many-positional-arguments
self,
tools: Optional[List[Callable]] = None,
warmup_code: WarmupCodeInput = None,
shared_vars: Optional[Dict[str, Any]] = None,
locale=None,
post_processing=None,
provider_config: Optional[ProviderConfig] = None,
extra_resources: ExtraResources | None = None,
**kwargs
) -> JobDef:
# pylint: disable=unused-argument
Expand All @@ -510,6 +527,7 @@ def create_job_def(
locale: optional locale for job execution
post_processing: optional resolved post-processor or sequence of
post-processors for this job definition
extra_resources: parsed prompt resources transported by internal callers
kwargs: job specific parameters for prompt formatting (i.e. job_params)

Returns:
Expand Down Expand Up @@ -541,6 +559,7 @@ def create_job_def(
locale=locale,
post_processing=resolved_post_processing,
provider_config=provider_config,
extra_resources=extra_resources,
)

def _combine_warmup_code(self, warmup_code):
Expand Down
4 changes: 4 additions & 0 deletions statek/agents/dialog_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import dbzero as db0
from statek.agents.agent import SupervisedAgent
from statek.chat_style import ChatStyle
from statek.extra_resources import ExtraResources

if TYPE_CHECKING:
from statek.executors.job import Job
Expand Down Expand Up @@ -238,6 +239,7 @@ def create_job_def( # pylint: disable=no-member,arguments-renamed,too-many-argu
locale=None,
post_processing=None,
provider_config=None,
extra_resources: ExtraResources | None = None,
**kwargs
):
"""Create a JobDef with a dialog chat style.
Expand All @@ -254,6 +256,7 @@ def create_job_def( # pylint: disable=no-member,arguments-renamed,too-many-argu
chat_style: chat style override; if omitted, falls back to prompt metadata
locale: optional locale for job execution
post_processing: optional resolved post-processor or sequence of post-processors
extra_resources: parsed prompt resources forwarded to the parent
**kwargs: job-specific parameters forwarded to the parent
"""
if chat_style is None and self._metadata and 'CHAT_STYLE' in self._metadata:
Expand All @@ -265,6 +268,7 @@ def create_job_def( # pylint: disable=no-member,arguments-renamed,too-many-argu
locale=locale,
post_processing=post_processing,
provider_config=provider_config,
extra_resources=extra_resources,
**kwargs
)
job_def.set_chat_style(chat_style or ChatStyle.MD_DIALOG) # pylint: disable=no-member
Expand Down
12 changes: 8 additions & 4 deletions statek/agents/list_of_documents.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,13 @@

# pylint: disable=unused-argument

from typing import Optional, Union
from typing import Optional, Sequence, Union

from statek.document import load_documents, find_topic, find_document
from statek.document import (
find_document,
find_topic,
load_documents,
)
from statek.settings import get_statek_settings
from statek.system import tool
from statek.utils import perm_ctx_set, perm_ctx_get
Expand All @@ -36,7 +40,7 @@ def _get_documents_dir() -> Optional[str]:


@tool(system=True)
def list_of_documents(agent_name: str, documents_dir: str,
def list_of_documents(agent_name: str | Sequence[str], documents_dir: str,
topic: Optional[Union[int, str]] = None,
start_index: int = 0, limit: int = 25, **kwargs): # pylint: disable=unused-argument
"""Lists available topics or documents within a topic.
Expand Down Expand Up @@ -111,7 +115,7 @@ def _resolve_topic(topic_key, agent_name, all_topics):


@tool(system=True)
def show_document(agent_name: str, documents_dir: str, # pylint: disable=too-many-positional-arguments,too-many-arguments
def show_document(agent_name: str | Sequence[str], documents_dir: str, # pylint: disable=too-many-positional-arguments,too-many-arguments
key: Union[int, str], topic: Optional[Union[int, str]] = None,
start_from: int = 0, limit: int = 50, **kwargs):
"""Shows the contents of a specific document.
Expand Down
Loading
Loading