跳到正文
DEV Community· Carlos José Castro Galante·· 3 小時前AI 評分49

我教 Gemma 4 閲讀大學課程大綱,讓教授不必親自處理

I Taught Gemma 4 to Read University Syllabi So Professors Don't Have To

AI 導讀

Tramo 是一款以 Gemma 4 為核心的開源助理,可比對大學課程大綱並草擬學分認可報告,教授保留最終決定權。它透過 Ollama 在本機運行,模型為各課題提出判斷並列出依據,程式則計算內容覆蓋率及套用院系規定,文件不會離開使用者的電腦。教授可更改課題判斷,系統會即時重新計算結果。

正文

This is a submission for the Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady

What I Built

My request to have a single course recognized was filed in 2025 and resolved in September 2026. In Argentina that isn't unusual. When a student moves between universities, a professor has to read two long syllabi side by side, match topics by hand, count them against a percentage written in a regulation and write down what the student still has to take, and the case then climbs through faculty offices and councils. I'm not writing this to argue with the outcome of my own case. I'm writing it because most of the waiting happens around reading and counting, which is exactly the kind of work a model does well, while the decision itself should always stay with a person.

So I built Tramo, an open source assistant that drafts course equivalency reports with Gemma 4 running locally through Ollama.

You give it the analytic program of the subject a student already passed and the program of the subject they want recognized, as a PDF, a Word file or pasted text. Gemma extracts the teaching topics of both programs, compares every destination topic against the whole origin program, labels it as covered, partial or without evidence and explains each judgment with the origin topics it relied on. Plain code then computes the coverage and applies the rule of my faculty, which only allows a partial equivalency when more than half of the destination program is covered, and it lists the topics the student would still have to take. The professor can overrule any topic, and every number is recalculated on the spot.

The professor gets to be lazy about the part of the job that never needed a professor, which is reading and counting, and keeps all of the part that does, which is deciding. Students get an answer sooner. And no document ever leaves the computer, because there's no cloud anywhere in the loop.

What an equivalency is, for readers outside Latin America

Every course in an Argentine university has an analytic program, a document that lists its units and topics in detail. When you change careers or universities, you can ask the new institution to recognize courses you already passed, and the institution compares both programs to decide whether the content is equivalent. At my faculty, the Facultad de Tecnología y Ciencias Aplicadas of the Universidad Nacional de Catamarca, the regulation says that a partial equivalency can only be granted when the equivalent content exceeds 50 percent of the destination program, and that the professor must then list the topics the student has to take before the course counts as passed. It's a fair rule, but applying it means reading and counting dozens of topics by hand for every single request.

What a professor can do with Tramo

A professor can paste both programs or drop a PDF or a Word file on each field, check and correct the extracted text before running anything, and try the whole flow in seconds with a built in sample pair of programs. The analysis runs on the same computer and shows its progress as it happens. The report presents the coverage against the 50 percent threshold, the proposed verdict, a count of topics by status, the topics still to be taken, a written recommendation and the detail of every topic with its evidence and its reasoning. The list can be filtered by status, any status can be changed with a single selector, and the final report can be copied as text or printed and saved as a PDF that carries a notice saying it's a draft generated by AI.

Tramo means a stretch of road in Spanish, and the interface borrows its language from road markings. A covered topic gets a continuous line, a partial one gets a dashed line and a topic without evidence gets a dotted line, so a professor can scan a long report the way you read the lanes ahead of you.

Demo

Here's the complete flow in under a minute and a half, from loading two programs to overruling the model and reading the limits of the tool. The video has no voice, only text on screen, and the analysis is sped up eight times so you don't have to sit through the wait.

The interface is in Spanish because it's built for Argentine faculties, so here's what each screen does.

Tramo home page with the title from syllabus to equivalency report and a road marking illustration
The home page explains in one sentence what Tramo does and who makes the final decision.

Tramo form with both study programs loaded and a responsibility notice above the generate button
Both programs are loaded, and the note above the button explains what Tramo is and what it isn't before anything runs.

Tramo analysis screen with the title filling as Gemma 4 processes the syllabi
While Gemma works, the title and the road fill with the progress reported by the pipeline itself, stage by stage, next to an elapsed timer.

Tramo equivalency report with coverage meter, 50 percent threshold and verdict
The report shows coverage against the 50 percent threshold, the verdict that follows from the regulation and a count of topics by status.

Tramo topic detail showing cited origin topics and the reasoning of the model
Every topic cites the origin topics behind its status and explains the reasoning in plain language.

Tramo report after the professor changes a topic status, with recalculated coverage
When the professor changes a status, code recalculates everything and the AI recommendation disappears, because it was written for a report that no longer exists.

Tramo limits and responsibility section explaining what the AI does and who decides
The page explains what the AI does, when it can be wrong and who is responsible for the result.

Code

GitHub logo carlosjcastro / tramo

Local AI assistant that compares two course syllabi and drafts a university equivalency report. Open weights, private, the professor decides.

Tramo

Tramo, from syllabus to equivalency report

Watch the demo on YouTube License MIT Gemma 4 with Ollama

