Skip to content

Commit 34a5509

Browse files
authored
[FEATURE] Add LLM Grading
2 parents 9599413 + 05793a6 commit 34a5509

26 files changed

Lines changed: 1925 additions & 15 deletions
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of TYPO3 CMS-based extension "aim" by b13.
7+
*
8+
* It is free software; you can redistribute it and/or modify it under
9+
* the terms of the GNU General Public License, either version 2
10+
* of the License, or any later version.
11+
*/
12+
13+
namespace B13\Aim\Command;
14+
15+
use B13\Aim\Domain\Repository\RequestLogRepository;
16+
use B13\Aim\Service\GradingService;
17+
use Symfony\Component\Console\Attribute\AsCommand;
18+
use Symfony\Component\Console\Command\Command;
19+
use Symfony\Component\Console\Input\InputInterface;
20+
use Symfony\Component\Console\Input\InputOption;
21+
use Symfony\Component\Console\Output\OutputInterface;
22+
23+
/**
24+
* Scheduler safety-net: grade request_log rows that were marked pending but
25+
* never finished, typically because the request didn't run under PHP-FPM
26+
* (CLI, daemon, crash) and register_shutdown_function in GraderMiddleware
27+
* couldn't reach a successful grade.
28+
*
29+
* Picks up rows older than --min-age (default 60s) to avoid racing the
30+
* shutdown handler for live requests. Mark as failed if grading itself
31+
* errors, so a retry loop is opt-in via --retry-failed (future).
32+
*/
33+
#[AsCommand(
34+
name: 'aim:grade-pending',
35+
description: 'Grade tx_aim_request_log rows still marked grade_status=pending.',
36+
)]
37+
final class GradePendingLogs extends Command
38+
{
39+
public function __construct(
40+
private readonly RequestLogRepository $logRepository,
41+
private readonly GradingService $gradingService,
42+
) {
43+
parent::__construct();
44+
}
45+
46+
protected function configure(): void
47+
{
48+
$this
49+
->setHelp(
50+
'Grades AiM request log rows where grade_status="pending" and the row is at '
51+
. 'least --min-age seconds old. Intended as a safety-net behind GraderMiddleware\'s '
52+
. 'shutdown-function path. Run it from the TYPO3 scheduler every few minutes.'
53+
)
54+
->addOption(
55+
'limit',
56+
null,
57+
InputOption::VALUE_REQUIRED,
58+
'Maximum number of rows to grade in this run.',
59+
'50',
60+
)
61+
->addOption(
62+
'min-age',
63+
null,
64+
InputOption::VALUE_REQUIRED,
65+
'Only pick rows older than this many seconds (avoid racing the live shutdown handler).',
66+
'60',
67+
);
68+
}
69+
70+
protected function execute(InputInterface $input, OutputInterface $output): int
71+
{
72+
$limit = max(1, (int)$input->getOption('limit'));
73+
$minAge = max(0, (int)$input->getOption('min-age'));
74+
75+
$rows = $this->logRepository->findPendingGrades($minAge, $limit);
76+
if ($rows === []) {
77+
$output->writeln('No pending grades older than ' . $minAge . 's.');
78+
return Command::SUCCESS;
79+
}
80+
81+
$output->writeln(sprintf('Grading %d pending row(s).', count($rows)));
82+
$graded = 0;
83+
$failed = 0;
84+
foreach ($rows as $row) {
85+
$uid = (int)$row['uid'];
86+
try {
87+
$this->gradingService->grade($uid);
88+
$output->writeln(' - graded uid ' . $uid);
89+
$graded++;
90+
} catch (\Throwable $e) {
91+
$output->writeln(sprintf(' - <error>uid %d failed: %s</error>', $uid, $e->getMessage()));
92+
$failed++;
93+
}
94+
}
95+
96+
$output->writeln(sprintf('<info>Done: %d graded, %d failed.</info>', $graded, $failed));
97+
return $failed > 0 ? Command::FAILURE : Command::SUCCESS;
98+
}
99+
}

0 commit comments

Comments
 (0)