feat: solution for 'Повторный экзамен: Граф с рефлексией и доработкой'
This commit is contained in:
+40
-72
@@ -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 instance’s own attributes and public methods.
|
||||
- Introspection helpers (`getNodeProperties`, `getEdgeProperties`) that return the keys of a node’s or edge’s data dictionary.
|
||||
- A parallel Python implementation (`src/index.py`) that mirrors the JavaScript API for cross‑language 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 object‑oriented 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 class’s 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.
|
||||
Reference in New Issue
Block a user