Skip to content

Commit b0b19e7

Browse files
authored
Merge pull request #1113 from writer/fix-AB-272-writer-tool-calling-issues
fix: improve WriterToolCalling block error handling and AI behavior
2 parents d71ecf6 + 3b84c66 commit b0b19e7

File tree

1 file changed

+60
-44
lines changed

1 file changed

+60
-44
lines changed

src/writer/blocks/writertoolcalling.py

Lines changed: 60 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1+
import textwrap
2+
from datetime import date
3+
14
from writer.abstract import register_abstract_template
25
from writer.blocks.base_block import WriterBlock
36
from writer.ss_types import AbstractTemplate
47

58
DEFAULT_MODEL = "palmyra-x5"
69

10+
711
class WriterToolCalling(WriterBlock):
812
@classmethod
913
def register(cls, type: str):
@@ -17,16 +21,17 @@ def register(cls, type: str):
1721
"description": "Connects the Agent to external tools to complete tasks it cannot handle directly.",
1822
"category": "Writer",
1923
"fields": {
20-
"prompt": {"name": "Prompt", "type": "Text", "control": "Textarea", "desc": "The task that needs to be carried out."},
21-
"modelId": {
22-
"name": "Model",
23-
"type": "Model Id",
24-
"default": DEFAULT_MODEL
24+
"prompt": {
25+
"name": "Prompt",
26+
"type": "Text",
27+
"control": "Textarea",
28+
"desc": "The task that needs to be carried out.",
2529
},
30+
"modelId": {"name": "Model", "type": "Model Id", "default": DEFAULT_MODEL},
2631
"maxIterations": {
2732
"name": "Max iterations",
2833
"type": "Number",
29-
"default": 10
34+
"default": 10,
3035
},
3136
"tools": {
3237
"name": "Tools",
@@ -63,27 +68,35 @@ def _make_callable(self, tool_name: str):
6368

6469
def callable(**args):
6570
expanded_execution_environment = self.execution_environment | args
66-
return_value = repr(
67-
self.runner.run_branch(
68-
self.component.id,
69-
f"tools_{tool_name}",
70-
expanded_execution_environment,
71-
f"Blueprint branch execution (tool {tool_name})",
72-
)
71+
raw_return_value = self.runner.run_branch(
72+
self.component.id,
73+
f"tools_{tool_name}",
74+
expanded_execution_environment,
75+
f"Blueprint branch execution (tool {tool_name})",
7376
)
74-
if return_value is None:
77+
78+
if raw_return_value is None:
7579
self.outcome = "error"
7680
raise ValueError(
7781
f'No value has been returned for the outcome branch "{tool_name}". Use the block "Return value" to specify one.'
7882
)
79-
self.execution_environment.get("trace").append(
80-
{
81-
"type": "functionCall",
82-
"time": time.time(),
83-
"name": tool_name,
84-
"parameters": args,
85-
}
86-
)
83+
84+
transformed_result = self._project_common_tools_result(raw_return_value)
85+
if transformed_result is None:
86+
# Fallback avoids returning the literal string "None"
87+
transformed_result = raw_return_value
88+
return_value = repr(transformed_result)
89+
90+
trace = self.execution_environment.get("trace")
91+
if trace is not None:
92+
trace.append(
93+
{
94+
"type": "functionCall",
95+
"time": time.time(),
96+
"name": tool_name,
97+
"parameters": args,
98+
}
99+
)
87100
return return_value
88101

89102
return callable
@@ -123,15 +136,13 @@ def reasoning_callable(**kwargs):
123136
action = kwargs.get("action")
124137
status = kwargs.get("status")
125138

126-
self.execution_environment.get("trace").append({
127-
"type": "reasoning",
128-
"time": time.time(),
129-
"thought": thought,
130-
"action": action
131-
})
139+
trace = self.execution_environment.get("trace")
140+
if trace is not None:
141+
trace.append(
142+
{"type": "reasoning", "time": time.time(), "thought": thought, "action": action}
143+
)
132144
if status == "DONE":
133145
self.is_complete = True
134-
135146

136147
reasoning_tool = {
137148
"type": "function",
@@ -149,8 +160,8 @@ def reasoning_callable(**kwargs):
149160
},
150161
"status": {
151162
"type": "string",
152-
"description": "Set to DONE if you consider the task complete. Set to INCOMPLETE if you wish to keep iterating."
153-
}
163+
"description": "Set to DONE if you consider the task complete. Set to INCOMPLETE if you wish to keep iterating.",
164+
},
154165
},
155166
}
156167

@@ -159,31 +170,36 @@ def reasoning_callable(**kwargs):
159170
return tools
160171

161172
def _get_react_prompt(self, base_prompt: str):
162-
return f"""
163-
You're a ReAct agent.
164-
Disclose your reasoning using the provided function.
165-
166-
Task: {base_prompt}
167-
"""
168-
173+
return textwrap.dedent(f"""
174+
You're a ReAct agent. Your knowledge cut-off date is 2024, but today is {str(date.today())}.
175+
Disclose your reasoning using "disclose_reasoning" function.
176+
177+
Task: {base_prompt.strip()}
178+
""").strip()
179+
180+
def _project_common_tools_result(self, tool_result):
181+
# Handle common HTTP-like shapes safely; pass through other types.
182+
if isinstance(tool_result, dict):
183+
if tool_result.get("request") and "body" in tool_result:
184+
return tool_result["body"]
185+
return tool_result
169186

170187
def run(self):
171188
import writer.ai
189+
172190
self.is_complete = False
173191

174192
try:
175193
prompt = self._get_field("prompt")
176194
model_id = self._get_field("modelId", False, default_field_value=DEFAULT_MODEL)
177-
max_iterations = int(self._get_field("maxIterations", False, "10"))
195+
max_iterations = max(1, int(self._get_field("maxIterations", False, "10")))
178196
conversation = writer.ai.Conversation()
179197
tools = self._get_tools()
180198

199+
conversation += {"role": "user", "content": self._get_react_prompt(prompt)}
200+
181201
for i in range(max_iterations):
182-
conversation += {
183-
"role": "user",
184-
"content": self._get_react_prompt(prompt)
185-
}
186-
config = {"model": model_id}
202+
config = {"model": model_id, "temperature": 0.1}
187203
msg = conversation.complete(tools=tools, config=config)
188204
conversation += msg
189205
if self.is_complete:

0 commit comments

Comments
 (0)