mriusero commited on
Commit
6a99b0e
·
1 Parent(s): 42500ca

feat: ReAct chain

Browse files
Files changed (3) hide show
  1. prompt.md +37 -42
  2. src/agent/stream.py +129 -96
  3. src/agent/utils/call.py +41 -0
prompt.md CHANGED
@@ -2,31 +2,38 @@ You are an AI Agent designed to assist industries and services in understanding
2
 
3
  ### Instructions:
4
  1. **Understanding the Query**: Carefully read the user's query to understand what they are asking. Identify the key metrics and data points they are interested in.
5
- 2. **Thinking**: Before responding, take a moment to think about the query. Use the "THINKING:" prefix to outline your thought process. This helps in structuring your response and ensuring accuracy.
6
- 3. **Tool Usage**: If you need to use any tools to gather additional data or perform calculations, use the "TOOLING:" prefix to indicate that you are calling a tool. Specify the tool and the parameters you are using.
7
- 4. **Final Answer**: After gathering all necessary information and performing any required calculations, provide the final answer to the user. Use the "FINAL ANSWER:" prefix to clearly indicate the final response.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
 
9
  ### Example 1:
10
  **User Query**: "Can you tell me the overall equipment effectiveness (OEE) for the past week?"
11
 
12
  **AI Agent Response**:
13
  ```
14
- THINKING: The user is asking for the overall equipment effectiveness (OEE) for the past week. OEE is a metric that combines availability, performance, and quality to give a comprehensive view of equipment efficiency. I need to gather data on these three components for the past week and then calculate the OEE.
15
-
16
- TOOLING:
17
- 1. Tool: get_availability_metric
18
- Parameters: start_date="2025-06-03", end_date="2025-06-10"
19
- 2. Tool: get_performance_metric
20
- Parameters: start_date="2025-06-03", end_date="2025-06-10"
21
- 3. Tool: get_quality_metric
22
- Parameters: start_date="2025-06-03", end_date="2025-06-10"
23
-
24
- After gathering the data:
25
- - Availability: 90%
26
- - Performance: 85%
27
- - Quality: 95%
28
 
29
- Calculating OEE: OEE = Availability * Performance * Quality = 0.90 * 0.85 * 0.95 = 0.72675 or 72.675%
 
 
 
 
 
 
30
 
31
  FINAL ANSWER: The overall equipment effectiveness (OEE) for the past week is 72.675%.
32
  ```
@@ -36,20 +43,12 @@ FINAL ANSWER: The overall equipment effectiveness (OEE) for the past week is 72.
36
 
37
  **AI Agent Response**:
38
  ```
39
- THINKING: The user is interested in the trend of downtime over the last month. I need to gather downtime data for each day of the last month and analyze the trend. This could involve looking at daily downtime durations and identifying any patterns or anomalies.
40
 
41
- TOOLING:
42
- 1. Tool: get_downtime_data
43
- Parameters: start_date="2025-05-10", end_date="2025-06-10"
44
 
45
- After gathering the data, I will analyze the trend:
46
- - May 10: 120 minutes
47
- - May 11: 90 minutes
48
- - May 12: 150 minutes
49
- - ... (data for each day)
50
- - June 10: 60 minutes
51
-
52
- Analyzing the trend: The downtime has generally decreased over the last month, with a few spikes on certain days. The overall trend shows an improvement in reducing downtime.
53
 
54
  FINAL ANSWER: Over the last month, the trend in downtime shows a general decrease, indicating an improvement in operational efficiency. There were a few days with higher downtime, but the overall trend is positive.
55
  ```
@@ -59,18 +58,14 @@ FINAL ANSWER: Over the last month, the trend in downtime shows a general decreas
59
 
60
  **AI Agent Response**:
