Skip to content

Commit 2a49d7d

Browse files
Add final_response_tool for direct agent replies
Tools (functions) marked with @final_response_tool bypass LLM post-processing and return their output as the agent's final response. This is useful for pre-formatted outputs such as large markdown tables, where regenerating the content token-by-token through the LLM is unnecessary. Example: from sdialog.agents import Agent, final_response_tool @final_response_tool def get_table() -> str: return "| col |\n|---|\n| val |" agent = Agent(tools=[get_table])
1 parent e1129b9 commit 2a49d7d

File tree

2 files changed

+44
-1
lines changed

2 files changed

+44
-1
lines changed

src/sdialog/agents.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,35 @@
3434
logger = logging.getLogger(__name__)
3535

3636

37+
def final_response_tool(func=None):
38+
"""
39+
Decorator to mark a tool whose raw output should be returned directly as the
40+
agent response (bypassing the post-tool LLM synthesis step).
41+
42+
This is useful for pre-formatted outputs (e.g., large markdown tables)
43+
where token-by-token regeneration by the LLM is unnecessary.
44+
45+
Usage:
46+
47+
.. code-block:: python
48+
49+
from sdialog.agents import final_response_tool
50+
51+
@final_response_tool
52+
def my_tool(...) -> str:
53+
...
54+
55+
:param func: The tool function to mark.
56+
:type func: Optional[callable]
57+
:return: Decorated function.
58+
:rtype: callable
59+
"""
60+
if func is None:
61+
return final_response_tool
62+
setattr(func, "_sdialog_final_response_tool", True)
63+
return func
64+
65+
3766
class Agent:
3867
"""
3968
Agent that simulates a persona-driven conversational actor using an LLM.
@@ -88,6 +117,8 @@ class Agent:
88117
:type example_dialogs: Optional[List[Dialog]]
89118
:param tools: List of functions to be used as tools by the agent (if supported by the LLM).
90119
:type tools: Optional[List[callable]]
120+
Tools decorated with ``@final_response_tool`` return their
121+
raw output directly as the final agent response.
91122
:param think: If True, enables "thinking" segments in responses (if supported by the LLM).
92123
:type think: bool
93124
:param thinking_pattern: Regex pattern to manually identify "thinking" segments in responses.
@@ -161,6 +192,10 @@ def __init__(self,
161192
# Private attributes
162193
self._system_prompt_template = system_prompt_template
163194
self._thinking_pattern = thinking_pattern
195+
self._final_response_tools = {
196+
fn.__name__ for fn in tools
197+
if getattr(fn, "_sdialog_final_response_tool", False)
198+
} if tools else set()
164199
self._tools = {fn.__name__: tool(fn) for fn in tools} if tools else None
165200
self._model_uri = model
166201
self._context = context
@@ -479,6 +514,14 @@ def _get_llm_response(self, messages, update_tool_memory: bool = False) -> Tuple
479514
"output": tool_msg.content,
480515
"call_id": tool_msg.tool_call_id},
481516
timestamp=int(time())))
517+
518+
# For tools explicitly marked as direct-response tools,
519+
# bypass the post-tool LLM step only if output is non-empty.
520+
# Empty outputs should be handled as regular tools.
521+
if tool_call["name"] in self._final_response_tools:
522+
output = tool_msg.content.strip() if isinstance(tool_msg.content, str) else tool_msg.content
523+
if output:
524+
return AIMessage(content=str(output)), events
482525
else:
483526
logger.warning(f"Tool '{tool_call['name']}' not found among bound tools.")
484527

src/sdialog/server.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -831,7 +831,7 @@ def _send_chunk(content: str = None, thinking: str = None,
831831
# Stream while preserving original whitespace separators (spaces, newlines, tabs)
832832
for match in re.finditer(r'\S+\s*', content):
833833
yield _send_chunk(content=match.group(0))
834-
time.sleep(0.01) # Adjust delay as needed
834+
time.sleep(0.001) # Adjust delay as needed
835835

836836
# Send final chunk
837837
yield _send_chunk(content="", done=True)

0 commit comments

Comments
 (0)