Tramo compares the analytic program of a subject a student has already passed with the program of the subject they want recognized, and drafts an equivalency report for the professor who reviews the case. It runs entirely on the computer of whoever uses it, with an open weights model served by Ollama, so the documents never leave the machine.

The software does not decide anything. The model proposes a status for every topic and explains its reasoning with evidence taken from the documents, plain code computes the coverage and applies the equivalency rule, and the professor remains free to overrule any single judgment before signing. The final resolution belongs to the university authorities.

The problem it addresses

Universities in Argentina and across Latin America resolve equivalencies by hand, and a single case often takes many months. The reviewing professor has to read two long analytic programs side by side…

Tramo is MIT licensed. The repository includes 119 tests, a security policy with private vulnerability reporting, a contributing guide and a README that documents the architecture, every setting and the known limits in detail.

If you have Ollama installed, you can run it in a few minutes.

git clone https://github.com/carlosjcastro/tramo
cd tramo
python -m venv .venv
.venv\Scripts\activate
pip install -r requirements_dev.txt
ollama pull gemma4:e4b
python -m pytest
python -m uvicorn app.api:app

On Linux and macOS, activate the environment with source .venv/bin/activate. Then open http://localhost:8000, press the button that loads the sample programs and generate your first report.

How I Built It

Tramo runs Gemma 4 (e4b) locally through Ollama. The backend is Python with FastAPI and Pydantic, and the interface is plain HTML, CSS and JavaScript modules with no framework and no build step. I developed it on a computer with an RTX 4050 and 24 GB of RAM, where Gemma runs fully on the GPU at about 59 tokens per second.

Layer Choice
Model Gemma 4 e4b, open weights
Inference Ollama on the same machine, structured outputs with a JSON schema
Backend Python, FastAPI, Pydantic
Interface HTML, CSS and ES modules, no framework, strict content security policy
Data Processed in memory, never stored or logged
Tests 119 unit and API tests with fake model clients
License MIT

I chose the e4b size of Gemma 4 because it fits entirely on the GPU of a consumer card with a context of 12288 tokens, it handled programs written in Spanish well, and it's small enough that a faculty could run it on a computer it already owns.

The model proposes, the code decides

I designed everything around one rule, which is that Gemma never produces a number that the interface presents as a fact. The pipeline has four stages, and only three of them call the model.

  1. Topic extraction. Gemma reads each program and returns its teaching topics through the structured outputs of Ollama, so every call sends the JSON schema the answer has to follow, with temperature zero. If the answer doesn't validate, a rule based splitter takes over and the report says so, because a silent fallback would be worse than an honest one.
  2. Topic assessment. Destination topics go to the model in batches of twelve, and each one is compared against the complete list of origin topics. The model labels it as covered, partial or without evidence, cites up to five origin topics and writes a short reason. The code then checks that every topic was assessed exactly once and that every cited source actually exists in the origin program.
  3. The rule. Plain Python computes the coverage, counting a covered topic as one and a partial one as half, and applies the regulation of my faculty. Article 87 allows a partial equivalency only above 50 percent, and article 88 asks for the list of topics still to be taken. No text inside a document can change this result, because the model never touches it.
  4. The recommendation. Gemma writes a short recommendation from facts the code has already computed, including the longest runs of consecutive topics without evidence. Before the text reaches the screen, any sentence with a number the code didn't produce is removed. I deliberately didn't use embeddings or retrieval for the comparison. A syllabus fits comfortably in the context window, so every destination topic sees the whole origin program instead of a few chunks picked by similarity, and topics that are worded very differently in each university still get a fair reading.

Structured outputs with Ollama

The heart of every model call is the same pattern. Ollama receives the JSON schema of a Pydantic model, Gemma answers inside that shape, and Pydantic validates the result before anything else touches it. This is a simplified version of the call.

response = client.chat(
    model="gemma4:e4b",
    messages=messages,
    format=TopicList.model_json_schema(),
    options={"temperature": 0, "num_ctx": 12288},
    think=False,
)
topics = TopicList.model_validate_json(response.message.content)

The rule that decides the verdict is the opposite of a model call. It's a few lines of arithmetic that anyone can read and test, which is exactly why it lives in code. This simplified version shows the idea.

WEIGHTS = {"cubierto": 1.0, "parcial": 0.5, "sin_evidencia": 0.0}

def coverage(statuses):
    return sum(WEIGHTS[status] for status in statuses) / len(statuses)

What broke along the way

Three moments shaped the final design more than any plan did.

My first version didn't use a model to read the programs at all. It split them with rules, and on one test it reported 55 percent coverage while proposing that the student retake the names of the professors and the weekly hours of the course. That report convinced me that reading had to be done by a model and counting by code, and never the other way around.

The second moment came from the thinking mode of Gemma 4. With thinking on, Ollama ignored my JSON schema and returned plain text, and a complete analysis took five and a half minutes. I wrote a small benchmark to compare both settings, and with thinking off the model returned valid JSON for the extraction in about 4.6 seconds. The whole analysis dropped to about a minute and a half, so thinking is off by default and a single environment variable turns it back on. The benchmark lives in the repository, so anyone can repeat the comparison on their own hardware.