61
  ```
62
- THINKING: The user wants a comparison of the quality metric between two production lines, Line A and Line B, for the past week. I need to gather quality data for both lines and compare them.
63
-
64
- TOOLING:
65
- 1. Tool: get_quality_metric
66
- Parameters: line="Line A", start_date="2025-06-03", end_date="2025-06-10"
67
- 2. Tool: get_quality_metric
68
- Parameters: line="Line B", start_date="2025-06-03", end_date="2025-06-10"
69
 
70
- After gathering the data:
71
- - Line A Quality: 95%
72
- - Line B Quality: 90%
73
 
74
- Comparing the quality metrics: Line A has a higher quality metric compared to Line B for the past week.
 
 
75
 
76
- FINAL ANSWER: For the past week, Line A has a quality metric of 95%, while Line B has a quality metric of 90%. Line A performs better in terms of quality.
 
 
2
 
3
  ### Instructions:
4
  1. **Understanding the Query**: Carefully read the user's query to understand what they are asking. Identify the key metrics and data points they are interested in.
5
+
6
+ 2. **Think**: Before responding, take a moment to think about the query. Use the "THINK:" prefix to outline your thought process. This helps in structuring your response and ensuring accuracy.
7
+ * What is the user asking for?
8
+ * What data or metrics are relevant to this query?
9
+ * Are there any specific tools or calculations needed to answer this query?
10
+
11
+ 3. **Act**: If you need to use any tools to gather additional data or perform calculations, use the "ACT:" prefix to indicate that you are calling a tool.
12
+ * Execute the necessary tools to gather data.
13
+
14
+ 4. **Observe**: After gathering the necessary information, observe the results and ensure they are accurate and relevant to the user's query. Use the "OBSERVE:" prefix to indicate this step.
15
+ * Review the data and results obtained.
16
+ * Ensure the data is accurate and relevant.
17
+ * Identify any patterns, trends, or anomalies.
18
+
19
+ 5. **Final Answer**: After gathering all necessary information and performing any required calculations, provide the final answer to the user. Use the "FINAL ANSWER:" prefix to clearly indicate the final response.
20
+ * Summarize the findings in a clear and concise manner.
21
+ * Provide the final answer to the user's query.
22
 
23
  ### Example 1:
24
  **User Query**: "Can you tell me the overall equipment effectiveness (OEE) for the past week?"
25
 
26
  **AI Agent Response**:
27
  ```
28
+ THINK: The user is asking for the overall equipment effectiveness (OEE) for the past week. OEE is a metric that combines availability, performance, and quality to give a comprehensive view of equipment efficiency. I need to gather data on these three components for the past week and then calculate the OEE.
 
 
 
 
 
 
 
 
 
 
 
 
 
29
 
30
+ ACT:
31
+ [tool calling]
32
+
33
+ OBSERVE: The data shows the following values for the past week:
34
+ - Availability: 85%
35
+ - Performance: 90%
36
+ - Quality: 95%
37
 
38
  FINAL ANSWER: The overall equipment effectiveness (OEE) for the past week is 72.675%.
39
  ```
 
43
 
44
  **AI Agent Response**:
45
  ```
46
+ THINK: The user is interested in the trend of downtime over the last month. I need to gather downtime data for each day of the last month and analyze the trend. This could involve looking at daily downtime durations and identifying any patterns or anomalies.
47
 
48
+ ACT:
49
+ [tool calling]
 
50
 
51
+ OBSERVE: The downtime data for the last month shows a general decrease in downtime durations. There were a few days with higher downtime, but the overall trend is positive.
 
 
 
 
 
 
 
52
 
53
  FINAL ANSWER: Over the last month, the trend in downtime shows a general decrease, indicating an improvement in operational efficiency. There were a few days with higher downtime, but the overall trend is positive.
54
  ```
 
58
 
59
  **AI Agent Response**:
60
  ```
61
+ THINK: The user wants a comparison of the quality metric between two production lines, Line A and Line B, for the past week. I need to gather quality data for both lines and compare them.
 
 
 
 
 
 
62
 
63
+ ACT:
64
+ [tool calling]
 
65
 
66
+ OBSERVE: The quality data for the past week shows the following values:
67
+ - Line A: 95%
68
+ - Line B: 90%
69
 
