Vilin97 Claude Opus 4.6 (1M context) commited on
Commit
1568228
·
1 Parent(s): d95748d

Email now includes proof.lean and research_log.md attachments

Browse files

- proof.lean: the Lean 4 code file
- research_log.md: full research log with all tool calls, search results,
Lean code attempts, and error messages
- Agent captures detailed log of every tool call and result

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Files changed (3) hide show
  1. agent.py +15 -1
  2. app.py +2 -0
  3. email_sender.py +41 -28
agent.py CHANGED
@@ -170,13 +170,20 @@ async def run_agent(question: str):
170
  )
171
  cost_tracker = CostTracker()
172
  status_log: list[str] = []
 
173
 
174
  def add_status(msg: str):
175
  status_log.append(msg)
176
 
 
 
 
177
  def render_status():
178
  return "\n".join(f"- {s}" for s in status_log)
179
 
 
 
 
180
  messages: list[dict] = [
181
  {"role": "system", "content": SYSTEM_PROMPT},
182
  {"role": "user", "content": question},
@@ -228,6 +235,8 @@ async def run_agent(question: str):
228
  messages.append(msg_dict)
229
 
230
  if not assistant_msg.tool_calls:
 
 
231
  add_status("Thinking...")
232
  yield render_status(), None
233
  continue
@@ -239,15 +248,20 @@ async def run_agent(question: str):
239
  except json.JSONDecodeError:
240
  fn_args = {}
241
 
 
 
242
  result = await _handle_tool_call(
243
  fn_name, fn_args, add_status, render_status,
244
- # Pass the yield-like callback for streaming status updates
245
  )
246
 
 
 
247
  # final_answer terminates the loop
248
  if fn_name == "final_answer":
249
  add_status("Research complete!")
250
  yield render_status(), fn_args
 
 
251
  return
252
 
253
  yield render_status(), None
 
170
  )
171
  cost_tracker = CostTracker()
172
  status_log: list[str] = []
173
+ full_log: list[str] = [] # Detailed log with tool contents
174
 
175
  def add_status(msg: str):
176
  status_log.append(msg)
177
 
178
+ def log_detail(msg: str):
179
+ full_log.append(msg)
180
+
181
  def render_status():
182
  return "\n".join(f"- {s}" for s in status_log)
183
 