The third one was a lesson in humility. Running the same pair of programs several times gave coverage between 24 and 32 percent while the verdict stayed the same. Temperature zero doesn't make a local model fully deterministic, and pretending otherwise would be dishonest. That's why every topic shows its reasoning, why the professor can change any status and why the numbers always come from code.

Streaming the progress

A minute and a half is long enough to wonder whether something froze, so the analysis is streamed. The backend runs the pipeline as a generator and sends newline delimited JSON events, and the browser reads them as they arrive.

{"type": "progress", "done": 4, "total": 9}
{"type": "report", "report": {"coverage": 0.31, "verdict": "...", "matches": []}}

Each stage has a weight, so the percentage on screen comes from completed work and never from a timer. When the professor overrules a status, the browser sends the edited list back and the server recalculates the coverage, the verdict and the pending topics with the same code that produced the original report.

After the progress screen looked frozen on my own machine because of the reduced motion setting of the system, I also added a toggle for animations. It starts from the preference of the operating system, remembers the choice, and with animations off the progress stays visible through the title, the bar and the percentage.

Treating every document as hostile

Tramo takes files from users and feeds their text to a model, so it treats every input as untrusted.

Uploads have to be PDF or Word files verified by their signature and not by their extension, and their size is capped before they're stored in memory. PDFs with scripts, launch actions or attachments are rejected, PDFs longer than 100 pages are refused, and Word files with macros, ActiveX controls, path traversal or an abnormal expanded size are rejected too. Hidden and control characters are removed from every text, and file names are cleaned before they're shown.

Program text reaches the model between delimiters as data, the delimiters are stripped from the input, and any text that tries to give the model instructions raises a visible warning in the report. Even a successful injection couldn't change the verdict, because the verdict is computed by code.

Requests are rate limited per IP address, with three analyses every ten minutes, twenty uploads per minute and one analysis running at a time. Request bodies are capped while they're being read, the Host header is validated, the API documentation is disabled, responses carry a strict content security policy, and errors never echo the submitted text or expose internals. Documents are processed in memory and never stored or logged.

Accessible and honest by design

The interface works with a keyboard, announces progress and errors to screen readers, adapts from phones to wide screens and follows the light or dark preference of the system. The content security policy forbids inline scripts and styles, so even the progress bar is driven through the CSS object model.

An equivalency is an administrative decision about a person, so honesty is part of the product. The interface says what Tramo is before anything runs, and the report, including its printed version, says that it's a draft that can contain mistakes and doesn't replace the professor or the authorities who resolve the case. A dedicated section explains what the model does, when it can be wrong, how copyright and personal data are handled and who is responsible for the result. Tramo is an independent project and it has no endorsement from any university.

What it can't do yet

Tramo compares text, so it doesn't know the practical work, the bibliography or the depth with which each course was actually taught. It can't read scanned PDFs without a text layer, and its prompts still need calibration with professors and with the official equivalency tables of each institution. The next steps I have in mind are a second verification pass for topics labeled as covered, a button to cancel a running analysis, support for other regulations through configurable rules and an export of the report to Word.

Why Does Open Innovation Matter?

The documents around an equivalency belong to a student, and sending them to a closed API means agreements, consent forms and trust in the retention policy of a company. Many faculties can't arrange that quickly, and some never will. With open weights running locally that obstacle simply isn't there, because nothing leaves the computer and there's no account, key or invoice involved. Once the model is downloaded, Tramo doesn't even need an internet connection.

Openness also makes the result auditable. The model, the prompts, the schemas and the rule live in the same public repository, so anyone reviewing a report can see exactly how it was produced. A university council can't inspect a closed model, but it can read this code and run it on its own machines.

The tool also belongs to whoever uses it. A faculty with a different regulation can change the threshold with one variable, swap Gemma for another model in Ollama or rewrite the rule in a single Python module, without asking anyone for permission. The MIT license lets any university adopt it for free, and I'd love to hear from the ones that do.

A closed API would have made privacy a promise, while here it's a property of the architecture.

The Lazy Payoff

I haven't timed a professor doing this by hand, so I won't invent a number. What I can measure is the machine side. On my computer, Tramo reads both programs, assesses every destination topic and drafts the report in about a minute and a half, and the professor starts from a list where each topic already carries its evidence and its mark. Reviewing that list is still work, but it's the kind of work that needs a professor.

This week I've been the only user. I ran Tramo many times on syllabi from two Argentine universities, the same kind of documents that sat in my own case, and every run gave me a report I could audit topic by topic in a few minutes. The next step is putting it in front of the professors who review these cases every semester and calibrating the prompts with them.

Prize Categories

Gemma. Every model call in Tramo, from topic extraction to the written recommendation, runs on Gemma 4 locally through Ollama, and the whole design depends on an open model that a faculty can run on its own hardware.

If you've ever changed universities or careers, how long did it take to get your courses recognized, and what would have made that wait shorter for you?

來源:DEV Community · dev.to