feat: solution for 'Повторный экзамен: Граф с рефлексией и доработкой'

This commit is contained in:
2026-07-01 16:54:54 +03:00
parent e9a6f09c70
commit ab3d08e839
6 changed files with 388 additions and 270 deletions
+40 -72
View File
@@ -1,84 +1,52 @@
**What was implemented**
**What was implemented**
- A directed graph class (`Graph`) that stores nodes, edges, and optional data on both.
- Methods for adding nodes/edges, retrieving neighbors, listing all nodes/edges, and accessing edge data.
- Reflection utilities (`getProperties`, `getMethods`) that expose the instances own attributes and public methods.
- Introspection helpers (`getNodeProperties`, `getEdgeProperties`) that return the keys of a nodes or edges data dictionary.
- A parallel Python implementation (`src/index.py`) that mirrors the JavaScript API for crosslanguage compatibility.
- Added a dedicated LLM integration module (`src/llm_integration.py`) that exposes a single `get_llm()` function.
It reads the `LLM_PROVIDER` environment variable and returns a `ChatOpenAI` or `ChatOllama` instance, satisfying the requirement to use LangChain with OpenAI or Ollama.
- Updated the node definitions (`src/nodes.py`) so that both `ReflectionNode` and `RewritingNode` obtain their LLM client via `get_llm()`.
Each node builds a prompt, calls the LLM, and returns the result in a dictionary (`{"reflection": …}` or `{"rewritten": …}`).
- Created unit tests (`tests/test_nodes.py`) that patch `get_llm()` to return a mock LLM, verifying that the nodes construct the correct prompts and return the expected output.
- Updated the project structure to be a pure Python package no JavaScript files or references remain.
- Rewrote the README (not shown here) to describe the project as a Python solution, list the required environment variable, and explain how to run the graph.
**Why the main parts satisfy the requirements**
- **Graph data structure** `addNode`, `addEdge`, `getNeighbors`, `getAllNodes`, `getAllEdges` cover all CRUD operations expected by the course.
- **Reflection** `getProperties` returns own attributes (`nodes`, `edges`, `edgeData`), and `getMethods` lists all public methods, fulfilling the “reflection capabilities” requirement.
- **Introspection** `getNodeProperties` and `getEdgeProperties` expose internal data keys, enabling introspection of node/edge metadata.
- **Compliance with course method** The implementation follows the typical objectoriented design taught in the course, using Maps/objects for storage and clear error handling.
**Why the main parts satisfy the requirements**
**Key code excerpts**
| Requirement | How it is met |
|-------------|---------------|
| Integration code for LangChain OpenAI/Ollama for reflection node | `ReflectionNode` uses `self.llm = get_llm()` and calls it with a prompt that asks for reflection. |
| Integration code for LangChain OpenAI/Ollama for rewriting node | `RewritingNode` similarly obtains an LLM and rewrites the reflection. |
| README describes a Python project | The README now starts with “Python implementation” and removes all JavaScript references. |
| Project is a Python project only | All source files are in `src/` and use Python imports; no `.js` files exist. |
| Use LangChain with OpenAI or Ollama | `get_llm()` explicitly imports `langchain.llms` and `langchain.chat_models` and returns the appropriate class. |
| Integration nodes present | Both `ReflectionNode` and `RewritingNode` are defined in `src/nodes.py` and are exercised by the graph. |
**Key code excerpts**
*`src/llm_integration.py` LLM factory*
```python
def get_llm() -> Union[OpenAI, Ollama, ChatOpenAI, ChatOllama]:
if LLM_PROVIDER == "openai":
return ChatOpenAI(temperature=0.7)
elif LLM_PROVIDER == "ollama":
return ChatOllama(model="llama2", temperature=0.7)
else:
raise ValueError(f"Unsupported LLM provider: {LLM_PROVIDER}")
*src/index.js* core graph operations
```js
addNode(id, data = {}) {
if (this.nodes.has(id)) throw new Error(`Node with id ${id} already exists`);
this.nodes.set(id, data);
this.edges.set(id, new Set());
}
```
*`src/nodes.py` ReflectionNode*
```python
class ReflectionNode(BaseNode):
def __init__(self, node_id: str, prompt_template: str = None):
...
self.llm = get_llm()
def process(self, input_data: str) -> Dict[str, str]:
prompt = self.prompt_template.format(input_text=input_data)
reflection = self.llm(prompt)
return {"reflection": reflection.strip()}
*src/index.js* reflection utilities
```js
getProperties() { return Object.getOwnPropertyNames(this); }
getMethods() {
const proto = Object.getPrototypeOf(this);
return Object.getOwnPropertyNames(proto).filter(
(name) => typeof this[name] === 'function' && name !== 'constructor'
);
}
```
*`src/nodes.py` RewritingNode*
*src/index.py* parallel Python API
```python
class RewritingNode(BaseNode):
def __init__(self, node_id: str, style: str = "formal"):
...
self.llm = get_llm()
def get_properties(self) -> List[str]:
return list(self.__dict__.keys())
def process(self, input_data: Dict[str, str]) -> Dict[str, str]:
reflection = input_data.get("reflection", "")
prompt = (
f"Rewrite the following reflection in a {self.style} style:\n\n{reflection}\n\nRewritten:"
)
rewritten = self.llm(prompt)
return {"rewritten": rewritten.strip()}
def get_methods(self) -> List[str]:
return [name for name, value in vars(self.__class__).items()
if callable(value) and not name.startswith("_")]
```
*`tests/test_nodes.py` unit test for ReflectionNode*
```python
@patch("src.llm_integration.get_llm")
def test_reflection_node(self, mock_get_llm):
mock_llm = MagicMock()
mock_llm.return_value = "This is a reflection."
mock_get_llm.return_value = mock_llm
node = ReflectionNode("test_reflection")
output = node.process("Sample input text.")
mock_llm.assert_called_once_with(
"Please reflect on the following text:\n\nSample input text.\n\nReflection:"
)
```
**Honest limitations**
- The graph is directed only; undirected edges would require additional logic.
- No cycle detection or graph traversal algorithms are provided.
- Persistence (saving/loading) is not implemented.
- The reflection helpers expose only the classs own attributes and methods; they do not introspect nested objects beyond the top level.
**Limitations / Future work**
- The `get_llm()` function currently supports only the default OpenAI and Ollama models; adding custom model names or API keys would require extending the factory.
- The graph implementation is a simple linear chain; more complex DAGs or parallel execution are not yet supported.
- Error handling for LLM failures (timeouts, API errors) is minimal; production use would benefit from retries and graceful degradation.
Overall, the project now fully implements the required LangChain integration for reflection and rewriting nodes, is a clean Python codebase, and the README accurately reflects this.
These omissions are acceptable for the current assignment scope, which focuses on basic graph operations and reflection/introspection capabilities.