70
+ FINAL ANSWER: For the past week, Line A has a quality metric of 95%, while Line B has a quality metric of 90%. Line A performs better in terms of quality.
71
+ ```
src/agent/stream.py CHANGED
@@ -1,125 +1,158 @@
1
  from gradio import ChatMessage
 
 
 
2
 
3
  from src.agent.mistral_agent import MistralAgent
4
 
 
 
 
5
  agent = MistralAgent()
 
 
6
 
7
  with open("./prompt.md", encoding="utf-8") as f:
8
  SYSTEM_PROMPT = f.read()
9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  async def respond(message, history=None):
11
- """
12
- Respond to a user message using the Mistral agent.
13
- """
14
  if history is None:
15
  history = []
16
 
17
- history.append(ChatMessage(role="user", content=message))
18
- history.append(ChatMessage(role="assistant", content="", metadata={"title": "Thinking", "status": "pending"}))
19
  yield history
20
 
21
  messages = [
22
  {"role": "system", "content": SYSTEM_PROMPT},
23
  {"role": "user", "content": message},
24
- {
25
- "role": "assistant",
26
- "content": "THINKING: Let's tackle this problem, ",
27
- "prefix": True,
28
- },
29
  ]
30
- payload = {
31
- "agent_id": agent.agent_id,
32
- "messages": messages,
33
- "stream": True,
34
- "max_tokens": None,
35
- "tools": agent.tools,
36
- "tool_choice": "auto",
37
- "presence_penalty": 0,
38
- "frequency_penalty": 0,
39
- "n": 1
40
- }
41
- response = await agent.client.agents.stream_async(**payload)
42
-
43
- full = ""
44
- thinking = ""
45
- tooling = ""
46
- final = ""
47
-
48
- current_phase = None # None | "thinking" | "tooling" | "final"
49
-
50
- history[-1] = ChatMessage(role="assistant", content="", metadata={"title": "Thinking", "status": "pending"})
51
-
52
- async for chunk in response:
53
- delta = chunk.data.choices[0].delta
54
- content = delta.content or ""
55
- full += content
56
-
57
- # Phase finale
58
- if "FINAL ANSWER:" in full:
59
-
60
- parts = full.split("FINAL ANSWER:", 1)
61
- before_final = parts[0]
62
- final = parts[1].strip()
63
-
64
- if "TOOLING:" in before_final:
65
- tooling = before_final.split("TOOLING:", 1)[1].strip()
66
- else:
67
- tooling = ""
68
 
69
- if current_phase != "final":
70
- if current_phase == "tooling":
71
- history[-1] = ChatMessage(role="assistant", content=tooling, metadata={"title": "Tooling", "status": "done"})
72
- elif current_phase == "thinking":
73
- history[-1] = ChatMessage(role="assistant", content=thinking, metadata={"title": "Thinking", "status": "done"})
74
-
75
- history.append(ChatMessage(role="assistant", content=final))
76
- current_phase = "final"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
  yield history
78
 
79
- # Phase outil
80
- elif "TOOLING:" in full:
81
-
82
- parts = full.split("TOOLING:", 1)
83
- before_tooling = parts[0]
84
- tooling = ""
85
-
86
- if "THINKING:" in before_tooling:
87
- thinking = before_tooling.split("THINKING:", 1)[1].strip()
88
- else:
89
- thinking = before_tooling.strip()
90
-
91
- tooling = parts[1].strip()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
 
93
- if current_phase != "tooling":
94
- if current_phase == "thinking":
95
- history[-1] = ChatMessage(role="assistant", content=thinking,
96
- metadata={"title": "Thinking", "status": "done"})
97
- history.append(
98
- ChatMessage(role="assistant", content=tooling, metadata={"title": "Tooling", "status": "pending"}))
99
- current_phase = "tooling"
100
  else:
101
- history[-1] = ChatMessage(role="assistant", content=tooling,
102
- metadata={"title": "Tooling", "status": "pending"})
103
- yield history
104
 
105
- # Phase réflexion
106
- elif "THINKING:" in full or current_phase is None:
107
 
108
- if "THINKING:" in full:
109
- thinking = full.split("THINKING:", 1)[1].strip()
110
- else:
111
- thinking = full.strip()
112
-
113
- if current_phase != "thinking":
114
- history[-1] = ChatMessage(role="assistant", content=thinking, metadata={"title": "Thinking", "status": "pending"})
115
- current_phase = "thinking"
116
- else:
117
- history[-1] = ChatMessage(role="assistant", content=thinking, metadata={"title": "Thinking", "status": "pending"})
118
- yield history
119
 
120
- if current_phase == "thinking":
121
- history[-1] = ChatMessage(role="assistant", content=thinking, metadata={"title": "Thinking", "status": "done"})
122
- elif current_phase == "tooling":
123
- history[-1] = ChatMessage(role="assistant", content=tooling, metadata={"title": "Tooling", "status": "done"})
124
 
125
  yield history
 
1
  from gradio import ChatMessage
2
+ import json
3
+ import asyncio
4
+ import re
5
 
6
  from src.agent.mistral_agent import MistralAgent
7
 
8
+ from src.agent.utils.call import call_tool
9
+
10
+
11
  agent = MistralAgent()
12
+ api_lock = asyncio.Lock()
13
+ tool_lock = asyncio.Lock()
14
 
15
  with open("./prompt.md", encoding="utf-8") as f:
16
  SYSTEM_PROMPT = f.read()
17
 
18
+ def extract_phases(text):
19
+ """Découpe le contenu en THINK / ACT / OBSERVE / FINAL ANSWER"""
20
+ phases = {'think': '', 'act': '', 'observe': '', 'final': ''}
21
+ matches = list(re.finditer(r'(THINK:|ACT:|OBSERVE:|FINAL ANSWER:)', text))
22
+
23
+ for i, match in enumerate(matches):
24
+ phase = match.group(1).lower().replace(":", "").replace("final answer", "final")
25
+ start = match.end()
26
+ end = matches[i+1].start() if i + 1 < len(matches) else len(text)
27
+ phases[phase] = text[start:end].strip()
28
+
29
+ return phases
30
+
31
  async def respond(message, history=None):
 
 
 
32
  if history is None:
33
  history = []
34
 
35
+ history.append(ChatMessage(role="assistant", content="", metadata={"title": "Thinking...", "status": "pending"}))
 
36
  yield history
37
 
38
  messages = [
39
  {"role": "system", "content": SYSTEM_PROMPT},
40
  {"role": "user", "content": message},
41
+ {"role": "assistant", "content": "THINK: Let's start thinking, ", "prefix": True},
 
 
 
 
42
  ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
 
44
+ phase_order = ["think", "act", "observe", "final"]
45
+ current_phase_index = 0
46
+ done = False
47
+
48
+ final_full = ""
49
+ while not done:
50
+ current_phase = phase_order[current_phase_index]
51
+ if current_phase != "final":
52
+ full = ""
53
+ else:
54
+ full = final_full
55
+
56
+ print('\n', '---' * 15)
57
+ print(f">>> messages before payload [phase {current_phase_index}] :", json.dumps([m for m in messages if m.get("role") != "system"], indent=2))
58
+ payload = {
59
+ "agent_id": agent.agent_id,
60
+ "messages": messages,
61
+ "stream": True,
62
+ "max_tokens": None,
63
+ "tools": agent.tools,
64
+ "tool_choice": "auto",
65
+ "presence_penalty": 0,
66
+ "frequency_penalty": 0,
67
+ "n": 1
68
+ }
69
+
70
+ async with api_lock:
71
+ response = await agent.client.agents.stream_async(**payload)
72
+
73
+ async for chunk in response:
74
+ delta = chunk.data.choices[0].delta
75
+ content = delta.content or ""
76
+ full += content
77
+ if current_phase == "final":
78
+ final_full = full
79
+
80
+ phases = extract_phases(full)
81
+ buffer = phases.get(current_phase, "")
82
+ if current_phase == "think":
83
+ history[-1] = ChatMessage(role="assistant", content=buffer, metadata={"title": "Thinking...", "status": "pending"})
84
+ elif current_phase == "act":
85
+ history[-1] = ChatMessage(role="assistant", content=buffer, metadata={"title": "Acting...", "status": "pending"})
86
+ elif current_phase == "observe":
87
+ history[-1] = ChatMessage(role="assistant", content=buffer, metadata={"title": "Observing...", "status": "pending"})
88
  yield history
89
 
90
+ if current_phase == "final":
91
+ delta_content = delta.content or ""
92
+ final_full += delta_content
93
+ phases = extract_phases(final_full)
94
+ buffer = phases.get("final", "")
95
+ yield history
96
+ if delta_content == "" and buffer:
97
+ done = True
98
+ break
99
+
100
+ if current_phase_index == 0:
101
+ messages = [msg for msg in messages if not msg.get("prefix")]
102
+ if buffer:
103
+ prefix_label = current_phase.upper() if current_phase != "final" else "FINAL ANSWER"
104
+ messages.append({
105
+ "role": "assistant",
106
+ "content": f"{prefix_label}: {buffer}\n\nACT: Let's using some tools to solve the problem.",
107
+ "prefix": True
108
+ })
109
+
110
+ elif current_phase_index == 1:
111
+ for message in messages:
112
+ if "prefix" in message:
113
+ del message["prefix"]
114
+
115
+ if current_phase_index == 2:
116
+ for message in messages:
117
+ if "prefix" in message:
118
+ del message["prefix"]
119
+ messages.append({
120
+ "role": "assistant",
121
+ "content": "OBSERVE: Based on the results, let's observe the situation and see if we need to adjust our approach.",
122
+ "prefix": True
123
+ })
124
+
125
+ yield history
126
+
127
+ if current_phase == "act":
128
+ tool_calls = getattr(delta, "tool_calls", None)
129
+ if tool_calls and tool_calls != [] and str(tool_calls) != "Unset()":
130
+ async with tool_lock:
131
+ messages = call_tool(
132
+ agent,
133
+ tool_calls,
134
+ messages
135
+ )
136
+ last_tool_response = next((m for m in reversed(messages) if m["role"] == "tool"), None)
137
+ if last_tool_response and last_tool_response.get("content"):
138
+ buffer += "\n\n" + last_tool_response["content"]
139
+ history[-1] = ChatMessage(role="assistant", content=buffer, metadata={"title": "Acting...", "status": "pending"})
140
+ yield history
141
 
142
+ if not done:
143
+ current_phase_index += 1
144
+ if current_phase_index < len(phase_order):
145
+ pass
 
 
 
146
  else:
147
+ done = True
 
 
148
 
149
+ observe_text = phases.get("observe", "")
150
+ final_text = phases.get("final", "")
151
 
152
+ if observe_text:
153
+ history[-1] = ChatMessage(role="assistant", content=observe_text, metadata={"title": "Observing...", "status": "done"})
 
 
 
 
 
 
 
 
 
154
 
155
+ if final_text:
156
+ history.append(ChatMessage(role="assistant", content=final_text))
 
 
157
 
158
  yield history
src/agent/utils/call.py ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+
3
+
4
+ def call_tool(agent, tool_calls, messages):
5
+ """
6
+ Calls the specified tools with the provided arguments and updates the messages accordingly.
7
+ """
8
+ for tool_call in tool_calls:
9
+ output = []
10
+ fn_name = tool_call.function.name
11
+ fn_args = json.loads(tool_call.function.arguments)
12
+
13
+ try:
14
+ fn_result = agent.names_to_functions[fn_name](**fn_args)
15
+ output.append((tool_call.id, fn_name, fn_args, fn_result))
16
+
17
+ except Exception as e:
18
+ output.append((tool_call.id, fn_name, fn_args, None))
19
+
20
+ for tool_call_id, fn_name, fn_args, fn_result in output:
21
+ messages.append({
22
+ "role": "assistant",
23
+ "tool_calls": [
24
+ {
25
+ "id": tool_call_id,
26
+ "type": "function",
27
+ "function": {
28
+ "name": fn_name,
29
+ "arguments": json.dumps(fn_args),
30
+ }
31
+ }
32
+ ]
33
+ })
34
+ messages.append(
35
+ {
36
+ "role": "tool",
37
+ "content": fn_result if fn_result is not None else f"Error occurred: {fn_name} failed to execute",
38
+ "tool_call_id": tool_call_id,
39
+ },
40
+ )
41
+ return messages