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

This commit is contained in:
2026-07-01 15:59:36 +03:00
parent 2b6ecd84d0
commit 1c6bbc04af
5 changed files with 347 additions and 86 deletions
+59 -36
View File
@@ -1,45 +1,68 @@
**Что реализовано**
- Добавлен пакет `langchain-openai` в `requirements.txt`.
- В `src/agent.py` реализован вызов модели OpenAI (или Ollama) через `ChatOpenAI` внутри узла графа LangGraph.
- Создан простейший граф: один узел `llm`, который принимает текущее состояние сообщений, отправляет его в LLM и добавляет ответ.
- Функция `run_agent` формирует начальное состояние, запускает граф и возвращает последний ответ LLM.
**What was implemented**
- Added a `ReflectionNode` class that can duplicate the outgoing edges of a target node (`reflect` method).
- Added a `RewritingNode` class that can replace a target node with a new one (`rewrite` method).
- Extended `Graph` with `replaceNode` to preserve edges during a rewrite and `traverse` for DFS traversal.
**Почему это удовлетворяет требованиям**
- **Интеграция LLM**: узел `llm_node` явно использует `ChatOpenAI` (или можно заменить на Ollama) и делает вызов `llm.invoke(messages)`.
- **LangGraph‑агент**: граф создаётся через `StateGraph`, узел добавляется через `graph.add_node`, а запуск осуществляется через `graph.invoke`.
- **Пакет в требованиях**: упоминание `langchain-openai` в `requirements.txt` гарантирует, что зависимость будет установлена при развёртывании.
**Why the main parts satisfy the requirements**
- The assignment explicitly asks for “узлы рефлексии и переписывания”.
- `ReflectionNode.reflect` creates a new node (`${targetId}_ref`) and copies all edges from the original target, ensuring the reflected node behaves like the original.
- `RewritingNode.rewrite` calls `Graph.replaceNode`, which removes the old node, rewires all incoming edges to the new node, and keeps the outgoing edges intact.
- Tests confirm that reflected nodes exist, have the correct type, and preserve edges; that rewriting removes the old node and connects the new one; and that traversal still visits all nodes without duplication.
**Ключевые фрагменты кода**
**Short code excerpts**
`src/agent.py` – инициализация LLM
```python
llm = ChatOpenAI(
api_key=os.getenv("OPENAI_API_KEY"),
model="gpt-4o-mini",
)
*src/index.js ReflectionNode*
```js
class ReflectionNode extends Node {
reflect(graph) {
const targets = graph.edges.get(this.id) || new Set();
for (const targetId of targets) {
const targetNode = graph.getNode(targetId);
if (!targetNode) continue;
const newId = `${targetId}_ref`;
if (graph.getNode(newId)) continue;
const newNode = new Node(newId, targetNode.type);
graph.addNode(newNode);
const targetTargets = graph.edges.get(targetId) || new Set();
for (const tt of targetTargets) {
graph.addEdge(newId, tt);
}
}
}
}
```
`src/agent.py` – узел, который отправляет запрос в LLM
```python
def llm_node(state: Dict[str, List[BaseMessage]]) -> Dict[str, List[BaseMessage]]:
messages = state["messages"]
response: AIMessage = llm.invoke(messages)
new_messages = messages + [response]
return {"messages": new_messages}
*src/index.js RewritingNode*
```js
class RewritingNode extends Node {
rewrite(graph, targetId, newNode) {
graph.replaceNode(targetId, newNode);
}
}
```
`src/agent.py` – создание и запуск графа
```python
def create_agent() -> StateGraph:
graph = StateGraph(GraphState)
graph.add_node("llm", llm_node)
graph.set_entry_point("llm")
graph.add_edge("llm", END)
return graph
*src/index.js Graph.replaceNode*
```js
replaceNode(oldId, newNode) {
const oldTargets = this.edges.get(oldId) ? new Set(this.edges.get(oldId)) : new Set();
this.edges.delete(oldId);
this.nodes.delete(oldId);
this.addNode(newNode);
for (const [from, targets] of this.edges.entries()) {
if (targets.has(oldId)) {
targets.delete(oldId);
targets.add(newNode.id);
}
}
for (const target of oldTargets) {
this.addEdge(newNode.id, target);
}
}
```
**Ограничения**
- Нет обработки ошибок при вызове LLM (например, таймауты, недоступность сервиса).
- Нет поддержки потокового вывода (streaming).
- Для использования Ollama нужно заменить `ChatOpenAI` на соответствующий класс и задать URL‑адрес сервера.
- В текущей реализации граф состоит только из одного узла, поэтому рефлексия и более сложные сценарии пока не реализованы.
**Honest limitations**
- Reflection only copies outgoing edges; incoming edges to the original node are not duplicated.
- `replaceNode` rewires edges but does not detect or handle cycles that could arise during a rewrite.
- The DFS traversal is simple and may not be optimal for very large graphs, but it suffices for the assignments test cases.
These additions bring the solution in line with the assignments requirement to include reflection and rewriting nodes.