Use bounded PDF reading and source-guided verification; honor requested dates and image coordinates
Browse files
app.py
CHANGED
|
@@ -30,6 +30,7 @@ MODEL = os.getenv("GROQ_MODEL", "openai/gpt-oss-120b")
|
|
| 30 |
INPUT_PATH = Path("/tmp/course-agent-private-inputs.json")
|
| 31 |
CACHE_PATH = Path("/tmp/course-agent-owner-records.json")
|
| 32 |
MAX_BYTES = 10 * 1024 * 1024
|
|
|
|
| 33 |
USER_AGENT = "HF-Course-Agent/1.1 (https://huggingface.co/spaces/yongchoooon/Final_Assignment_Template)"
|
| 34 |
|
| 35 |
|
|
@@ -71,8 +72,9 @@ def download(url):
|
|
| 71 |
chunks, size = [], 0
|
| 72 |
for chunk in response.iter_content(65536):
|
| 73 |
size += len(chunk)
|
| 74 |
-
if
|
| 75 |
-
|
|
|
|
| 76 |
chunks.append(chunk)
|
| 77 |
return b"".join(chunks)
|
| 78 |
raise ValueError("Too many redirects.")
|
|
@@ -349,9 +351,10 @@ def attachment_tool(item):
|
|
| 349 |
{"type": "text", "text": "Analyze this attached image for the following question. "
|
| 350 |
"Describe precisely what is visible and relevant; distinguish observations from deductions. "
|
| 351 |
"For a chess board, give exact piece positions and a complete FEN, checking square coordinates and orientation. "
|
|
|
|
| 352 |
"Do not guess a winning move: a separate chess engine will verify the FEN. " + item["question"]},
|
| 353 |
{"type": "image_url", "image_url": {"url": "data:" + mime + ";base64," + base64.b64encode(content).decode()}}]}],
|
| 354 |
-
max_tokens=2048)
|
| 355 |
if response.choices[0].finish_reason == "length":
|
| 356 |
raise ValueError("Image analysis was truncated.")
|
| 357 |
return "Image analysis: " + (response.choices[0].message.content or "")
|
|
@@ -393,10 +396,13 @@ def solve(item, trace=None, progress=None):
|
|
| 393 |
"Use primary sources. Never treat a shopping/search-results page that merely repeats a question "
|
| 394 |
"as evidence. For named-source questions, read and confirm the claim in the specified source "
|
| 395 |
"before answering. A failed page download does not provide evidence. For English Wikipedia use read_wikipedia. If a historical year is "
|
| 396 |
-
"specified,
|
|
|
|
| 397 |
"and paginate if more text is available. Read an attachment when present. "
|
| 398 |
"Check every constraint and exclusion before answering. Culinary categories are not botanical "
|
| 399 |
-
"categories.
|
|
|
|
|
|
|
| 400 |
"For chess positions extract the FEN from the image, then verify the move with analyse_chess_position. "
|
| 401 |
"Prefer narrower entity searches over quoting an entire long question. "
|
| 402 |
"Do not repeat identical failed requests. Never claim to have read unsupported media. "
|
|
@@ -631,7 +637,7 @@ def import_answers(text, profile: gr.OAuthProfile | None):
|
|
| 631 |
return f"๋ต์ {len(records)}๊ฐ ๋ณต์ ์๋ฃ. ์์ง ์ ์ถํ์ง ์์์ต๋๋ค.", records, table, backup
|
| 632 |
|
| 633 |
|
| 634 |
-
def evaluate(limit, retry_ids, records, profile: gr.OAuthProfile | None, progress=gr.Progress()):
|
| 635 |
authorize(profile)
|
| 636 |
records = dict(records or {})
|
| 637 |
try:
|
|
@@ -653,7 +659,13 @@ def evaluate(limit, retry_ids, records, profile: gr.OAuthProfile | None, progres
|
|
| 653 |
table, backup = export_answers(records)
|
| 654 |
yield f"{index}/{len(pending)} ํ์ด ์ค. ๋ฌด๋ฃ ํ๋์ ๋ง์ถฐ ํธ์ถ ๊ฐ ๋๊ธฐํ ์ ์์ต๋๋ค.", records, table, backup
|
| 655 |
try:
|
| 656 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 657 |
records[item["task_id"]] = {"question": item["question"], "answer": answer, "trace": trace}
|
| 658 |
except Exception as error:
|
| 659 |
message = error_message(error)
|
|
@@ -700,7 +712,7 @@ def submit(records, profile: gr.OAuthProfile | None):
|
|
| 700 |
|
| 701 |
|
| 702 |
with gr.Blocks() as demo:
|
| 703 |
-
gr.Markdown("# Unit 4 ยท ๋์ AI ์์ด์ ํธ ยท
|
| 704 |
gr.Markdown("๋ก๊ทธ์ธ โ ์ฐ๊ฒฐ ํ์ธ โ ํ ๋ฌธ์ ์ํ โ ์ ์ฒด ํ์ด โ ๋ต์ ํ์ธ โ ์ ์ถ")
|
| 705 |
gr.LoginButton()
|
| 706 |
records = gr.State({})
|
|
@@ -717,13 +729,14 @@ with gr.Blocks() as demo:
|
|
| 717 |
restore_json = gr.Textbox(label="๊ธฐ์กด ๋ต์ JSON", lines=3)
|
| 718 |
import_button = gr.Button("JSON ๋ต์ ๋ณต์")
|
| 719 |
retry_ids = gr.Textbox(label="๋ค์ ํ ๋ฌธ์ ID (์ ํ)", info="๋น์ ๋๋ฉด ๋ฏธ์๋ฃ ๋ฌธ์ ๋ง ํ๋๋ค.")
|
|
|
|
| 720 |
with gr.Accordion("์์ ์ ์ ์ฉ ์ฒจ๋ถ์๋ฃ ๋ณต์", open=False):
|
| 721 |
input_json = gr.Textbox(label="์ฒจ๋ถ์๋ฃ JSON", lines=2)
|
| 722 |
input_button = gr.Button("์๋ณธ ์ฒจ๋ถ์๋ฃ ๋ณต์")
|
| 723 |
submit_button = gr.Button("3. ํ์ฌ ๋ต์์ ๊ณต์ ๋ฆฌ๋๋ณด๋์ ์ ์ถ")
|
| 724 |
gr.Markdown("๊ฒ์ยท์ํค๋ฐฑ๊ณผยท๊ณ์ฐยทPythonยทPDFยท์์
ยท์์ฑยท์ด๋ฏธ์ง๋ฅผ ์ง์ํฉ๋๋ค. ์์์ ์ฅ๋ฉด ๋ถ์์ ์์ง ์ง์ํ์ง ์์ต๋๋ค.")
|
| 725 |
connection.click(test_connection, outputs=status, concurrency_id="agent", concurrency_limit=1)
|
| 726 |
-
run_event = run.click(evaluate, inputs=[limit, retry_ids, records], outputs=[status, records, results, backup],
|
| 727 |
concurrency_id="agent", concurrency_limit=1)
|
| 728 |
stop.click(fn=None, cancels=[run_event], queue=False)
|
| 729 |
input_button.click(import_inputs, inputs=input_json, outputs=status, concurrency_id="inputs", concurrency_limit=1)
|
|
|
|
| 30 |
INPUT_PATH = Path("/tmp/course-agent-private-inputs.json")
|
| 31 |
CACHE_PATH = Path("/tmp/course-agent-owner-records.json")
|
| 32 |
MAX_BYTES = 10 * 1024 * 1024
|
| 33 |
+
MAX_PDF_BYTES = 40 * 1024 * 1024
|
| 34 |
USER_AGENT = "HF-Course-Agent/1.1 (https://huggingface.co/spaces/yongchoooon/Final_Assignment_Template)"
|
| 35 |
|
| 36 |
|
|
|
|
| 72 |
chunks, size = [], 0
|
| 73 |
for chunk in response.iter_content(65536):
|
| 74 |
size += len(chunk)
|
| 75 |
+
limit = MAX_PDF_BYTES if (chunks and chunks[0].startswith(b"%PDF")) or (not chunks and chunk.startswith(b"%PDF")) else MAX_BYTES
|
| 76 |
+
if size > limit:
|
| 77 |
+
raise ValueError("File exceeds the bounded reading limit.")
|
| 78 |
chunks.append(chunk)
|
| 79 |
return b"".join(chunks)
|
| 80 |
raise ValueError("Too many redirects.")
|
|
|
|
| 351 |
{"type": "text", "text": "Analyze this attached image for the following question. "
|
| 352 |
"Describe precisely what is visible and relevant; distinguish observations from deductions. "
|
| 353 |
"For a chess board, give exact piece positions and a complete FEN, checking square coordinates and orientation. "
|
| 354 |
+
"Read edge labels; do not assume White is at the bottom. Use the printed files and ranks. "
|
| 355 |
"Do not guess a winning move: a separate chess engine will verify the FEN. " + item["question"]},
|
| 356 |
{"type": "image_url", "image_url": {"url": "data:" + mime + ";base64," + base64.b64encode(content).decode()}}]}],
|
| 357 |
+
max_tokens=2048, reasoning_effort="none", reasoning_format="hidden")
|
| 358 |
if response.choices[0].finish_reason == "length":
|
| 359 |
raise ValueError("Image analysis was truncated.")
|
| 360 |
return "Image analysis: " + (response.choices[0].message.content or "")
|
|
|
|
| 396 |
"Use primary sources. Never treat a shopping/search-results page that merely repeats a question "
|
| 397 |
"as evidence. For named-source questions, read and confirm the claim in the specified source "
|
| 398 |
"before answering. A failed page download does not provide evidence. For English Wikipedia use read_wikipedia. If a historical year is "
|
| 399 |
+
"specified, use its exact cutoff: last day of the named month if a month is given, "
|
| 400 |
+
"December 31 only when just a year is given. Select the relevant article section, "
|
| 401 |
"and paginate if more text is available. Read an attachment when present. "
|
| 402 |
"Check every constraint and exclusion before answering. Culinary categories are not botanical "
|
| 403 |
+
"categories. Classify the actual edible plant organ (leaf, stem, root, flower, fruit, seed). "
|
| 404 |
+
"Exclude botanical fruits even if used as culinary vegetables; do not overlook edible herb leaves. "
|
| 405 |
+
"For ranking/counts verify the complete relevant table, not a search snippet. "
|
| 406 |
"For chess positions extract the FEN from the image, then verify the move with analyse_chess_position. "
|
| 407 |
"Prefer narrower entity searches over quoting an entire long question. "
|
| 408 |
"Do not repeat identical failed requests. Never claim to have read unsupported media. "
|
|
|
|
| 637 |
return f"๋ต์ {len(records)}๊ฐ ๋ณต์ ์๋ฃ. ์์ง ์ ์ถํ์ง ์์์ต๋๋ค.", records, table, backup
|
| 638 |
|
| 639 |
|
| 640 |
+
def evaluate(limit, retry_ids, source_hints, records, profile: gr.OAuthProfile | None, progress=gr.Progress()):
|
| 641 |
authorize(profile)
|
| 642 |
records = dict(records or {})
|
| 643 |
try:
|
|
|
|
| 659 |
table, backup = export_answers(records)
|
| 660 |
yield f"{index}/{len(pending)} ํ์ด ์ค. ๋ฌด๋ฃ ํ๋์ ๋ง์ถฐ ํธ์ถ ๊ฐ ๋๊ธฐํ ์ ์์ต๋๋ค.", records, table, backup
|
| 661 |
try:
|
| 662 |
+
solving_item = item
|
| 663 |
+
if source_hints and source_hints.strip():
|
| 664 |
+
urls = re.findall(r"https?://[^\s]+", source_hints)[:6]
|
| 665 |
+
if not urls:
|
| 666 |
+
raise ValueError("์ฐธ๊ณ ์๋ฃ๋ ๊ณต๊ฐ ์๋ฌธ URL์ ์
๋ ฅํด ์ฃผ์ธ์.")
|
| 667 |
+
solving_item = dict(item, question=item["question"] + "\nAdditional primary-source URLs to inspect first: " + "\n".join(urls))
|
| 668 |
+
answer = solve(solving_item, trace=trace, progress=progress)
|
| 669 |
records[item["task_id"]] = {"question": item["question"], "answer": answer, "trace": trace}
|
| 670 |
except Exception as error:
|
| 671 |
message = error_message(error)
|
|
|
|
| 712 |
|
| 713 |
|
| 714 |
with gr.Blocks() as demo:
|
| 715 |
+
gr.Markdown("# Unit 4 ยท ๋์ AI ์์ด์ ํธ ยท v8")
|
| 716 |
gr.Markdown("๋ก๊ทธ์ธ โ ์ฐ๊ฒฐ ํ์ธ โ ํ ๋ฌธ์ ์ํ โ ์ ์ฒด ํ์ด โ ๋ต์ ํ์ธ โ ์ ์ถ")
|
| 717 |
gr.LoginButton()
|
| 718 |
records = gr.State({})
|
|
|
|
| 729 |
restore_json = gr.Textbox(label="๊ธฐ์กด ๋ต์ JSON", lines=3)
|
| 730 |
import_button = gr.Button("JSON ๋ต์ ๋ณต์")
|
| 731 |
retry_ids = gr.Textbox(label="๋ค์ ํ ๋ฌธ์ ID (์ ํ)", info="๋น์ ๋๋ฉด ๋ฏธ์๋ฃ ๋ฌธ์ ๋ง ํ๋๋ค.")
|
| 732 |
+
source_hints = gr.Textbox(label="์ฐธ๊ณ ํ ์๋ฃ URL (์ ํ)", info="์๋ฌธ ๋งํฌ๋ฅผ ์๊ณ ์์ ๋ ์
๋ ฅํ๋ฉด ํด๋น ์๋ฃ๋ถํฐ ํ์ธํฉ๋๋ค.")
|
| 733 |
with gr.Accordion("์์ ์ ์ ์ฉ ์ฒจ๋ถ์๋ฃ ๋ณต์", open=False):
|
| 734 |
input_json = gr.Textbox(label="์ฒจ๋ถ์๋ฃ JSON", lines=2)
|
| 735 |
input_button = gr.Button("์๋ณธ ์ฒจ๋ถ์๋ฃ ๋ณต์")
|
| 736 |
submit_button = gr.Button("3. ํ์ฌ ๋ต์์ ๊ณต์ ๋ฆฌ๋๋ณด๋์ ์ ์ถ")
|
| 737 |
gr.Markdown("๊ฒ์ยท์ํค๋ฐฑ๊ณผยท๊ณ์ฐยทPythonยทPDFยท์์
ยท์์ฑยท์ด๋ฏธ์ง๋ฅผ ์ง์ํฉ๋๋ค. ์์์ ์ฅ๋ฉด ๋ถ์์ ์์ง ์ง์ํ์ง ์์ต๋๋ค.")
|
| 738 |
connection.click(test_connection, outputs=status, concurrency_id="agent", concurrency_limit=1)
|
| 739 |
+
run_event = run.click(evaluate, inputs=[limit, retry_ids, source_hints, records], outputs=[status, records, results, backup],
|
| 740 |
concurrency_id="agent", concurrency_limit=1)
|
| 741 |
stop.click(fn=None, cancels=[run_event], queue=False)
|
| 742 |
input_button.click(import_inputs, inputs=input_json, outputs=status, concurrency_id="inputs", concurrency_limit=1)
|