diff --git a/dspy_agent/Dockerfile b/dspy_agent/Dockerfile
index fa39d49..ee8ea06 100644
--- a/dspy_agent/Dockerfile
+++ b/dspy_agent/Dockerfile
@@ -1,4 +1,4 @@
-FROM python:3.10-slim
+FROM python:3.11-slim
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
@@ -32,4 +32,6 @@ RUN pip install --no-cache-dir -r requirements_nicegui_dspy.txt
COPY . .
+COPY dspy_agent/ .
+
EXPOSE 9090
diff --git a/dspy_agent/chat_dspy.py b/dspy_agent/chat_dspy.py
index f8d99e8..d3fafc8 100644
--- a/dspy_agent/chat_dspy.py
+++ b/dspy_agent/chat_dspy.py
@@ -122,22 +122,6 @@
"anndata", "Bio", "vcf", "statsmodels", "plotly", "itertools", "collections", "json",
]
-class FinishTool(dspy.Tool):
- """A dummy tool that signals the end of the interaction and provides the final answer."""
- name = "finish"
- input_variable = "final_answer"
- output_variable = "text"
- description = "Use this action to end the interaction and provide the final answer."
-
- def __init__(self):
- # This line registers the __call__ method as the function to run for this tool.
- super().__init__(func=self.__call__)
-
- def __call__(self, final_answer: str) -> str:
- # This tool doesn't need to do anything. Its only job is
- # to exist and have the correct signature for validation.
- return "Final answer received."
-
class PythonCodeTool(dspy.Tool):
name = "python_code_executor"
input_variable = "code"
@@ -295,11 +279,11 @@ class DataAnalysisSignature(dspy.Signature):
**IMPORTANT: To prevent file conflicts, all generated file and plot names MUST end with a unique suffix (e.g., a short random string or number). For example, save 'plot.png' as 'plot_a8d3.png'.**
When you have gathered all the necessary information and are ready to provide the final answer,
- you MUST use the special 'finish' action. The 'finish' action requires a single argument: 'final_answer'.
- The value for 'answer' MUST be a single, valid JSON string.
+ you MUST use the special 'finish' action.
+ The 'finish' action takes NO arguments.
Here is a literal example of the final step:
- Thought: I have collected all the results and I am ready to provide the final answer.
- Action: finish(answer='{"explanation": "The analysis is complete.", "plots": ["generated_files/plot1.png"], "files": ["generated_files/data.csv"], "next_steps_suggestion": ["Consider further analysis."]}')
+ Thought: I have collected all the results and I am ready to provide the final answer. 'answer={"explanation": "The analysis is complete.", "plots": ["generated_files/plot1.png"], "files": ["generated_files/data.csv"], "next_steps_suggestion": ["Consider further analysis."]}'
+ Action: finish()
Finally, provide a comprehensive answer to the user in JSON format. This JSON MUST include:
- "explanation": A textual explanation of what was done and the insights.
@@ -309,7 +293,7 @@ class DataAnalysisSignature(dspy.Signature):
"""
context = dspy.InputField(desc="Provides context: conversation history, current dataset path, dataset type, and output directory information.")
question = dspy.InputField(desc="The user's question or data analysis task.")
- final_answer = dspy.OutputField(desc=f"A JSON string with 'explanation', 'plots' (list of strings relative to '{AGENT_GENERATED_FILES_SUBDIR.name}/'), 'files' (list of strings relative to '{AGENT_GENERATED_FILES_SUBDIR.name}/'), and 'next_steps_suggestion' (a list of 2-3 relevant follow-up questions or analysis tasks).") # Ensure AGENT_GENERATED_FILES_SUBDIR is globally defined
+ answer = dspy.OutputField(desc=f"A JSON string with 'explanation', 'plots' (list of strings relative to '{AGENT_GENERATED_FILES_SUBDIR.name}/'), 'files' (list of strings relative to '{AGENT_GENERATED_FILES_SUBDIR.name}/'), and 'next_steps_suggestion' (a list of 2-3 relevant follow-up questions or analysis tasks).") # Ensure AGENT_GENERATED_FILES_SUBDIR is globally defined
class DataAnalysisAgentModule(dspy.Module): # Renamed to avoid conflict with smolagents.CodeAgent if it was an object
"""The main DSPy agent module for data analysis, using ReAct."""
@@ -317,8 +301,7 @@ def __init__(self, outputs_dir: Path, current_dataset_path: Path | None, max_ite
super().__init__()
self.react_agent = dspy.ReAct(
DataAnalysisSignature,
- tools=[PythonCodeTool(outputs_dir=outputs_dir, current_dataset_path=current_dataset_path),
- FinishTool()],
+ tools=[PythonCodeTool(outputs_dir=outputs_dir, current_dataset_path=current_dataset_path)],
max_iters=max_iters
)
@@ -1124,12 +1107,20 @@ def format_raw_middle_steps_for_display(self, trajectory_data) -> str:
else: # Indexed steps
for i in range(max_idx + 1):
+
+ is_last_step = (i == max_idx)
+ observation_for_check = str(trajectory_data.get(f'observation{i}') or trajectory_data.get(f'tool_output{i}', ''))
+ is_failed_step = "is not in the tool's args" in observation_for_check or 'Execution error in finish' in observation_for_check
+
current_step_md_parts = [f"\n##### Step {i + 1}"]
thought_content = trajectory_data.get(f'thought_{i}') or trajectory_data.get(f'rationale_{i}')
action_name = trajectory_data.get(f'tool_name_{i}') or trajectory_data.get(f'action_{i}')
action_input_dict = trajectory_data.get(f'tool_args_{i}') or trajectory_data.get(f'action_input_{i}')
observation = trajectory_data.get(f'observation_{i}') or trajectory_data.get(f'tool_output_{i}')
+ if is_last_step and is_failed_step:
+ observation = None
+
if thought_content:
current_step_md_parts.append(f"**Thought:**\n```text\n{str(thought_content).strip()}\n```")
diff --git a/tools/chat_analysis.xml b/tools/chat_analysis.xml
index 8cf64d6..0e18f02 100644
--- a/tools/chat_analysis.xml
+++ b/tools/chat_analysis.xml
@@ -60,7 +60,7 @@
-
+