guoj5 commited on
Commit
b9c9b71
·
1 Parent(s): e406969

Optimised the demonstration, Added constraint judge.

Browse files
backend/Example.txt CHANGED
@@ -50,9 +50,9 @@ categories = [
50
  NUM_POSITIONS = len(categories[0][1])
51
 
52
  item_to_cat_and_index = {}
53
- for cat_idx, (_, item_list) in enumerate(categories):
54
  for item_idx, item_str in enumerate(item_list):
55
- item_to_cat_and_index[item_str] = (cat_idx, item_idx)
56
 
57
  Vars = []
58
  for cat_idx, (cat_name, item_list) in enumerate(categories):
@@ -66,70 +66,70 @@ for cat_idx, (cat_name, item_list) in enumerate(categories):
66
  s.add(Vars[cat_idx][item_idx] >= 1, Vars[cat_idx][item_idx] <= NUM_POSITIONS)
67
  s.add(Distinct(Vars[cat_idx]))
68
 
69
- def pos(item_str):
70
- (cat_idx, item_idx) = item_to_cat_and_index[item_str]
71
  return Vars[cat_idx][item_idx]
72
 
73
  # All clues here
74
  # The producer whose jam comes from Wild pickings is in the fourth position.
75
- s.add(pos("wild pickings") == 4)
76
 
77
  # The producer selling Cherry jam is somewhere to the right of the one wearing a Green shirt.
78
- s.add(pos("cherry") > pos("green"))
79
 
80
  # Isabella is selling Cherry jam.
81
- s.add(pos("Isabella") == pos("cherry"))
82
 
83
  # The producer wearing a Pink shirt is somewhere between the producer whose jam source is the Backyard and the one selling Watermelon jam, in that order.
84
- s.add(And(pos("backyard") < pos("pink"), pos("pink") < pos("watermelon")))
85
 
86
  # The producer selling 14 oz jars is somewhere to the right of the one wearing a Green shirt.
87
- s.add(pos("14 oz") > pos("green"))
88
 
89
  # The producer with 14 oz jars is next to the one selling Raspberry jam.
90
- s.add(Abs(pos("14 oz") - pos("raspberry")) == 1)
91
 
92
  # The Raspberry jam producer is positioned at one of the ends.
93
- s.add(Or(pos("raspberry") == 1, pos("raspberry") == NUM_POSITIONS))
94
 
95
  # Barbara is next to the producer whose jam source is the Organic farm.
96
- s.add(Abs(pos("Barbara") - pos("organic farm")) == 1)
97
 
98
  # Jane is located somewhere between Nicole and Isabella, in that order.
99
- s.add(And(pos("Nicole") < pos("Jane"), pos("Jane") < pos("Isabella")))
100
 
101
  # The producer of Fig jam sources the fruit from an Organic farm.
102
- s.add(pos("fig") == pos("organic farm"))
103
 
104
  # The producer with 10 oz jars is at one of the ends.
105
- s.add(Or(pos("10 oz") == 1, pos("10 oz") == NUM_POSITIONS))
106
 
107
  # The Raspberry jam producer is somewhere to the right of the one wearing a Green shirt.
108
- s.add(pos("raspberry") > pos("green"))
109
 
110
  # The producer with 12 oz jars is next to the one who has 6 oz jars.
111
- s.add(Abs(pos("12 oz") - pos("6 oz")) == 1)
112
 
113
  # Isabella is next to the producer wearing a Black shirt.
114
- s.add(Abs(pos("Isabella") - pos("black")) == 1)
115
 
116
  # The producer with 6 oz jars is next to the one whose source is the Local grocer.
117
- s.add(Abs(pos("6 oz") - pos("local grocer")) == 1)
118
 
119
  # The producer with 6 oz jars is in the second position.
120
- s.add(pos("6 oz") == 2)
121
 
122
  # Rachel's source of fruit is the Farmers' coop.
123
- s.add(pos("Rachel") == pos("farmers' coop"))
124
 
125
  # Barbara is next to Nicole.
126
- s.add(Abs(pos("Barbara") - pos("Nicole")) == 1)
127
 
128
  # The producer wearing an Orange shirt gets her fruit from the Backyard.
129
- s.add(pos("orange") == pos("backyard"))
130
 
131
  # The producer with 12 oz jars is in the very first position.
132
- s.add(pos("12 oz") == 1)
133
 
134
  # Solve the puzzle
135
  if s.check() == sat:
 
50
  NUM_POSITIONS = len(categories[0][1])
51
 
52
  item_to_cat_and_index = {}
53
+ for cat_idx, (cat_str, item_list) in enumerate(categories):
54
  for item_idx, item_str in enumerate(item_list):
55
+ item_to_cat_and_index[(cat_str, item_str)] = (cat_idx, item_idx)
56
 
57
  Vars = []
58
  for cat_idx, (cat_name, item_list) in enumerate(categories):
 
66
  s.add(Vars[cat_idx][item_idx] >= 1, Vars[cat_idx][item_idx] <= NUM_POSITIONS)
67
  s.add(Distinct(Vars[cat_idx]))
68
 
69
+ def pos(cat_str, item_str):
70
+ (cat_idx, item_idx) = item_to_cat_and_index[(cat_str, item_str)]
71
  return Vars[cat_idx][item_idx]
72
 
73
  # All clues here
74
  # The producer whose jam comes from Wild pickings is in the fourth position.
75
+ s.add(pos("Source", "wild pickings") == 4)
76
 
77
  # The producer selling Cherry jam is somewhere to the right of the one wearing a Green shirt.
78
+ s.add(pos("Jam", "cherry") > pos("Shirt", "green"))
79
 
80
  # Isabella is selling Cherry jam.
81
+ s.add(pos("Name", "Isabella") == pos("Jam", "cherry"))
82
 
83
  # The producer wearing a Pink shirt is somewhere between the producer whose jam source is the Backyard and the one selling Watermelon jam, in that order.
84
+ s.add(And(pos("Source", "backyard") < pos("Shirt", "pink"), pos("Shirt", "pink") < pos("Jam", "watermelon")))
85
 
86
  # The producer selling 14 oz jars is somewhere to the right of the one wearing a Green shirt.
87
+ s.add(pos("Size", "14 oz") > pos("Shirt", "green"))
88
 
89
  # The producer with 14 oz jars is next to the one selling Raspberry jam.
90
+ s.add(Abs(pos("Size", "14 oz") - pos("Jam", "raspberry")) == 1)
91
 
92
  # The Raspberry jam producer is positioned at one of the ends.
93
+ s.add(Or(pos("Jam", "raspberry") == 1, pos("Jam", "raspberry") == NUM_POSITIONS))
94
 
95
  # Barbara is next to the producer whose jam source is the Organic farm.
96
+ s.add(Abs(pos("Name", "Barbara") - pos("Source", "organic farm")) == 1)
97
 
98
  # Jane is located somewhere between Nicole and Isabella, in that order.
99
+ s.add(And(pos("Name", "Nicole") < pos("Name", "Jane"), pos("Name", "Jane") < pos("Name", "Isabella")))
100
 
101
  # The producer of Fig jam sources the fruit from an Organic farm.
102
+ s.add(pos("Jam", "fig") == pos("Source", "organic farm"))
103
 
104
  # The producer with 10 oz jars is at one of the ends.
105
+ s.add(Or(pos("Size", "10 oz") == 1, pos("Size", "10 oz") == NUM_POSITIONS))
106
 
107
  # The Raspberry jam producer is somewhere to the right of the one wearing a Green shirt.
108
+ s.add(pos("Jam", "raspberry") > pos("Shirt", "green"))
109
 
110
  # The producer with 12 oz jars is next to the one who has 6 oz jars.
111
+ s.add(Abs(pos("Size", "12 oz") - pos("Size", "6 oz")) == 1)
112
 
113
  # Isabella is next to the producer wearing a Black shirt.
114
+ s.add(Abs(pos("Name", "Isabella") - pos("Shirt", "black")) == 1)
115
 
116
  # The producer with 6 oz jars is next to the one whose source is the Local grocer.
117
+ s.add(Abs(pos("Size", "6 oz") - pos("Source", "local grocer")) == 1)
118
 
119
  # The producer with 6 oz jars is in the second position.
120
+ s.add(pos("Size", "6 oz") == 2)
121
 
122
  # Rachel's source of fruit is the Farmers' coop.
123
+ s.add(pos("Name", "Rachel") == pos("Source", "farmers' coop"))
124
 
125
  # Barbara is next to Nicole.
126
+ s.add(Abs(pos("Name", "Barbara") - pos("Name", "Nicole")) == 1)
127
 
128
  # The producer wearing an Orange shirt gets her fruit from the Backyard.
129
+ s.add(pos("Shirt", "orange") == pos("Source", "backyard"))
130
 
131
  # The producer with 12 oz jars is in the very first position.
132
+ s.add(pos("Size", "12 oz") == 1)
133
 
134
  # Solve the puzzle
135
  if s.check() == sat:
backend/Sat_cnt.txt ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Solve the puzzle
2
+ if s.check() == sat:
3
+ m = s.model()
4
+ rows = []
5
+ header = ["House"] + [cat_name for cat_name, _ in categories]
6
+ for position in range(1, NUM_POSITIONS + 1):
7
+ row = [str(position)]
8
+ for cat_idx, (cat_name, item_list) in enumerate(categories):
9
+ for item_idx, item_str in enumerate(item_list):
10
+ if m.evaluate(Vars[cat_idx][item_idx]).as_long() == position:
11
+ row.append(item_str)
12
+ break
13
+ rows.append(row)
14
+ result_dict = {"header": header, "rows": rows}
15
+
16
+ cnt = 1
17
+ block = []
18
+ for cat_idx, (cat_name, item_list) in enumerate(categories):
19
+ for i in range(NUM_POSITIONS):
20
+ block.append(Vars[cat_idx][i] != m[Vars[cat_idx][i]])
21
+ s.add(Or(block))
22
+ while s.check() == sat:
23
+ m = s.model()
24
+ cnt += 1
25
+ block = []
26
+ for cat_idx, (cat_name, item_list) in enumerate(categories):
27
+ for i in range(NUM_POSITIONS):
28
+ block.append(Vars[cat_idx][i] != m[Vars[cat_idx][i]])
29
+ s.add(Or(block))
30
+ print(f"sat:{cnt}")
31
+ else:
32
+ print(f"error")
backend/__pycache__/solver.cpython-313.pyc CHANGED
Binary files a/backend/__pycache__/solver.cpython-313.pyc and b/backend/__pycache__/solver.cpython-313.pyc differ
 
backend/app.py CHANGED
@@ -10,13 +10,13 @@ example_path = os.path.join(BASE_DIR, "Example.txt")
10
  with open(example_path, "r", encoding="utf-8") as f:
11
  DEFAULT_SYS_CONTENT = f.read()
12
 
13
- # 只初始化一次
14
  app = Flask(__name__, static_folder='static', static_url_path='')
15
  CORS(app)
16
 
17
  @app.route('/')
18
  def index():
19
- # 返回静态文件夹下编译好的 index.html
20
  return send_from_directory(app.static_folder, 'index.html')
21
 
22
  @app.route("/get_puzzle", methods=["GET"])
 
10
  with open(example_path, "r", encoding="utf-8") as f:
11
  DEFAULT_SYS_CONTENT = f.read()
12
 
13
+ # Only initialize once
14
  app = Flask(__name__, static_folder='static', static_url_path='')
15
  CORS(app)
16
 
17
  @app.route('/')
18
  def index():
19
+ # Return built static index.html
20
  return send_from_directory(app.static_folder, 'index.html')
21
 
22
  @app.route("/get_puzzle", methods=["GET"])
backend/solver.py CHANGED
@@ -20,8 +20,8 @@ def solve_puzzle(index, puzzle, expected_solution, sys_content):
20
  )
21
 
22
  messages = [
23
- {"role": "user", "content": sys_content}, # 先把 sys_content 放进去
24
- {"role": "user", "content": puzzle}, # 再放 puzzle
25
  ]
26
  attempts = 0
27
  current_solution = None
@@ -43,7 +43,7 @@ def solve_puzzle(index, puzzle, expected_solution, sys_content):
43
  messages.append({"role": "user", "content": "Please write a complete Python code in your response. Try again."})
44
  continue
45
 
46
- code_to_run = code_blocks[0].strip()
47
  result = subprocess.run(
48
  [sys.executable, "-c", code_to_run],
49
  stdout=subprocess.PIPE,
@@ -53,6 +53,10 @@ def solve_puzzle(index, puzzle, expected_solution, sys_content):
53
  output = result.stdout.strip()
54
  # print(output)
55
 
 
 
 
 
56
  try:
57
  current_solution = json.loads(output)
58
  except json.JSONDecodeError:
@@ -66,10 +70,117 @@ def solve_puzzle(index, puzzle, expected_solution, sys_content):
66
  "solution": current_solution,
67
  "attempts": attempts,
68
  "generatedCode": code_to_run,
69
- "modelResponse": content
 
70
  }
71
  else:
72
- messages.append({"role": "user", "content": "The solution does not match the expected answer. Please check your categories and constraints and provide the complete code again."})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
 
74
  return {
75
  "index": index,
@@ -77,5 +188,6 @@ def solve_puzzle(index, puzzle, expected_solution, sys_content):
77
  "solution": current_solution,
78
  "attempts": attempts,
79
  "generatedCode": code_to_run,
80
- "modelResponse": content
 
81
  }
 
20
  )
21
 
22
  messages = [
23
+ {"role": "user", "content": sys_content},
24
+ {"role": "user", "content": puzzle + f'\nCategories: {str(expected_solution["header"][1:])}\n'},
25
  ]
26
  attempts = 0
27
  current_solution = None
 
43
  messages.append({"role": "user", "content": "Please write a complete Python code in your response. Try again."})
44
  continue
45
 
46
+ code_to_run = code_blocks[-1].strip()
47
  result = subprocess.run(
48
  [sys.executable, "-c", code_to_run],
49
  stdout=subprocess.PIPE,
 
53
  output = result.stdout.strip()
54
  # print(output)
55
 
56
+ if result.stderr.strip():
57
+ messages.append({"role": "user", "content": f"Your code has errors: {result.stderr.strip()}. Please check your code and provide the complete code in the end."})
58
+ continue
59
+
60
  try:
61
  current_solution = json.loads(output)
62
  except json.JSONDecodeError:
 
70
  "solution": current_solution,
71
  "attempts": attempts,
72
  "generatedCode": code_to_run,
73
+ "modelResponse": content,
74
+ "problematicConstraints": None
75
  }
76
  else:
77
+ messages.append({"role": "user", "content": "The solution does not match the expected answer. Please check your categories and step by step reason your constraints and provide the complete code in the end."})
78
+
79
+ if result.stderr.strip():
80
+ return {
81
+ "index": index,
82
+ "success": False,
83
+ "solution": current_solution,
84
+ "attempts": attempts,
85
+ "generatedCode": code_to_run,
86
+ "modelResponse": content,
87
+ "problematicConstraints": "Code error:\n" + result.stderr.strip()
88
+ }
89
+
90
+ # if 'rows' not in current_solution.keys():
91
+ # return {
92
+ # "index": index,
93
+ # "success": False,
94
+ # "solution": current_solution,
95
+ # "attempts": attempts,
96
+ # "generatedCode": code_to_run,
97
+ # "modelResponse": content,
98
+ # "problematicConstraints": str(current_solution)
99
+ # }
100
+
101
+ code_by_line = code_to_run.split("\n")
102
+
103
+ fisrt_cons_line = None
104
+ last_cons_line = None
105
+ for i, line in enumerate(code_by_line):
106
+ if "s.add" in line and "pos" in line:
107
+ if not fisrt_cons_line:
108
+ fisrt_cons_line = i
109
+ last_cons_line = i
110
+
111
+ experiment_code_line = code_by_line[:fisrt_cons_line]
112
+ categories = expected_solution['header']
113
+ for i, houses in enumerate(expected_solution['rows']):
114
+ for j in range(1, len(houses)):
115
+ experiment_code_line.append(f"s.add(pos(\"{categories[j]}\", \"{houses[j]}\") == {i+1})")
116
+ experiment_code_line.append("")
117
+ experiment_code_line.append("print(s.check())")
118
+
119
+ def satisfied(constraint):
120
+ experiment_code_line[-2] = constraint
121
+ experiment_code = "\n".join(experiment_code_line)
122
+ sat_checker = experiment_code.strip()
123
+ result = subprocess.run(
124
+ [sys.executable, "-c", sat_checker],
125
+ stdout=subprocess.PIPE,
126
+ stderr=subprocess.PIPE,
127
+ text=True
128
+ )
129
+ output = result.stdout.strip()
130
+ return output.lower()
131
+
132
+ if "error" in current_solution.keys():
133
+ if "Multiple" in current_solution['error']:
134
+ problematic_constraints = "Issue: Multiple solutions\n"
135
+ else:
136
+ problematic_constraints = "Issue: No solution\n"
137
+ else:
138
+ problematic_constraints = "Issue: Wrong answer\n"
139
+
140
+ cnt_cons = 0
141
+ problematic_constraints += "\nSatisfaction judge:\n"
142
+ for i, line in enumerate(code_by_line):
143
+ if "s.add" in line and "pos" in line:
144
+ constraint = line.strip()
145
+ cnt_cons += 1
146
+ if satisfied(constraint) == "unsat":
147
+ problematic_constraints += f"In line {i + 1}, the {cnt_cons}-th constraint: {constraint}. Not satisfied.\n"
148
+
149
+ with open("Sat_cnt.txt", "r") as f:
150
+ sat_cnt = f.read()
151
+ cnt_cons = 0
152
+ problematic_constraints += "\nMultiple solutions judge:\n"
153
+ code_by_line_experiment = code_to_run.split("\n")[:last_cons_line + 1]
154
+ code_by_line_experiment.append("\n")
155
+ def run_result():
156
+ experiment_code = "\n".join(code_by_line_experiment) + sat_cnt
157
+ sat_checker = experiment_code.strip()
158
+ result = subprocess.run(
159
+ [sys.executable, "-c", sat_checker],
160
+ stdout=subprocess.PIPE,
161
+ stderr=subprocess.PIPE,
162
+ text=True
163
+ )
164
+ return result.stdout.strip()
165
+ res = run_result()
166
+ # print("\n".join(code_by_line_experiment) + sat_cnt)
167
+ # print(res)
168
+ if not res or res == "error":
169
+ problematic_constraints += "Unable to judge."
170
+ else:
171
+ cur = int(res.split(':')[1])
172
+ for i, line in enumerate(code_by_line):
173
+ if "s.add" in line and "pos" in line:
174
+ cnt_cons += 1
175
+ code_by_line_experiment[i] = ""
176
+ res = run_result()
177
+ if not res or res == "error":
178
+ problematic_constraints += "Unable to judge."
179
+ break
180
+ now_cnt = int(res.split(':')[1])
181
+ if now_cnt == cur:
182
+ problematic_constraints += f"In line {i + 1}, the {cnt_cons}-th constraint: {line}. A suspect redundency.\n"
183
+ code_by_line_experiment[i] = line
184
 
185
  return {
186
  "index": index,
 
188
  "solution": current_solution,
189
  "attempts": attempts,
190
  "generatedCode": code_to_run,
191
+ "modelResponse": content,
192
+ "problematicConstraints": problematic_constraints
193
  }
backend/static/asset-manifest.json CHANGED
@@ -1,15 +1,15 @@
1
  {
2
  "files": {
3
  "main.css": "/static/css/main.e6c13ad2.css",
4
- "main.js": "/static/js/main.f13a7657.js",
5
  "static/js/453.8ab44547.chunk.js": "/static/js/453.8ab44547.chunk.js",
6
  "index.html": "/index.html",
7
  "main.e6c13ad2.css.map": "/static/css/main.e6c13ad2.css.map",
8
- "main.f13a7657.js.map": "/static/js/main.f13a7657.js.map",
9
  "453.8ab44547.chunk.js.map": "/static/js/453.8ab44547.chunk.js.map"
10
  },
11
  "entrypoints": [
12
  "static/css/main.e6c13ad2.css",
13
- "static/js/main.f13a7657.js"
14
  ]
15
  }
 
1
  {
2
  "files": {
3
  "main.css": "/static/css/main.e6c13ad2.css",
4
+ "main.js": "/static/js/main.120fc3ad.js",
5
  "static/js/453.8ab44547.chunk.js": "/static/js/453.8ab44547.chunk.js",
6
  "index.html": "/index.html",
7
  "main.e6c13ad2.css.map": "/static/css/main.e6c13ad2.css.map",
8
+ "main.120fc3ad.js.map": "/static/js/main.120fc3ad.js.map",
9
  "453.8ab44547.chunk.js.map": "/static/js/453.8ab44547.chunk.js.map"
10
  },
11
  "entrypoints": [
12
  "static/css/main.e6c13ad2.css",
13
+ "static/js/main.120fc3ad.js"
14
  ]
15
  }
backend/static/index.html CHANGED
@@ -1 +1 @@
1
- <!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site created using create-react-app"/><link rel="apple-touch-icon" href="/logo192.png"/><link rel="manifest" href="/manifest.json"/><title>React App</title><script defer="defer" src="/static/js/main.f13a7657.js"></script><link href="/static/css/main.e6c13ad2.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
 
1
+ <!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site created using create-react-app"/><link rel="apple-touch-icon" href="/logo192.png"/><link rel="manifest" href="/manifest.json"/><title>React App</title><script defer="defer" src="/static/js/main.120fc3ad.js"></script><link href="/static/css/main.e6c13ad2.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
backend/static/static/js/{main.f13a7657.js → main.120fc3ad.js} RENAMED
The diff for this file is too large to render. See raw diff
 
backend/static/static/js/{main.f13a7657.js.LICENSE.txt → main.120fc3ad.js.LICENSE.txt} RENAMED
File without changes
backend/static/static/js/{main.f13a7657.js.map → main.120fc3ad.js.map} RENAMED
The diff for this file is too large to render. See raw diff
 
frontend/src/App.js CHANGED
@@ -2,23 +2,26 @@
2
  import React, { useState, useEffect } from 'react';
3
 
4
  function App() {
5
- // 用于存储 puzzle index 及其 puzzle 数据
6
  const [puzzleIndex, setPuzzleIndex] = useState(0);
7
  const [puzzleText, setPuzzleText] = useState("");
8
  const [expectedSolution, setExpectedSolution] = useState(null);
9
 
10
- // sysContent 如果用户不改,就用默认 Example.txt
11
  const [sysContent, setSysContent] = useState("");
12
 
13
- // 交互结果
14
- const [modelResponse, setModelResponse] = useState("");
15
  const [generatedCode, setGeneratedCode] = useState("");
16
  const [executionSuccess, setExecutionSuccess] = useState(null);
17
  const [attempts, setAttempts] = useState(0);
 
 
18
 
19
- // 前端先获取默认 sysContent
 
 
20
  useEffect(() => {
21
- fetch("/default_sys_content")
22
  .then(res => res.json())
23
  .then(data => {
24
  if(data.success) {
@@ -28,16 +31,16 @@ function App() {
28
  .catch(e => console.error(e));
29
  }, []);
30
 
31
- // puzzleIndex 改变时,自动获取对应 puzzle
32
  useEffect(() => {
33
- fetch(`/get_puzzle?index=${puzzleIndex}`)
34
  .then(res => res.json())
35
  .then(data => {
36
  if(data.success) {
37
  setPuzzleText(data.puzzle);
38
  setExpectedSolution(data.expected_solution);
39
  } else {
40
- console.error("获取 puzzle 失败", data.error);
41
  setPuzzleText("");
42
  setExpectedSolution(null);
43
  }
@@ -47,17 +50,20 @@ function App() {
47
 
48
  const handleSolve = () => {
49
  if(!puzzleText || !expectedSolution) {
50
- alert("puzzle expectedSolution 不完整");
51
  return;
52
  }
53
  const payload = {
54
  index: puzzleIndex,
55
  puzzle: puzzleText,
56
  expected_solution: expectedSolution,
57
- sys_content: sysContent
 
58
  };
59
 
60
- fetch("/solve", {
 
 
61
  method: "POST",
62
  headers: { "Content-Type": "application/json" },
63
  body: JSON.stringify(payload)
@@ -65,16 +71,19 @@ function App() {
65
  .then(res => res.json())
66
  .then(data => {
67
  if(!data.success) {
68
- alert("后端处理错误: " + data.error);
69
  return;
70
  }
71
  const result = data.result;
72
- setModelResponse(result.modelResponse || "");
73
  setGeneratedCode(result.generatedCode || "");
74
  setExecutionSuccess(result.success);
75
  setAttempts(result.attempts || 0);
 
76
  })
77
- .catch(e => console.error(e));
 
 
 
78
  };
79
 
80
  return (
@@ -82,7 +91,7 @@ function App() {
82
  <h1>Zebra Puzzle Demo</h1>
83
 
84
  <div style={{ marginBottom: 20 }}>
85
- <label>选择 puzzle index (0 - 999): </label>
86
  <input
87
  type="number"
88
  value={puzzleIndex}
@@ -101,7 +110,7 @@ function App() {
101
  </div>
102
 
103
  <div style={{ marginBottom: 20 }}>
104
- <h3>sys_content (可编辑)</h3>
105
  <textarea
106
  rows={10}
107
  cols={80}
@@ -111,17 +120,17 @@ function App() {
111
  </div>
112
 
113
  <div style={{ marginBottom: 20 }}>
114
- <button onClick={handleSolve}>Solve Puzzle with LLM</button>
115
  </div>
116
 
117
  <div>
118
  <h2>Result</h2>
119
  <p>Success: {executionSuccess === null ? "N/A" : executionSuccess ? "✅" : "❌"}</p>
120
  <p>Attempts: {attempts}</p>
 
 
121
  <h3>Generated Code</h3>
122
  <pre>{generatedCode}</pre>
123
- <h3>Model Response</h3>
124
- <pre>{modelResponse}</pre>
125
  </div>
126
  </div>
127
  );
 
2
  import React, { useState, useEffect } from 'react';
3
 
4
  function App() {
5
+ // For puzzle index and puzzle data
6
  const [puzzleIndex, setPuzzleIndex] = useState(0);
7
  const [puzzleText, setPuzzleText] = useState("");
8
  const [expectedSolution, setExpectedSolution] = useState(null);
9
 
10
+ // sysContent can be editted, default using Example.txt
11
  const [sysContent, setSysContent] = useState("");
12
 
13
+ // Interaction results
 
14
  const [generatedCode, setGeneratedCode] = useState("");
15
  const [executionSuccess, setExecutionSuccess] = useState(null);
16
  const [attempts, setAttempts] = useState(0);
17
+ const [isSolving, setIsSolving] = useState(false);
18
+ const [problematicConstraints, setProblematicConstraints] = useState("");
19
 
20
+ const FLASK_BASE_URL = 'http://localhost:7860';
21
+
22
+ // Frontend fetch sysContent in default
23
  useEffect(() => {
24
+ fetch(`${FLASK_BASE_URL}/default_sys_content`)
25
  .then(res => res.json())
26
  .then(data => {
27
  if(data.success) {
 
31
  .catch(e => console.error(e));
32
  }, []);
33
 
34
+ // When puzzleIndex changing,auto get puzzle
35
  useEffect(() => {
36
+ fetch(`${FLASK_BASE_URL}/get_puzzle?index=${puzzleIndex}`)
37
  .then(res => res.json())
38
  .then(data => {
39
  if(data.success) {
40
  setPuzzleText(data.puzzle);
41
  setExpectedSolution(data.expected_solution);
42
  } else {
43
+ console.error("Failed to fetch puzzle", data.error);
44
  setPuzzleText("");
45
  setExpectedSolution(null);
46
  }
 
50
 
51
  const handleSolve = () => {
52
  if(!puzzleText || !expectedSolution) {
53
+ alert("puzzle or expectedSolution incomplete");
54
  return;
55
  }
56
  const payload = {
57
  index: puzzleIndex,
58
  puzzle: puzzleText,
59
  expected_solution: expectedSolution,
60
+ sys_content: sysContent,
61
+ problematic_constraints: problematicConstraints
62
  };
63
 
64
+ setIsSolving(true);
65
+
66
+ fetch(`${FLASK_BASE_URL}/solve`, {
67
  method: "POST",
68
  headers: { "Content-Type": "application/json" },
69
  body: JSON.stringify(payload)
 
71
  .then(res => res.json())
72
  .then(data => {
73
  if(!data.success) {
74
+ alert("Backend error: " + data.error);
75
  return;
76
  }
77
  const result = data.result;
 
78
  setGeneratedCode(result.generatedCode || "");
79
  setExecutionSuccess(result.success);
80
  setAttempts(result.attempts || 0);
81
+ setProblematicConstraints(result.problematicConstraints || "");
82
  })
83
+ .catch(e => console.error(e))
84
+ .finally(() => {
85
+ setIsSolving(false);
86
+ });
87
  };
88
 
89
  return (
 
91
  <h1>Zebra Puzzle Demo</h1>
92
 
93
  <div style={{ marginBottom: 20 }}>
94
+ <label>Choose puzzle index (0 - 999): </label>
95
  <input
96
  type="number"
97
  value={puzzleIndex}
 
110
  </div>
111
 
112
  <div style={{ marginBottom: 20 }}>
113
+ <h3>sys_content</h3>
114
  <textarea
115
  rows={10}
116
  cols={80}
 
120
  </div>
121
 
122
  <div style={{ marginBottom: 20 }}>
123
+ <button onClick={handleSolve} disabled={isSolving}>Solve Puzzle with LLM</button>
124
  </div>
125
 
126
  <div>
127
  <h2>Result</h2>
128
  <p>Success: {executionSuccess === null ? "N/A" : executionSuccess ? "✅" : "❌"}</p>
129
  <p>Attempts: {attempts}</p>
130
+ <h3>Problematic Constraints</h3>
131
+ <pre>{problematicConstraints}</pre>
132
  <h3>Generated Code</h3>
133
  <pre>{generatedCode}</pre>
 
 
134
  </div>
135
  </div>
136
  );
test.py ADDED
@@ -0,0 +1,146 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from z3 import *
2
+ import json
3
+ import sys
4
+
5
+ # Define all categories in a single list of tuples:
6
+ # (House is implicit; each row's first column will be the house number.)
7
+ categories = [
8
+ ("Name", ["Bob", "Arnold", "Carol", "Alice", "Peter", "Eric"]),
9
+ ("Mother", ["Sarah", "Janelle", "Aniya", "Kailyn", "Holly", "Penny"]),
10
+ ("Children", ["Fred", "Samantha", "Bella", "Meredith", "Alice", "Timothy"]),
11
+ ("Vacation", ["city", "mountain", "camping", "beach", "cruise", "cultural"]),
12
+ ("BookGenre", ["romance", "mystery", "historical fiction", "science fiction", "biography", "fantasy"])
13
+ ]
14
+
15
+ # No need to change here, automatically processing
16
+ NUM_POSITIONS = len(categories[0][1])
17
+
18
+ item_to_cat_and_index = {}
19
+ for cat_idx, (cat_str, item_list) in enumerate(categories):
20
+ for item_idx, item_str in enumerate(item_list):
21
+ item_to_cat_and_index[(cat_str, item_str)] = (cat_idx, item_idx)
22
+
23
+ Vars = []
24
+ for cat_idx, (cat_name, item_list) in enumerate(categories):
25
+ var = IntVector(cat_name, len(item_list))
26
+ Vars.append(var)
27
+
28
+ s = Solver()
29
+
30
+ for cat_idx, (cat_name, item_list) in enumerate(categories):
31
+ for item_idx, item_str in enumerate(item_list):
32
+ s.add(Vars[cat_idx][item_idx] >= 1, Vars[cat_idx][item_idx] <= NUM_POSITIONS)
33
+ s.add(Distinct(Vars[cat_idx]))
34
+
35
+ def pos(cat_str, item_str):
36
+ (cat_idx, item_idx) = item_to_cat_and_index[(cat_str, item_str)]
37
+ return Vars[cat_idx][item_idx]
38
+
39
+ # All clues here
40
+ # 1. The person who loves beach vacations is not in the second house.
41
+ s.add(pos("Vacation", "beach") != 2)
42
+
43
+ # 2. The person who loves fantasy books is somewhere to the left of Peter.
44
+ s.add(pos("BookGenre", "fantasy") < pos("Name", "Peter"))
45
+
46
+ # 3. The person whose mother's name is Sarah is the person who prefers city breaks.
47
+ s.add(pos("Mother", "Sarah") == pos("Vacation", "city"))
48
+
49
+ # 4. The person who enjoys camping trips is somewhere to the right of Peter.
50
+ s.add(pos("Vacation", "camping") > pos("Name", "Peter"))
51
+
52
+ # 5. The person who likes going on cruises is the person's child is named Meredith.
53
+ s.add(pos("Vacation", "cruise") == pos("Children", "Meredith"))
54
+
55
+ # 6. There is one house between the person who is the mother of Timothy and Eric.
56
+ s.add(Abs(pos("Mother", "Timothy") - pos("Name", "Eric")) == 2)
57
+
58
+ # 7. The person whose mother's name is Janelle is not in the second house.
59
+ s.add(pos("Mother", "Janelle") != 2)
60
+
61
+ # 8. The person's child is named Fred is somewhere to the left of Eric.
62
+ s.add(pos("Children", "Fred") < pos("Name", "Eric"))
63
+
64
+ # 9. The person who goes on cultural tours is in the fourth house.
65
+ s.add(pos("Vacation", "cultural") == 4)
66
+
67
+ # 10. The person whose mother's name is Janelle is not in the first house.
68
+ s.add(pos("Mother", "Janelle") != 1)
69
+
70
+ # 11. The person whose mother's name is Holly is somewhere to the right of the person who loves historical fiction books.
71
+ s.add(pos("Mother", "Holly") > pos("BookGenre", "historical fiction"))
72
+
73
+ # 12. The person's child is named Bella is somewhere to the left of Alice.
74
+ s.add(pos("Children", "Bella") < pos("Name", "Alice"))
75
+
76
+ # 13. Arnold is somewhere to the right of the person who loves fantasy books.
77
+ s.add(pos("Name", "Arnold") > pos("BookGenre", "fantasy"))
78
+
79
+ # 14. The person who loves mystery books is in the fourth house.
80
+ s.add(pos("BookGenre", "mystery") == 4)
81
+
82
+ # 15. The person's child is named Alice is the person who enjoys camping trips.
83
+ s.add(pos("Children", "Alice") == pos("Vacation", "camping"))
84
+
85
+ # 16. The person whose mother's name is Kailyn is the person who likes going on cruises.
86
+ s.add(pos("Mother", "Kailyn") == pos("Vacation", "cruise"))
87
+
88
+ # 17. There are two houses between the person who loves fantasy books and The person whose mother's name is Aniya.
89
+ s.add(Abs(pos("BookGenre", "fantasy") - pos("Mother", "Aniya")) == 3)
90
+
91
+ # 18. The person who loves fantasy books is Carol.
92
+ s.add(pos("BookGenre", "fantasy") == pos("Name", "Carol"))
93
+
94
+ # 19. The person who likes going on cruises is the person who loves biography books.
95
+ s.add(pos("Vacation", "cruise") == pos("BookGenre", "biography"))
96
+
97
+ # 20. The person who loves fantasy books is in the third house.
98
+ s.add(pos("BookGenre", "fantasy") == 3)
99
+
100
+ # 21. The person whose mother's name is Aniya is the person who loves romance books.
101
+ s.add(pos("Mother", "Aniya") == pos("BookGenre", "romance"))
102
+
103
+ # 22. The person whose mother's name is Janelle is not in the fourth house.
104
+ s.add(pos("Mother", "Janelle") != 4)
105
+
106
+ # 23. The person's child is named Fred is not in the fourth house.
107
+ s.add(pos("Children", "Fred") != 4)
108
+
109
+ # 24. The person who loves biography books is not in the second house.
110
+ s.add(pos("BookGenre", "biography") != 2)
111
+
112
+ # 25. There are two houses between The person whose mother's name is Holly and Eric.
113
+ s.add(Abs(pos("Mother", "Holly") - pos("Name", "Eric")) == 3)
114
+
115
+ # Solve the puzzle
116
+ if s.check() == sat:
117
+ m = s.model()
118
+ rows = []
119
+ header = ["House"] + [cat_name for cat_name, _ in categories]
120
+ for position in range(1, NUM_POSITIONS + 1):
121
+ row = [str(position)]
122
+ for cat_idx, (cat_name, item_list) in enumerate(categories):
123
+ for item_idx, item_str in enumerate(item_list):
124
+ if m.evaluate(Vars[cat_idx][item_idx]).as_long() == position:
125
+ row.append(item_str)
126
+ break
127
+ rows.append(row)
128
+ result_dict = {"header": header, "rows": rows}
129
+
130
+ cnt = 1
131
+ block = []
132
+ for cat_idx, (cat_name, item_list) in enumerate(categories):
133
+ for i in range(NUM_POSITIONS):
134
+ block.append(Vars[cat_idx][i] != m[Vars[cat_idx][i]])
135
+ s.add(Or(block))
136
+ while s.check() == sat:
137
+ m = s.model()
138
+ cnt += 1
139
+ block = []
140
+ for cat_idx, (cat_name, item_list) in enumerate(categories):
141
+ for i in range(NUM_POSITIONS):
142
+ block.append(Vars[cat_idx][i] != m[Vars[cat_idx][i]])
143
+ s.add(Or(block))
144
+ print(f"sat:{cnt}")
145
+ else:
146
+ print(f"error")