184
+ def get_full_log():
185
+ return "\n\n".join(full_log)
186
+
187
  messages: list[dict] = [
188
  {"role": "system", "content": SYSTEM_PROMPT},
189
  {"role": "user", "content": question},
 
235
  messages.append(msg_dict)
236
 
237
  if not assistant_msg.tool_calls:
238
+ if assistant_msg.content:
239
+ log_detail(f"## Agent thinking\n{assistant_msg.content}")
240
  add_status("Thinking...")
241
  yield render_status(), None
242
  continue
 
248
  except json.JSONDecodeError:
249
  fn_args = {}
250
 
251
+ log_detail(f"## Tool call: `{fn_name}`\n**Args:** ```json\n{json.dumps(fn_args, indent=2)[:2000]}\n```")
252
+
253
  result = await _handle_tool_call(
254
  fn_name, fn_args, add_status, render_status,
 
255
  )
256
 
257
+ log_detail(f"**Result:** ```\n{result[:5000]}\n```")
258
+
259
  # final_answer terminates the loop
260
  if fn_name == "final_answer":
261
  add_status("Research complete!")
262
  yield render_status(), fn_args
263
+ # Attach full_log to result for email
264
+ fn_args["_full_log"] = get_full_log()
265
  return
266
 
267
  yield render_status(), None
app.py CHANGED
@@ -42,10 +42,12 @@ async def respond(message, history, email):
42
  answer = final_result.get("answer", "") if final_result else ""
43
  lean_code = final_result.get("lean_code", "") if final_result else ""
44
  verified = final_result.get("verified", False) if final_result else False
 
45
  sent = send_result_email(
46
  to_email=email.strip(),
47
  question=message,
48
  status_log=status_text,
 
49
  answer=answer,
50
  lean_code=lean_code,
51
  verified=verified,
 
42
  answer = final_result.get("answer", "") if final_result else ""
43
  lean_code = final_result.get("lean_code", "") if final_result else ""
44
  verified = final_result.get("verified", False) if final_result else False
45
+ full_log = final_result.get("_full_log", "") if final_result else ""
46
  sent = send_result_email(
47
  to_email=email.strip(),
48
  question=message,
49
  status_log=status_text,
50
+ full_log=full_log,
51
  answer=answer,
52
  lean_code=lean_code,
53
  verified=verified,
email_sender.py CHANGED
@@ -4,6 +4,8 @@ import os
4
  import smtplib
5
  from email.mime.text import MIMEText
6
  from email.mime.multipart import MIMEMultipart
 
 
7
 
8
  SMTP_HOST = os.getenv("SMTP_HOST", "smtp.gmail.com")
9
  SMTP_PORT = int(os.getenv("SMTP_PORT", "587"))
@@ -15,11 +17,12 @@ def send_result_email(
15
  to_email: str,
16
  question: str,
17
  status_log: str,
 
18
  answer: str,
19
  lean_code: str,
20
  verified: bool,
21
  ) -> bool:
22
- """Send research results via email. Returns True on success."""
23
  if not SMTP_EMAIL or not SMTP_PASSWORD:
24
  print("SMTP not configured, skipping email")
25
  return False
@@ -27,7 +30,12 @@ def send_result_email(
27
  badge = "VERIFIED" if verified else "UNVERIFIED (best effort)"
28
  subject = f"VeriDeepResearch: {question[:60]}{'...' if len(question) > 60 else ''}"
29
 
30
- # Build HTML email
 
 
 
 
 
31
  html = f"""\
32
  <html>
33
  <body style="font-family: -apple-system, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px;">
@@ -49,7 +57,7 @@ def send_result_email(
49
  </pre>
50
 
51
  <details>
52
- <summary><strong>Research Log</strong></summary>
53
  <pre style="background: #f0f0f0; padding: 12px; border-radius: 8px; font-size: 12px; white-space: pre-wrap;">
54
  {_escape(status_log)}
55
  </pre>
@@ -57,37 +65,42 @@ def send_result_email(
57
 
58
  <hr>
59
  <p style="color: #666; font-size: 12px;">
 
60
  Generated by <a href="https://vilin97-verideepresearch.hf.space">VeriDeepResearch</a>.
61
  All Lean code checked with Axle (Lean 4.28.0 + Mathlib).
62
  </p>
63
  </body>
64
  </html>
65
  """
66
-
67
- # Plain text fallback
68
- text = f"""\
69
- VeriDeepResearch Results
70
- ========================
71
-
72
- Question: {question}
73
- Status: {badge}
74
-
75
- Answer:
76
- {answer}
77
-
78
- Lean 4 Code:
79
- {lean_code}
80
-
81
- Research Log:
82
- {status_log}
83
- """
84
-
85
- msg = MIMEMultipart("alternative")
86
- msg["Subject"] = subject
87
- msg["From"] = f"VeriDeepResearch <{SMTP_EMAIL}>"
88
- msg["To"] = to_email
89
- msg.attach(MIMEText(text, "plain"))
90
- msg.attach(MIMEText(html, "html"))
 
 
 
 
91
 
92
  try:
93
  with smtplib.SMTP(SMTP_HOST, SMTP_PORT) as server:
 
4
  import smtplib
5
  from email.mime.text import MIMEText
6
  from email.mime.multipart import MIMEMultipart
7
+ from email.mime.base import MIMEBase
8
+ from email import encoders
9
 
10
  SMTP_HOST = os.getenv("SMTP_HOST", "smtp.gmail.com")
11
  SMTP_PORT = int(os.getenv("SMTP_PORT", "587"))
 
17
  to_email: str,
18
  question: str,
19
  status_log: str,
20
+ full_log: str,
21
  answer: str,
22
  lean_code: str,
23
  verified: bool,
24
  ) -> bool:
25
+ """Send research results via email with attached files. Returns True on success."""
26
  if not SMTP_EMAIL or not SMTP_PASSWORD:
27
  print("SMTP not configured, skipping email")
28
  return False
 
30
  badge = "VERIFIED" if verified else "UNVERIFIED (best effort)"
31
  subject = f"VeriDeepResearch: {question[:60]}{'...' if len(question) > 60 else ''}"
32
 
33
+ msg = MIMEMultipart("mixed")
34
+ msg["Subject"] = subject
35
+ msg["From"] = f"VeriDeepResearch <{SMTP_EMAIL}>"
36
+ msg["To"] = to_email
37
+
38
+ # HTML body (concise summary)
39
  html = f"""\
40
  <html>
41
  <body style="font-family: -apple-system, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px;">
 
57
  </pre>
58
 
59
  <details>
60
+ <summary><strong>Status Log</strong></summary>
61
  <pre style="background: #f0f0f0; padding: 12px; border-radius: 8px; font-size: 12px; white-space: pre-wrap;">
62
  {_escape(status_log)}
63
  </pre>
 
65
 
66
  <hr>
67
  <p style="color: #666; font-size: 12px;">
68
+ Full research log and Lean code attached as files.<br>
69
  Generated by <a href="https://vilin97-verideepresearch.hf.space">VeriDeepResearch</a>.
70
  All Lean code checked with Axle (Lean 4.28.0 + Mathlib).
71
  </p>
72
  </body>
73
  </html>
74
  """
75
+ body = MIMEMultipart("alternative")
76
+ body.attach(MIMEText(f"Question: {question}\nStatus: {badge}\n\nAnswer:\n{answer}\n\nSee attached files for Lean code and full research log.", "plain"))
77
+ body.attach(MIMEText(html, "html"))
78
+ msg.attach(body)
79
+
80
+ # Attachment 1: Lean code (.lean file)
81
+ if lean_code.strip():
82
+ lean_attachment = MIMEBase("application", "octet-stream")
83
+ lean_attachment.set_payload(lean_code.encode("utf-8"))
84
+ encoders.encode_base64(lean_attachment)
85
+ lean_attachment.add_header(
86
+ "Content-Disposition",
87
+ "attachment",
88
+ filename="proof.lean",
89
+ )
90
+ msg.attach(lean_attachment)
91
+
92
+ # Attachment 2: Full research log (.md file)
93
+ if full_log.strip():
94
+ log_attachment = MIMEBase("application", "octet-stream")
95
+ log_content = f"# VeriDeepResearch Full Log\n\n**Question:** {question}\n**Status:** {badge}\n\n---\n\n{full_log}"
96
+ log_attachment.set_payload(log_content.encode("utf-8"))
97
+ encoders.encode_base64(log_attachment)
98
+ log_attachment.add_header(
99
+ "Content-Disposition",
100
+ "attachment",
101
+ filename="research_log.md",
102
+ )
103
+ msg.attach(log_attachment)
104
 
105
  try:
106
  with smtplib.SMTP(SMTP_HOST, SMTP_PORT) as server: