feat: solution for 'Повторный экзамен: Граф с рефлексией и доработкой'
This commit is contained in:
+59
-36
@@ -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 assignment’s test cases.
|
||||
|
||||
These additions bring the solution in line with the assignment’s requirement to include reflection and rewriting nodes.
|
||||
Reference in New Issue
Block a user