AtCoderStudyBooster 0.4.1__tar.gz → 0.4.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/PKG-INFO +2 -1
- atcoderstudybooster-0.4.2/atcdr/ai.py +434 -0
- {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/atcdr/cli.py +2 -2
- atcoderstudybooster-0.4.2/atcdr/util/openai.py +45 -0
- {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/atcdr/util/parse.py +1 -1
- {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/pyproject.toml +2 -1
- {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/requirements-dev.lock +37 -0
- {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/requirements.lock +37 -0
- {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/uv.lock +604 -1
- atcoderstudybooster-0.4.1/atcdr/generate.py +0 -197
- atcoderstudybooster-0.4.1/atcdr/util/gpt.py +0 -116
- {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/.github/workflows/deploy.yaml +0 -0
- {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/.gitignore +0 -0
- {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/.images/demo0-ja.gif +0 -0
- {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/.images/demo0.gif +0 -0
- {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/.images/demo1.png +0 -0
- {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/.images/demo2.png +0 -0
- {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/.images/demo3.png +0 -0
- {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/.images/demo4.png +0 -0
- {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/.pre-commit-config.yaml +0 -0
- {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/.python-version +0 -0
- {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/.vscode/extensions.json +0 -0
- {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/.vscode/setting.json +0 -0
- {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/.vscode/tasks.json +0 -0
- {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/README.ja.md +0 -0
- {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/README.md +0 -0
- {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/atcdr/__init__.py +0 -0
- {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/atcdr/download.py +0 -0
- {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/atcdr/login.py +0 -0
- {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/atcdr/logout.py +0 -0
- {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/atcdr/markdown.py +0 -0
- {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/atcdr/open.py +0 -0
- {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/atcdr/submit.py +0 -0
- {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/atcdr/test.py +0 -0
- {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/atcdr/util/__init__.py +0 -0
- {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/atcdr/util/fileops.py +0 -0
- {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/atcdr/util/filetype.py +0 -0
- {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/atcdr/util/i18n.py +0 -0
- {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/atcdr/util/problem.py +0 -0
- {atcoderstudybooster-0.4.1 → atcoderstudybooster-0.4.2}/atcdr/util/session.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: AtCoderStudyBooster
|
3
|
-
Version: 0.4.
|
3
|
+
Version: 0.4.2
|
4
4
|
Summary: A tool to download and manage AtCoder problems.
|
5
5
|
Project-URL: Homepage, https://github.com/yuta6/AtCoderStudyBooster
|
6
6
|
Author-email: yuta6 <46110512+yuta6@users.noreply.github.com>
|
@@ -9,6 +9,7 @@ Requires-Python: >=3.8
|
|
9
9
|
Requires-Dist: beautifulsoup4
|
10
10
|
Requires-Dist: click-aliases>=1.0.5
|
11
11
|
Requires-Dist: markdownify==0.13.1
|
12
|
+
Requires-Dist: openai>=1.99.6
|
12
13
|
Requires-Dist: pywebview>=5.4
|
13
14
|
Requires-Dist: questionary>=2.0.1
|
14
15
|
Requires-Dist: requests
|
@@ -0,0 +1,434 @@
|
|
1
|
+
import json
|
2
|
+
import random
|
3
|
+
import time
|
4
|
+
from pathlib import Path
|
5
|
+
from typing import Any, Callable, Dict, List, Optional
|
6
|
+
|
7
|
+
import rich_click as click
|
8
|
+
from openai import BadRequestError, OpenAI
|
9
|
+
from rich.console import Console
|
10
|
+
from rich.live import Live
|
11
|
+
from rich.markup import escape
|
12
|
+
from rich.panel import Panel
|
13
|
+
from rich.syntax import Syntax
|
14
|
+
from rich.table import Table
|
15
|
+
from rich.text import Text
|
16
|
+
|
17
|
+
from atcdr.test import (
|
18
|
+
LabeledTestCase,
|
19
|
+
ResultStatus,
|
20
|
+
TestCase,
|
21
|
+
TestRunner,
|
22
|
+
)
|
23
|
+
from atcdr.util.fileops import add_file_selector
|
24
|
+
from atcdr.util.filetype import (
|
25
|
+
COMPILED_LANGUAGES,
|
26
|
+
FILE_EXTENSIONS,
|
27
|
+
INTERPRETED_LANGUAGES,
|
28
|
+
Lang,
|
29
|
+
str2lang,
|
30
|
+
)
|
31
|
+
from atcdr.util.i18n import _
|
32
|
+
from atcdr.util.openai import set_api_key
|
33
|
+
from atcdr.util.parse import ProblemHTML
|
34
|
+
|
35
|
+
|
36
|
+
def render_result_for_GPT(test: TestRunner) -> tuple[str, bool]:
|
37
|
+
results = list(test)
|
38
|
+
match test.info.summary:
|
39
|
+
case ResultStatus.CE:
|
40
|
+
return f'Compile Error \n {test.info.compiler_message}', False
|
41
|
+
case _:
|
42
|
+
message_for_gpt = ''.join(
|
43
|
+
(
|
44
|
+
f'\n{r.label} => {r.result.passed.value}, Execution Time : {r.result.executed_time}\n'
|
45
|
+
f'\nInput :\n{r.testcase.input}\nOutput :\n{r.result.output}\nExpected :\n{r.testcase.output}\n'
|
46
|
+
if r.result.passed == ResultStatus.WA
|
47
|
+
else f'\n{r.label} => {r.result.passed.value}\nInput :\n{r.testcase.input}\nOutput :\n{r.result.output}\n'
|
48
|
+
)
|
49
|
+
for r in results
|
50
|
+
)
|
51
|
+
return message_for_gpt, False
|
52
|
+
|
53
|
+
|
54
|
+
def display_test_results(console: Console, test: TestRunner) -> None:
|
55
|
+
results = list(test)
|
56
|
+
|
57
|
+
table = Table(title='🧪 Test Results')
|
58
|
+
table.add_column('Test Case', style='cyan', no_wrap=True)
|
59
|
+
table.add_column('Status', justify='center', no_wrap=True)
|
60
|
+
table.add_column('Input', style='dim', max_width=30)
|
61
|
+
table.add_column('Output', style='yellow', max_width=30)
|
62
|
+
table.add_column('Expected', style='green', max_width=30)
|
63
|
+
|
64
|
+
for r in results:
|
65
|
+
if r.result.passed == ResultStatus.AC:
|
66
|
+
status = '[green]✅ AC[/green]'
|
67
|
+
elif r.result.passed == ResultStatus.WA:
|
68
|
+
status = '[red]❌ WA[/red]'
|
69
|
+
elif r.result.passed == ResultStatus.TLE:
|
70
|
+
status = '[yellow]⏰ TLE[/yellow]'
|
71
|
+
elif r.result.passed == ResultStatus.RE:
|
72
|
+
status = '[red]💥 RE[/red]'
|
73
|
+
else:
|
74
|
+
status = f'[red]{r.result.passed.value}[/red]'
|
75
|
+
|
76
|
+
input_preview = escape(
|
77
|
+
r.testcase.input.strip()[:50] + '...'
|
78
|
+
if len(r.testcase.input.strip()) > 50
|
79
|
+
else r.testcase.input.strip()
|
80
|
+
)
|
81
|
+
output_preview = escape(
|
82
|
+
r.result.output.strip()[:50] + '...'
|
83
|
+
if len(r.result.output.strip()) > 50
|
84
|
+
else r.result.output.strip()
|
85
|
+
)
|
86
|
+
expected_preview = escape(
|
87
|
+
r.testcase.output.strip()[:50] + '...'
|
88
|
+
if len(r.testcase.output.strip()) > 50
|
89
|
+
else r.testcase.output.strip()
|
90
|
+
)
|
91
|
+
|
92
|
+
table.add_row(r.label, status, input_preview, output_preview, expected_preview)
|
93
|
+
|
94
|
+
console.print(table)
|
95
|
+
|
96
|
+
|
97
|
+
def create_func(labeled_cases: list[LabeledTestCase], model: str):
|
98
|
+
def test_example_case(code: str, language: str) -> str:
|
99
|
+
language_enum: Lang = str2lang(language)
|
100
|
+
source_path = Path(f'{model}{FILE_EXTENSIONS[language_enum]}')
|
101
|
+
source_path.write_text(code, encoding='utf-8')
|
102
|
+
test = TestRunner(str(source_path), labeled_cases)
|
103
|
+
message_for_gpt, _ = render_result_for_GPT(test)
|
104
|
+
return message_for_gpt
|
105
|
+
|
106
|
+
def execute_code(input: Optional[str], code: str, language: str) -> str:
|
107
|
+
language_enum: Lang = str2lang(language)
|
108
|
+
random_name = random.randint(0, 100_000_000)
|
109
|
+
source_path = Path(f'tmp{random_name}{FILE_EXTENSIONS[language_enum]}')
|
110
|
+
source_path.write_text(code, encoding='utf-8')
|
111
|
+
labeled_cases = [LabeledTestCase('case by gpt', TestCase(input or '', ''))]
|
112
|
+
test = TestRunner(str(source_path), labeled_cases)
|
113
|
+
labeled_result = next(test)
|
114
|
+
source_path.unlink(missing_ok=True)
|
115
|
+
return labeled_result.result.output
|
116
|
+
|
117
|
+
return test_example_case, execute_code
|
118
|
+
|
119
|
+
|
120
|
+
def solve_problem(path: Path, lang: Lang, model: str) -> None:
|
121
|
+
console = Console()
|
122
|
+
content = path.read_text(encoding='utf-8')
|
123
|
+
html = ProblemHTML(content)
|
124
|
+
md = html.make_problem_markdown('en')
|
125
|
+
labeled_cases = html.load_labeled_testcase()
|
126
|
+
|
127
|
+
test_example_case, execute_code = create_func(labeled_cases, model)
|
128
|
+
|
129
|
+
# Responses API 形式のツール定義(トップレベル)
|
130
|
+
TOOLS = [
|
131
|
+
{
|
132
|
+
'type': 'function',
|
133
|
+
'name': 'test_example_case',
|
134
|
+
'description': 'Run the given source code against example test cases and return a summarized result.',
|
135
|
+
'parameters': {
|
136
|
+
'type': 'object',
|
137
|
+
'properties': {
|
138
|
+
'code': {'type': 'string'},
|
139
|
+
'language': {
|
140
|
+
'type': 'string',
|
141
|
+
'enum': [
|
142
|
+
lang.value
|
143
|
+
for lang in (COMPILED_LANGUAGES + INTERPRETED_LANGUAGES)
|
144
|
+
],
|
145
|
+
},
|
146
|
+
},
|
147
|
+
'required': ['code', 'language'],
|
148
|
+
'additionalProperties': False,
|
149
|
+
},
|
150
|
+
'strict': True,
|
151
|
+
},
|
152
|
+
{
|
153
|
+
'type': 'function',
|
154
|
+
'name': 'execute_code',
|
155
|
+
'description': 'Execute the given source code with a single input and return the actual output.',
|
156
|
+
'parameters': {
|
157
|
+
'type': 'object',
|
158
|
+
'properties': {
|
159
|
+
'input': {'type': 'string'},
|
160
|
+
'code': {'type': 'string'},
|
161
|
+
'language': {
|
162
|
+
'type': 'string',
|
163
|
+
'enum': [
|
164
|
+
lang.value
|
165
|
+
for lang in (COMPILED_LANGUAGES + INTERPRETED_LANGUAGES)
|
166
|
+
],
|
167
|
+
},
|
168
|
+
},
|
169
|
+
'required': ['input', 'code', 'language'],
|
170
|
+
'additionalProperties': False,
|
171
|
+
},
|
172
|
+
'strict': True,
|
173
|
+
},
|
174
|
+
]
|
175
|
+
|
176
|
+
client = OpenAI()
|
177
|
+
if set_api_key() is None:
|
178
|
+
console.print('[red]OpenAI API key is not set.[/red]')
|
179
|
+
return
|
180
|
+
|
181
|
+
system_prompt = f"""You are a competitive programming assistant for {lang.value}.
|
182
|
+
The user will provide problems in Markdown format.
|
183
|
+
Read the problem carefully and output a complete, correct, and efficient solution in {lang.value}.
|
184
|
+
Use standard input and output. Do not omit any code.
|
185
|
+
Always pay close attention to algorithmic complexity (time and space).
|
186
|
+
Choose the most optimal algorithms and data structures so that the solution runs within time limits even for the largest possible inputs.
|
187
|
+
|
188
|
+
Use the provided tool test_example_case to run the example test cases from the problem statement.
|
189
|
+
If tests do not pass, fix the code and repeat.
|
190
|
+
The last tested code will be automatically saved to a local file on the user's computer.
|
191
|
+
You do not need to include the final source code in your response.
|
192
|
+
Simply confirm to the user that all tests passed, or briefly explain if they did not.
|
193
|
+
Once you run test_example_case, the exact code you tested will already be saved locally on the user's machine, so sending it again in the response is unnecessary."""
|
194
|
+
|
195
|
+
# ツール名→ローカル実装のディスパッチ
|
196
|
+
tool_impl: Dict[str, Callable[..., Any]] = {
|
197
|
+
'test_example_case': test_example_case,
|
198
|
+
'execute_code': lambda **p: execute_code(
|
199
|
+
p.get('input', ''), # ← 空なら空文字に
|
200
|
+
p.get('code', ''),
|
201
|
+
p.get('language', lang.value),
|
202
|
+
),
|
203
|
+
}
|
204
|
+
|
205
|
+
console.print(f'Solving :{path} Language: {lang.value} / Model: {model}')
|
206
|
+
|
207
|
+
context_msgs = [
|
208
|
+
{'role': 'system', 'content': system_prompt},
|
209
|
+
{'role': 'user', 'content': md},
|
210
|
+
]
|
211
|
+
turn = 1
|
212
|
+
assistant_text = Text()
|
213
|
+
|
214
|
+
def call_model():
|
215
|
+
try:
|
216
|
+
return client.responses.create(
|
217
|
+
model=model,
|
218
|
+
input=context_msgs,
|
219
|
+
tools=TOOLS,
|
220
|
+
tool_choice='auto',
|
221
|
+
include=['reasoning.encrypted_content'],
|
222
|
+
store=False,
|
223
|
+
)
|
224
|
+
except BadRequestError as e:
|
225
|
+
body = getattr(getattr(e, 'response', None), 'json', lambda: None)()
|
226
|
+
console.print(
|
227
|
+
Panel.fit(f'{e}\n\n{body}', title='API Error', border_style='red')
|
228
|
+
)
|
229
|
+
raise
|
230
|
+
|
231
|
+
while True:
|
232
|
+
start_time = time.time()
|
233
|
+
with Live(
|
234
|
+
Panel(
|
235
|
+
f'[bold blue]🤔 Thinking... (turn {turn})[/bold blue]\n[dim]Elapsed: 0.0s[/dim]',
|
236
|
+
border_style='blue',
|
237
|
+
),
|
238
|
+
console=console,
|
239
|
+
refresh_per_second=10,
|
240
|
+
) as live:
|
241
|
+
|
242
|
+
def update_timer():
|
243
|
+
elapsed = time.time() - start_time
|
244
|
+
live.update(
|
245
|
+
Panel(
|
246
|
+
f'[bold blue]🤔 Thinking... (turn {turn})[/bold blue]\n[dim]Elapsed: {elapsed:.1f}s[/dim]',
|
247
|
+
border_style='blue',
|
248
|
+
)
|
249
|
+
)
|
250
|
+
|
251
|
+
import threading
|
252
|
+
|
253
|
+
resp = None
|
254
|
+
error = None
|
255
|
+
|
256
|
+
def model_call():
|
257
|
+
nonlocal resp, error
|
258
|
+
try:
|
259
|
+
resp = call_model()
|
260
|
+
except Exception as e:
|
261
|
+
error = e
|
262
|
+
|
263
|
+
thread = threading.Thread(target=model_call)
|
264
|
+
thread.start()
|
265
|
+
|
266
|
+
while thread.is_alive():
|
267
|
+
update_timer()
|
268
|
+
time.sleep(0.1)
|
269
|
+
|
270
|
+
thread.join()
|
271
|
+
|
272
|
+
if error:
|
273
|
+
raise error
|
274
|
+
|
275
|
+
elapsed = time.time() - start_time
|
276
|
+
live.update(
|
277
|
+
Panel(
|
278
|
+
f'[bold green]✓ Completed thinking (turn {turn})[/bold green]\n[dim]Time taken: {elapsed:.1f}s[/dim]',
|
279
|
+
border_style='green',
|
280
|
+
)
|
281
|
+
)
|
282
|
+
|
283
|
+
# Display token usage
|
284
|
+
if resp and hasattr(resp, 'usage') and resp.usage:
|
285
|
+
usage = resp.usage
|
286
|
+
input_tokens = getattr(usage, 'input_tokens', 0)
|
287
|
+
output_tokens = getattr(usage, 'output_tokens', 0)
|
288
|
+
total_tokens = getattr(usage, 'total_tokens', 0)
|
289
|
+
|
290
|
+
# Check for cached tokens
|
291
|
+
cached_tokens = 0
|
292
|
+
if hasattr(usage, 'input_tokens_details'):
|
293
|
+
details = usage.input_tokens_details
|
294
|
+
if hasattr(details, 'cached_tokens'):
|
295
|
+
cached_tokens = details.cached_tokens
|
296
|
+
|
297
|
+
# Check for reasoning tokens
|
298
|
+
reasoning_tokens = 0
|
299
|
+
if hasattr(usage, 'output_tokens_details'):
|
300
|
+
details = usage.output_tokens_details
|
301
|
+
if hasattr(details, 'reasoning_tokens'):
|
302
|
+
reasoning_tokens = details.reasoning_tokens
|
303
|
+
|
304
|
+
token_msg = f'[dim]Tokens - Input: {input_tokens:,}'
|
305
|
+
if cached_tokens > 0:
|
306
|
+
token_msg += f' (cached: {cached_tokens:,})'
|
307
|
+
token_msg += f' | Output: {output_tokens:,}'
|
308
|
+
if reasoning_tokens > 0:
|
309
|
+
token_msg += f' (reasoning: {reasoning_tokens:,})'
|
310
|
+
token_msg += f' | Total: {total_tokens:,}[/dim]'
|
311
|
+
console.print(token_msg)
|
312
|
+
|
313
|
+
if resp and getattr(resp, 'output_text', None):
|
314
|
+
assistant_text.append(resp.output_text)
|
315
|
+
|
316
|
+
output_content = str(resp.output_text).strip()
|
317
|
+
if any(
|
318
|
+
keyword in output_content
|
319
|
+
for keyword in [
|
320
|
+
'def ',
|
321
|
+
'class ',
|
322
|
+
'import ',
|
323
|
+
'from ',
|
324
|
+
'#include',
|
325
|
+
'public class',
|
326
|
+
]
|
327
|
+
):
|
328
|
+
try:
|
329
|
+
syntax = Syntax(
|
330
|
+
output_content, lang, theme='monokai', line_numbers=True
|
331
|
+
)
|
332
|
+
console.print(
|
333
|
+
Panel(
|
334
|
+
syntax,
|
335
|
+
title='Assistant Output (Code)',
|
336
|
+
border_style='green',
|
337
|
+
)
|
338
|
+
)
|
339
|
+
except Exception:
|
340
|
+
console.print(
|
341
|
+
Panel(
|
342
|
+
assistant_text,
|
343
|
+
title='Assistant Output',
|
344
|
+
border_style='green',
|
345
|
+
)
|
346
|
+
)
|
347
|
+
else:
|
348
|
+
console.print(
|
349
|
+
Panel(
|
350
|
+
assistant_text, title='Assistant Output', border_style='green'
|
351
|
+
)
|
352
|
+
)
|
353
|
+
|
354
|
+
if resp and hasattr(resp, 'output'):
|
355
|
+
context_msgs += resp.output
|
356
|
+
|
357
|
+
# function_call を収集
|
358
|
+
calls: List[dict] = []
|
359
|
+
for o in resp.output:
|
360
|
+
if getattr(o, 'type', '') == 'function_call':
|
361
|
+
try:
|
362
|
+
args = json.loads(o.arguments or '{}')
|
363
|
+
except Exception:
|
364
|
+
args = {}
|
365
|
+
call_id = getattr(o, 'call_id', None) or getattr(
|
366
|
+
o, 'id'
|
367
|
+
) # ★ ここがポイント
|
368
|
+
calls.append({'name': o.name, 'call_id': call_id, 'args': args})
|
369
|
+
else:
|
370
|
+
calls = []
|
371
|
+
|
372
|
+
if not calls:
|
373
|
+
console.print(
|
374
|
+
Panel.fit('✅ Done (no more tool calls).', border_style='green')
|
375
|
+
)
|
376
|
+
break
|
377
|
+
|
378
|
+
# ツールを実行し、function_call_output を context に積む
|
379
|
+
for c in calls:
|
380
|
+
args_str = json.dumps(c['args'], ensure_ascii=False) if c['args'] else ''
|
381
|
+
console.print(
|
382
|
+
Panel.fit(
|
383
|
+
f"Tool: [bold]{c['name']}[/bold]\nargs: {args_str}",
|
384
|
+
title=f"function_call ({c['call_id']})",
|
385
|
+
border_style='cyan',
|
386
|
+
)
|
387
|
+
)
|
388
|
+
|
389
|
+
impl = tool_impl.get(c['name'])
|
390
|
+
if not impl:
|
391
|
+
out = f"[ERROR] Unknown tool: {c['name']}"
|
392
|
+
else:
|
393
|
+
try:
|
394
|
+
with console.status(f"Running {c['name']}...", spinner='dots'):
|
395
|
+
out = impl(**c['args']) if c['args'] else impl()
|
396
|
+
except TypeError:
|
397
|
+
out = impl(
|
398
|
+
**{
|
399
|
+
k: v
|
400
|
+
for k, v in c['args'].items()
|
401
|
+
if k in impl.__code__.co_varnames
|
402
|
+
}
|
403
|
+
)
|
404
|
+
except Exception as e:
|
405
|
+
out = f"[Tool '{c['name']}' error] {e}"
|
406
|
+
|
407
|
+
console.print(
|
408
|
+
Panel(
|
409
|
+
str(out) or '(no output)',
|
410
|
+
title=f"{c['name']} result",
|
411
|
+
border_style='magenta',
|
412
|
+
)
|
413
|
+
)
|
414
|
+
|
415
|
+
context_msgs.append(
|
416
|
+
{
|
417
|
+
'type': 'function_call_output',
|
418
|
+
'call_id': c['call_id'],
|
419
|
+
'output': str(out),
|
420
|
+
}
|
421
|
+
)
|
422
|
+
|
423
|
+
turn += 1
|
424
|
+
|
425
|
+
|
426
|
+
@click.command(short_help=_('cmd_generate'), help=_('cmd_generate'))
|
427
|
+
@add_file_selector('files', filetypes=[Lang.HTML])
|
428
|
+
@click.option('--lang', default='Python', help=_('opt_output_lang'))
|
429
|
+
@click.option('--model', default='gpt-5-mini', help=_('opt_model'))
|
430
|
+
def ai(files, lang, model):
|
431
|
+
"""HTMLファイルからコード生成またはテンプレート出力を行います。"""
|
432
|
+
lang_enum: Lang = str2lang(lang)
|
433
|
+
for path in files:
|
434
|
+
solve_problem(Path(path), lang_enum, model)
|
@@ -8,8 +8,8 @@ from rich.table import Table
|
|
8
8
|
from rich.traceback import install
|
9
9
|
from rich_click import RichGroup
|
10
10
|
|
11
|
+
from atcdr.ai import ai
|
11
12
|
from atcdr.download import download
|
12
|
-
from atcdr.generate import generate
|
13
13
|
from atcdr.login import login
|
14
14
|
from atcdr.logout import logout
|
15
15
|
from atcdr.markdown import markdown
|
@@ -76,7 +76,7 @@ def cli():
|
|
76
76
|
cli.add_command(test, aliases=['t'])
|
77
77
|
cli.add_command(download, aliases=['d'])
|
78
78
|
cli.add_command(open_files, 'open', aliases=['o'])
|
79
|
-
cli.add_command(
|
79
|
+
cli.add_command(ai)
|
80
80
|
cli.add_command(markdown, aliases=['md'])
|
81
81
|
cli.add_command(submit, aliases=['s'])
|
82
82
|
cli.add_command(login)
|
@@ -0,0 +1,45 @@
|
|
1
|
+
import os
|
2
|
+
from typing import Optional
|
3
|
+
|
4
|
+
import requests
|
5
|
+
|
6
|
+
from atcdr.util.i18n import _
|
7
|
+
|
8
|
+
|
9
|
+
def set_api_key() -> Optional[str]:
|
10
|
+
api_key = os.getenv('OPENAI_API_KEY')
|
11
|
+
if api_key and validate_api_key(api_key):
|
12
|
+
return api_key
|
13
|
+
elif api_key:
|
14
|
+
print(_('api_key_validation_failed'))
|
15
|
+
else:
|
16
|
+
pass
|
17
|
+
|
18
|
+
api_key = input(_('get_api_key_prompt'))
|
19
|
+
if validate_api_key(api_key):
|
20
|
+
print(_('api_key_test_success'))
|
21
|
+
print(_('save_api_key_prompt'))
|
22
|
+
if input() == 'y':
|
23
|
+
zshrc_path = os.path.expanduser('~/.zshrc')
|
24
|
+
with open(zshrc_path, 'a') as f:
|
25
|
+
f.write(f'export OPENAI_API_KEY={api_key}\n')
|
26
|
+
print(_('api_key_saved', zshrc_path))
|
27
|
+
os.environ['OPENAI_API_KEY'] = api_key
|
28
|
+
return api_key
|
29
|
+
else:
|
30
|
+
print(_('api_key_required'))
|
31
|
+
return None
|
32
|
+
|
33
|
+
|
34
|
+
def validate_api_key(api_key: str) -> bool:
|
35
|
+
headers = {
|
36
|
+
'Content-Type': 'application/json',
|
37
|
+
'Authorization': f'Bearer {api_key}',
|
38
|
+
}
|
39
|
+
|
40
|
+
response = requests.get('https://api.openai.com/v1/models', headers=headers)
|
41
|
+
if response.status_code == 200:
|
42
|
+
return True
|
43
|
+
else:
|
44
|
+
print(_('api_key_validation_error'))
|
45
|
+
return False
|
@@ -110,7 +110,7 @@ class ProblemHTML(HTML):
|
|
110
110
|
def load_labeled_testcase(self) -> List:
|
111
111
|
from atcdr.test import LabeledTestCase, TestCase
|
112
112
|
|
113
|
-
problem_part = self.abstract_problem_part(
|
113
|
+
problem_part = self.abstract_problem_part('en')
|
114
114
|
if problem_part is None:
|
115
115
|
return []
|
116
116
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[project]
|
2
2
|
name = "AtCoderStudyBooster"
|
3
|
-
version = "0.4.
|
3
|
+
version = "0.4.2"
|
4
4
|
description = "A tool to download and manage AtCoder problems."
|
5
5
|
authors = [
|
6
6
|
{ name = "yuta6", email = "46110512+yuta6@users.noreply.github.com" }
|
@@ -17,6 +17,7 @@ dependencies = [
|
|
17
17
|
"pywebview>=5.4",
|
18
18
|
"rich-click>=1.8.8",
|
19
19
|
"click-aliases>=1.0.5",
|
20
|
+
"openai>=1.99.6",
|
20
21
|
]
|
21
22
|
readme = "README.md"
|
22
23
|
requires-python = ">= 3.8"
|
@@ -10,12 +10,19 @@
|
|
10
10
|
# universal: false
|
11
11
|
|
12
12
|
-e file:.
|
13
|
+
annotated-types==0.7.0
|
14
|
+
# via pydantic
|
15
|
+
anyio==4.10.0
|
16
|
+
# via httpx
|
17
|
+
# via openai
|
13
18
|
beautifulsoup4==4.12.3
|
14
19
|
# via atcoderstudybooster
|
15
20
|
# via markdownify
|
16
21
|
bottle==0.13.2
|
17
22
|
# via pywebview
|
18
23
|
certifi==2024.7.4
|
24
|
+
# via httpcore
|
25
|
+
# via httpx
|
19
26
|
# via requests
|
20
27
|
cfgv==3.4.0
|
21
28
|
# via pre-commit
|
@@ -28,14 +35,26 @@ click-aliases==1.0.5
|
|
28
35
|
# via atcoderstudybooster
|
29
36
|
distlib==0.3.8
|
30
37
|
# via virtualenv
|
38
|
+
distro==1.9.0
|
39
|
+
# via openai
|
31
40
|
filelock==3.15.4
|
32
41
|
# via virtualenv
|
42
|
+
h11==0.16.0
|
43
|
+
# via httpcore
|
44
|
+
httpcore==1.0.9
|
45
|
+
# via httpx
|
46
|
+
httpx==0.28.1
|
47
|
+
# via openai
|
33
48
|
identify==2.6.0
|
34
49
|
# via pre-commit
|
35
50
|
idna==3.7
|
51
|
+
# via anyio
|
52
|
+
# via httpx
|
36
53
|
# via requests
|
37
54
|
iniconfig==2.0.0
|
38
55
|
# via pytest
|
56
|
+
jiter==0.10.0
|
57
|
+
# via openai
|
39
58
|
markdown-it-py==3.0.0
|
40
59
|
# via rich
|
41
60
|
markdownify==0.13.1
|
@@ -47,6 +66,8 @@ mypy-extensions==1.0.0
|
|
47
66
|
# via mypy
|
48
67
|
nodeenv==1.9.1
|
49
68
|
# via pre-commit
|
69
|
+
openai==1.99.9
|
70
|
+
# via atcoderstudybooster
|
50
71
|
packaging==24.1
|
51
72
|
# via pytest
|
52
73
|
platformdirs==4.2.2
|
@@ -58,6 +79,10 @@ prompt-toolkit==3.0.36
|
|
58
79
|
# via questionary
|
59
80
|
proxy-tools==0.1.0
|
60
81
|
# via pywebview
|
82
|
+
pydantic==2.11.7
|
83
|
+
# via openai
|
84
|
+
pydantic-core==2.33.2
|
85
|
+
# via pydantic
|
61
86
|
pygments==2.18.0
|
62
87
|
# via rich
|
63
88
|
pytest==8.3.2
|
@@ -79,10 +104,15 @@ rich-click==1.8.8
|
|
79
104
|
# via atcoderstudybooster
|
80
105
|
six==1.16.0
|
81
106
|
# via markdownify
|
107
|
+
sniffio==1.3.1
|
108
|
+
# via anyio
|
109
|
+
# via openai
|
82
110
|
soupsieve==2.5
|
83
111
|
# via beautifulsoup4
|
84
112
|
tiktoken==0.7.0
|
85
113
|
# via atcoderstudybooster
|
114
|
+
tqdm==4.67.1
|
115
|
+
# via openai
|
86
116
|
types-beautifulsoup4==4.12.0.20240511
|
87
117
|
# via atcoderstudybooster
|
88
118
|
types-html5lib==1.1.11.20240806
|
@@ -90,9 +120,16 @@ types-html5lib==1.1.11.20240806
|
|
90
120
|
types-requests==2.32.0.20240712
|
91
121
|
# via atcoderstudybooster
|
92
122
|
typing-extensions==4.12.2
|
123
|
+
# via anyio
|
93
124
|
# via mypy
|
125
|
+
# via openai
|
126
|
+
# via pydantic
|
127
|
+
# via pydantic-core
|
94
128
|
# via pywebview
|
95
129
|
# via rich-click
|
130
|
+
# via typing-inspection
|
131
|
+
typing-inspection==0.4.1
|
132
|
+
# via pydantic
|
96
133
|
urllib3==2.2.2
|
97
134
|
# via requests
|
98
135
|
# via types-requests
|