WUT_Computer_Science/Programming/NLP/test_goldStandard/chunks.py
Krzysztof kuhy Rudnicki b017c7500e NLP: zero findings, and two real bugs found on the way
384 findings to none across ten scripts. Most of it is annotation and
docstring work, but the lint surfaced two defects that had nothing to do with
style:

- `gpt_chunks.py` stripped its GPT preamble with
  `input_string.strip("iSTS chunks:")`. str.strip takes a SET OF CHARACTERS,
  not a prefix, so it also ate any leading i/S/T/s/c/h/u/n/k/space/colon:
  "iSTS chunks: sentence one" came out as "entence one", silently losing the
  first letter of every chunked sentence. Now removeprefix. ruff found it as
  B005 + PLE1310.
- `is_numbered_line_format` had `for string in string` in its list branch,
  which raises NameError the moment a list is passed (F821).
- `processing.test_XML()` read a module-level `alignments_data` that does not
  exist at module scope; it only ever ran when pasted into a live session. It
  takes the data as an argument now and is renamed `log_first_alignment` so
  pytest stops collecting a non-test as a test.

Also real rather than cosmetic:

- The OpenAI key was a placeholder string literal in three files, one of them
  carrying a "Replace me with os.environ['API_KEY']" comment. All three read
  OPENAI_API_KEY now and say so when it is missing -- as committed, none of
  these scripts could actually run.
- `rating.rate_similarity` was submitted unfinished: it built a client, never
  touched its two arguments and returned None despite `-> str`. It now makes
  the same call the rest of the package makes.
- The two 2 kB alignment prompts and one 344-character chunking prompt moved
  to prompts/*.txt. Wrapping them in source would have put newlines into the
  middle of the prompt.
- `format_alignments` parsed GPT's replies with `eval()`; the file holds one
  repr()'d string per line, so ast.literal_eval is both sufficient and safe.
- NLP/__init__.py deleted -- it made the course directory a package named
  "NLP", which is an invalid module name (N999) and which nothing imports;
  the scripts use plain `import processing`.

INP001 is settled the same way throughout: a coursework file run as
`python x.py` gets a shebang and the executable bit, which is what it is,
rather than an __init__.py that would make a course code into a module name.

Verified: every module compiles and `./run.sh run Programming/NLP` is clean.
The scripts themselves need the SemEval corpus and a paid API key, so they
are not executed here.

Repo-wide: 3,159 findings -> 2,775.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0172dj34FDpao6wsix8ZuCzE
2026-08-28 20:13:19 +02:00

70 lines
2.4 KiB
Python
Executable File

#!/usr/bin/env python3
"""Ask GPT to divide the student-answer sentences into iSTS chunks.
The earlier, simpler sibling of ``../gpt_chunks.py``: no format detection and
no reformatting pass, just one request per sentence with the reply written
straight out. Its output is ``chunks_gpt_two.txt`` next to this file.
The prompt lives in ``prompt.txt`` because it is a single 373-character line
that cannot be wrapped without changing the prompt itself.
"""
import logging
import os
from pathlib import Path
from openai import OpenAI
logger = logging.getLogger(__name__)
PROMPT = (Path(__file__).parent / "prompt.txt").read_text(encoding="utf-8").strip()
def sentence_loop(client: OpenAI, sentences: list[str]) -> list[str]:
"""Chunk every sentence, in order, and collect the replies."""
chunked_sentences: list[str] = []
for sentence in sentences:
chunked_sentences = process_sentence(client, sentence, chunked_sentences)
return chunked_sentences
def process_sentence(
client: OpenAI, sentence: str, chunked_sentences: list[str]
) -> list[str]:
"""Chunk one sentence and append the reply to *chunked_sentences*."""
response = client.chat.completions.create(
messages=[{"role": "user", "content": PROMPT + sentence}],
model="gpt-3.5-turbo",
)
chunked_sentence = str(response.choices[0].message.content)
logger.info("chunked_sentence: %s", chunked_sentence)
chunked_sentences.append(chunked_sentence)
return chunked_sentences
def chunk_sentences(file_path: str, output_path: str) -> None:
"""Chunk every sentence in *file_path* and write them to *output_path*."""
# The key was a literal here with a "Replace me with os.environ" comment
# next to it, which is now done rather than noted.
key = os.environ.get("OPENAI_API_KEY")
if not key:
msg = "set OPENAI_API_KEY to run the chunking script"
raise RuntimeError(msg)
client = OpenAI(api_key=key)
with Path(file_path).open(encoding="utf-8") as file:
sentences = file.readlines()
chunked = sentence_loop(client, sentences)
logger.info("%s", chunked)
with Path(output_path).open("w", encoding="utf-8") as output_file:
output_file.writelines(sentence + "\n" for sentence in chunked)
if __name__ == "__main__":
logging.basicConfig(format="%(message)s", level=logging.INFO)
chunk_sentences(
"student/STSint.testinput.answers-students.sent2.txt",
"chunks_gpt_two.txt",
)