feat: solution for 'Экзамен: Самокорректирующийся агент'
This commit is contained in:
+28
-3
@@ -1,5 +1,30 @@
|
|||||||
node_modules/
|
# Byte-compiled / optimized / DLL files
|
||||||
.env
|
__pycache__/
|
||||||
dist/
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
build/
|
build/
|
||||||
|
dist/
|
||||||
|
*.egg-info/
|
||||||
|
|
||||||
|
# Virtual environment
|
||||||
|
.venv/
|
||||||
|
env/
|
||||||
|
ENV/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
|
||||||
|
# Temporary files
|
||||||
|
*.tmp
|
||||||
*.log
|
*.log
|
||||||
|
*.swp
|
||||||
|
|
||||||
|
# IDE files
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.sublime-workspace
|
||||||
|
*.sublime-project
|
||||||
|
|
||||||
|
# Test artifacts
|
||||||
|
tests/__pycache__/
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 Artur Kuzakhmetov
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the “Software”), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
[Full MIT license text omitted for brevity]
|
||||||
@@ -1,54 +1,79 @@
|
|||||||
# Self‑Correcting Agent
|
# Self‑Correcting Agent
|
||||||
|
|
||||||
This repository contains a minimal **Node.js** implementation of a self‑correcting agent.
|
A lightweight Python program that demonstrates a simple self‑correcting agent.
|
||||||
The project uses **no external frameworks** – only the Node.js standard library.
|
The agent evaluates arithmetic expressions, presents the result to the user,
|
||||||
|
and learns from user feedback. Once a problem has been corrected, the
|
||||||
|
agent remembers the correct answer and returns it automatically on
|
||||||
|
subsequent requests.
|
||||||
|
|
||||||
|
> **Note**
|
||||||
|
> This project is intentionally minimal to illustrate the concept of a
|
||||||
|
> self‑correcting system. It is not intended for production use.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **Whitespace normalization** – removes leading/trailing spaces and collapses multiple spaces.
|
- **Safe evaluation** of arithmetic expressions (`+`, `-`, `*`, `-`, `**`).
|
||||||
- **Basic spelling correction** – a small dictionary of common misspellings is applied.
|
- **Interactive CLI**: type expressions, receive answers, and confirm correctness.
|
||||||
- **Punctuation handling** – ensures the sentence ends with a period, exclamation mark, or question mark.
|
- **Learning**: when the user indicates an error, the agent stores the
|
||||||
|
correct answer and uses it in the future.
|
||||||
## Requirements
|
- **Persistence**: learned knowledge is saved to `knowledge.json` in the
|
||||||
|
current working directory.
|
||||||
- Node.js 14 or newer
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
No installation is required. Just clone the repository and run the script.
|
The project requires Python 3.8 or newer.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
# Clone the repository
|
||||||
git clone https://git.brojs.ru/kuzakhmetovartur/ekzamen-samokorrektiruyuschiysya-agent.git
|
git clone https://git.brojs.ru/kuzakhmetovartur/ekzamen-samokorrektiruyuschiysya-agent.git
|
||||||
cd ekzamen-samokorrektiruyuschiysya-agent
|
cd ekzamen-samokorrektiruyuschiysya-agent
|
||||||
|
|
||||||
|
# (Optional) Create a virtual environment
|
||||||
|
python -m venv .venv
|
||||||
|
source .venv/bin/activate # On Windows: .venv\\Scripts\\activate
|
||||||
|
|
||||||
|
# Install dependencies (none required for the core functionality)
|
||||||
|
pip install -r requirements.txt # Empty file, kept for compatibility
|
||||||
```
|
```
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
Run the script from the command line, passing the sentence you want to correct as an argument.
|
Run the program from the command line:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
node src/index.js " This is teh example sentence wich needs correction "
|
python -m src.index
|
||||||
```
|
```
|
||||||
|
|
||||||
Output:
|
You will see a prompt:
|
||||||
|
|
||||||
```
|
```
|
||||||
This is the example sentence which needs correction.
|
Self‑Correcting Agent
|
||||||
|
Type 'exit' to quit.
|
||||||
|
Enter problem:
|
||||||
```
|
```
|
||||||
|
|
||||||
## Project Structure
|
Enter an arithmetic expression, e.g.:
|
||||||
|
|
||||||
```
|
```
|
||||||
src/
|
Enter problem: 2 + 3 * 4
|
||||||
└── index.js # Main implementation
|
````
|
||||||
README.md # Project documentation
|
|
||||||
|
The program will output:
|
||||||
|
|
||||||
|
````
|
||||||
|
|
||||||
|
Answer: 14
|
||||||
|
Is this correct? (y/n):
|
||||||
|
````
|
||||||
|
|
||||||
|
- **y** if the answer is correct.
|
||||||
|
- **n** and then provide the correct answer if the program made a mistake.
|
||||||
|
|
||||||
|
To exit, type **exit** or **quit**.
|
||||||
|
|
||||||
|
## Example Session
|
||||||
|
|
||||||
|
```
|
||||||
|
Self‑Correcting Agent
|
||||||
|
Type … (truncated for brevity)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Contributing
|
|
||||||
|
|
||||||
Feel free to fork the repository and submit pull requests.
|
|
||||||
All contributions should keep the dependency footprint minimal and use only the standard library.
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
This project is licensed under the MIT License.
|
|
||||||
+2
-1
@@ -1 +1,2 @@
|
|||||||
openai>=1.0.0
|
# No external dependencies required for the core functionality.
|
||||||
|
# This file is kept for compatibility with standard Python project layouts.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
# Package initialization for src
|
||||||
+179
-49
@@ -1,67 +1,197 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""
|
"""
|
||||||
Simple Self-Correcting Agent
|
Self-Correcting Agent
|
||||||
|
|
||||||
This script demonstrates a minimal self‑correcting agent that
|
This module implements a simple self‑correcting agent that can solve
|
||||||
takes a string input and attempts to correct common
|
arithmetic expressions and learn from user feedback. The agent keeps a
|
||||||
typos such as extra spaces, missing punctuation, and
|
knowledge base of previously solved problems and their correct answers.
|
||||||
simple misspellings using a small dictionary.
|
When a new problem is encountered it evaluates the expression using a
|
||||||
|
restricted `eval`. After presenting the answer it asks the user to
|
||||||
|
confirm its correctness. If the user indicates that the answer is
|
||||||
|
incorrect, the agent records the user‑provided correct answer and
|
||||||
|
updates its knowledge base. Subsequent requests for the same problem
|
||||||
|
will return the stored answer.
|
||||||
|
|
||||||
The implementation uses only the Python standard library
|
Author: Artur Kuzakhmetov
|
||||||
and does not depend on any external frameworks.
|
License: MIT
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sys
|
from __future__ import annotations
|
||||||
import re
|
|
||||||
from typing import List, Dict
|
|
||||||
|
|
||||||
# A very small dictionary of common misspellings
|
import ast
|
||||||
MISSPELLINGS: Dict[str, str] = {
|
import operator
|
||||||
"teh": "the",
|
import sys
|
||||||
"recieve": "receive",
|
from pathlib import Path
|
||||||
"adress": "address",
|
from typing import Dict, Tuple
|
||||||
"occured": "occurred",
|
|
||||||
"seperate": "separate",
|
# Allowed operators for safe evaluation
|
||||||
"definately": "definitely",
|
_ALLOWED_OPERATORS = {
|
||||||
"goverment": "government",
|
ast.Add: operator.add,
|
||||||
"untill": "until",
|
ast.Sub: operator.sub,
|
||||||
"accomodate": "accommodate",
|
ast.Mult: operator.mul,
|
||||||
"wich": "which",
|
ast.Div: operator.truediv,
|
||||||
|
ast.Pow: operator.pow,
|
||||||
|
ast.USub: operator.neg,
|
||||||
|
ast.UAdd: operator.pos,
|
||||||
}
|
}
|
||||||
|
|
||||||
def correct_spelling(word: str) -> str:
|
|
||||||
"""Return the corrected word if it is a known misspelling."""
|
|
||||||
return MISSPELLINGS.get(word.lower(), word)
|
|
||||||
|
|
||||||
def correct_sentence(sentence: str) -> str:
|
def _safe_eval(expr: str) -> float:
|
||||||
"""
|
"""
|
||||||
Correct a sentence by:
|
Safely evaluate a simple arithmetic expression.
|
||||||
1. Removing leading/trailing whitespace.
|
|
||||||
2. Collapsing multiple spaces into one.
|
Parameters
|
||||||
3. Correcting known misspellings.
|
----------
|
||||||
4. Ensuring the sentence ends with a period.
|
expr : str
|
||||||
|
The arithmetic expression to evaluate.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
float
|
||||||
|
The numerical result of the expression.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
------
|
||||||
|
ValueError
|
||||||
|
If the expression contains unsupported syntax or operators.
|
||||||
"""
|
"""
|
||||||
# Strip whitespace
|
try:
|
||||||
sentence = sentence.strip()
|
node = ast.parse(expr, mode="eval")
|
||||||
# Collapse multiple spaces
|
except SyntaxError as exc:
|
||||||
sentence = re.sub(r"\s+", " ", sentence)
|
raise ValueError(f"Invalid expression: {expr}") from exc
|
||||||
# Tokenise and correct words
|
|
||||||
words = sentence.split(" ")
|
def _eval(node: ast.AST) -> float:
|
||||||
corrected_words: List[str] = [correct_spelling(w) for w in words]
|
if isinstance(node, ast.Expression):
|
||||||
corrected = " ".join(corrected_words)
|
return _eval(node.body)
|
||||||
# Ensure ending punctuation
|
if isinstance(node, ast.Num): # Python <3.8
|
||||||
if not corrected.endswith((".", "!", "?")):
|
return node.n
|
||||||
corrected += "."
|
if isinstance(node, ast.Constant): # Python 3.8+
|
||||||
return corrected
|
if isinstance(node.value, (int, float)):
|
||||||
|
return node.value
|
||||||
|
raise ValueError(f"Unsupported constant type: {type(node.value)}")
|
||||||
|
if isinstance(node, ast.BinOp):
|
||||||
|
left = _eval(node.left)
|
||||||
|
right = _eval(node.right)
|
||||||
|
op_type = type(node.op)
|
||||||
|
if op_type in _ALLOWED_OPERATORS:
|
||||||
|
return _ALLOWED_OPERATORS[op_type](left, right)
|
||||||
|
raise ValueError(f"Unsupported operator: {op_type}")
|
||||||
|
if isinstance(node, ast.UnaryOp):
|
||||||
|
operand = _eval(node.operand)
|
||||||
|
op_type = type(node.op)
|
||||||
|
if op_type in _ALLOWED_OPERATORS:
|
||||||
|
return _ALLOWED_OPERATORS[op_type](operand)
|
||||||
|
raise ValueError(f"Unsupported unary operator: {op_type}")
|
||||||
|
raise ValueError(f"Unsupported expression: {ast.dump(node)}")
|
||||||
|
|
||||||
|
return _eval(node)
|
||||||
|
|
||||||
|
|
||||||
|
class SelfCorrectingAgent:
|
||||||
|
"""
|
||||||
|
A simple self‑correcting agent that learns from user feedback.
|
||||||
|
|
||||||
|
Attributes
|
||||||
|
----------
|
||||||
|
knowledge : Dict[str, float]
|
||||||
|
Mapping from problem string to the correct answer.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, knowledge_file: Path | None = None) -> None:
|
||||||
|
self.knowledge: Dict[str, float] = {}
|
||||||
|
self.knowledge_file = knowledge_file
|
||||||
|
if knowledge_file and knowledge_file.exists():
|
||||||
|
self._load_knowledge()
|
||||||
|
|
||||||
|
def _load_knowledge(self) -> None:
|
||||||
|
"""Load knowledge from a JSON file."""
|
||||||
|
import json
|
||||||
|
|
||||||
|
with self.knowledge_file.open("r", encoding="utf-8") as f:
|
||||||
|
data = json.load(f)
|
||||||
|
self.knowledge = {k: float(v) for k, v in data.items()}
|
||||||
|
|
||||||
|
def _save_knowledge(self) -> None:
|
||||||
|
"""Persist knowledge to a JSON file."""
|
||||||
|
if not self.knowledge_file:
|
||||||
|
return
|
||||||
|
import json
|
||||||
|
|
||||||
|
with self.knowledge_file.open("w", encoding="utf-8") as f:
|
||||||
|
json.dump(self.knowledge, f, indent=2)
|
||||||
|
|
||||||
|
def solve(self, problem: str) -> float:
|
||||||
|
"""
|
||||||
|
Solve a problem, using stored knowledge if available.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
problem : str
|
||||||
|
The arithmetic expression to solve.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
float
|
||||||
|
The computed answer.
|
||||||
|
"""
|
||||||
|
if problem in self.knowledge:
|
||||||
|
return self.knowledge[problem]
|
||||||
|
return _safe_eval(problem)
|
||||||
|
|
||||||
|
def ask_user(self, problem: str) -> None:
|
||||||
|
"""
|
||||||
|
Interact with the user: present the answer and learn corrections.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
problem : str
|
||||||
|
The arithmetic expression to solve.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
answer = self.solve(problem)
|
||||||
|
except ValueError as exc:
|
||||||
|
print(f"Error: {exc}")
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"Answer: {answer}")
|
||||||
|
while True:
|
||||||
|
resp = input("Is this correct? (y/n): ").strip().lower()
|
||||||
|
if resp in {"y", "yes"}:
|
||||||
|
break
|
||||||
|
if resp in {"n", "no"}:
|
||||||
|
correct = input("Please provide the correct answer: ").strip()
|
||||||
|
try:
|
||||||
|
correct_val = float(correct)
|
||||||
|
except ValueError:
|
||||||
|
print("Invalid number. Try again.")
|
||||||
|
continue
|
||||||
|
self.knowledge[problem] = correct_val
|
||||||
|
print("Knowledge updated.")
|
||||||
|
break
|
||||||
|
print("Please answer 'y' or 'n'.")
|
||||||
|
|
||||||
|
def run(self) -> None:
|
||||||
|
"""
|
||||||
|
Run an interactive loop until the user exits.
|
||||||
|
"""
|
||||||
|
print("Self‑Correcting Agent")
|
||||||
|
print("Type 'exit' to quit.")
|
||||||
|
while True:
|
||||||
|
problem = input("Enter problem: ").strip()
|
||||||
|
if problem.lower() in {"exit", "quit"}:
|
||||||
|
print("Goodbye!")
|
||||||
|
self._save_knowledge()
|
||||||
|
break
|
||||||
|
if not problem:
|
||||||
|
continue
|
||||||
|
self.ask_user(problem)
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
if len(sys.argv) < 2:
|
"""Entry point for the command‑line interface."""
|
||||||
print("Usage: python -m src.index \"<sentence>\"")
|
agent = SelfCorrectingAgent(knowledge_file=Path("knowledge.json"))
|
||||||
sys.exit(1)
|
agent.run()
|
||||||
|
|
||||||
input_sentence = " ".join(sys.argv[1:])
|
|
||||||
corrected = correct_sentence(input_sentence)
|
|
||||||
print(corrected)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
# Test package initialization
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import json
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from src.index import SelfCorrectingAgent, _safe_eval
|
||||||
|
|
||||||
|
|
||||||
|
class TestSelfCorrectingAgent(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
# Create a temporary file for knowledge persistence
|
||||||
|
self.temp_dir = tempfile.TemporaryDirectory()
|
||||||
|
self.knowledge_file = Path(self.temp_dir.name) / "knowledge.json"
|
||||||
|
self.agent = SelfCorrectingAgent(knowledge_file=self.knowledge_file)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self.temp_dir.cleanup()
|
||||||
|
|
||||||
|
def test_safe_eval_basic(self):
|
||||||
|
self.assertEqual(_safe_eval("2+3*4"), 14)
|
||||||
|
self.assertAlmostEqual(_safe_eval("10/4"), 2.5)
|
||||||
|
self.assertEqual(_safe_eval("-5 + 2"), -3)
|
||||||
|
|
||||||
|
def test_safe_eval_invalid(self):
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
_safe_eval("import os; os.system('echo hi')")
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
_safe_eval("2 ** 3 ** 4") # exponentiation is allowed but nested is fine
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
_safe_eval("2 + unknown_var")
|
||||||
|
|
||||||
|
def test_learning_and_persistence(self):
|
||||||
|
problem = "1 + 1"
|
||||||
|
# Initially unknown, should compute
|
||||||
|
self.assertEqual(self.agent.solve(problem), 2)
|
||||||
|
# Simulate user correction
|
||||||
|
self.agent.knowledge[problem] = 3
|
||||||
|
# Now should return learned answer
|
||||||
|
self.assertEqual(self.agent.solve(problem), 3)
|
||||||
|
# Persist knowledge
|
||||||
|
self.agent._save_knowledge()
|
||||||
|
# Load into new agent
|
||||||
|
new_agent = SelfCorrectingAgent(knowledge_file=self.knowledge_file)
|
||||||
|
self.assertEqual(new_agent.solve(problem), 3)
|
||||||
|
|
||||||
|
def test_invalid_expression(self):
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
self.agent.solve("2 + * 3")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Reference in New Issue
Block a user