deep-agent-cli 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- app/__init__.py +0 -0
- app/core/LLM/__init__.py +3 -0
- app/core/LLM/anthropic_provider.py +10 -0
- app/core/LLM/factory.py +74 -0
- app/core/LLM/google_provider.py +19 -0
- app/core/LLM/ollama_provider.py +12 -0
- app/core/LLM/openai_provider.py +10 -0
- app/core/__init__.py +4 -0
- app/core/agent/__init__.py +3 -0
- app/core/agent/deep_agent.py +109 -0
- app/core/checkpoint/__init__.py +3 -0
- app/core/checkpoint/factory.py +54 -0
- app/service/ChatService.py +245 -0
- app/service/ConfigService.py +222 -0
- app/service/__init__.py +4 -0
- deep_agent_cli-1.0.0.dist-info/METADATA +225 -0
- deep_agent_cli-1.0.0.dist-info/RECORD +28 -0
- deep_agent_cli-1.0.0.dist-info/WHEEL +5 -0
- deep_agent_cli-1.0.0.dist-info/entry_points.txt +2 -0
- deep_agent_cli-1.0.0.dist-info/top_level.txt +3 -0
- main.py +32 -0
- ui/__init__.py +1 -0
- ui/controllers/ChatController.py +298 -0
- ui/controllers/__init__.py +1 -0
- ui/models/ChatModel.py +58 -0
- ui/models/__init__.py +1 -0
- ui/views/ChatView.py +237 -0
- ui/views/__init__.py +1 -0
app/__init__.py
ADDED
|
File without changes
|
app/core/LLM/__init__.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from langchain_anthropic import ChatAnthropic
|
|
2
|
+
from typing import Any, Dict, Optional
|
|
3
|
+
|
|
4
|
+
def create_anthropic_model(model_name: str, temperature: float, api_key: Optional[str], **extra_params: Any) -> ChatAnthropic:
|
|
5
|
+
return ChatAnthropic(
|
|
6
|
+
model=model_name,
|
|
7
|
+
temperature=temperature,
|
|
8
|
+
anthropic_api_key=api_key,
|
|
9
|
+
**extra_params
|
|
10
|
+
)
|
app/core/LLM/factory.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
from typing import Optional, Dict, Any
|
|
2
|
+
from pydantic import BaseModel, Field
|
|
3
|
+
from langchain_core.language_models.chat_models import BaseChatModel
|
|
4
|
+
|
|
5
|
+
class LLMConfig(BaseModel):
|
|
6
|
+
"""Cấu hình cho việc khởi tạo LLM."""
|
|
7
|
+
provider: str = Field(..., description="Nhà cung cấp (openai, anthropic, google, ollama, etc.)")
|
|
8
|
+
model_name: str = Field(..., description="Tên model cụ thể (gpt-4, claude-3, gemini-pro, etc.)")
|
|
9
|
+
temperature: float = Field(default=0, description="Độ sáng tạo của model (0-1)")
|
|
10
|
+
api_key: Optional[str] = Field(None, description="API Key nếu cần thiết")
|
|
11
|
+
base_url: Optional[str] = Field(None, description="Base URL cho các provider như Ollama hoặc Local LLMs")
|
|
12
|
+
extra_params: Dict[str, Any] = Field(default_factory=dict, description="Các tham số bổ sung khác")
|
|
13
|
+
|
|
14
|
+
class LLMFactory:
|
|
15
|
+
"""
|
|
16
|
+
Factory class để quản lý việc khởi tạo các LLM từ nhiều nhà cung cấp khác nhau.
|
|
17
|
+
Logic khởi tạo cụ thể được tách ra các file provider riêng biệt.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
@staticmethod
|
|
21
|
+
def create_llm(config: LLMConfig) -> BaseChatModel:
|
|
22
|
+
"""
|
|
23
|
+
Khởi tạo và trả về một instance chuẩn của BaseChatModel.
|
|
24
|
+
Sử dụng Lazy Import để tránh lỗi khi thiếu thư viện của các provider không dùng tới.
|
|
25
|
+
"""
|
|
26
|
+
provider = config.provider.lower()
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
if provider == "openai":
|
|
30
|
+
from app.core.LLM.openai_provider import create_openai_model
|
|
31
|
+
return create_openai_model(
|
|
32
|
+
model_name=config.model_name,
|
|
33
|
+
temperature=config.temperature,
|
|
34
|
+
api_key=config.api_key,
|
|
35
|
+
**config.extra_params
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
elif provider == "anthropic":
|
|
39
|
+
from app.core.LLM.anthropic_provider import create_anthropic_model
|
|
40
|
+
return create_anthropic_model(
|
|
41
|
+
model_name=config.model_name,
|
|
42
|
+
temperature=config.temperature,
|
|
43
|
+
api_key=config.api_key,
|
|
44
|
+
**config.extra_params
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
elif provider == "google":
|
|
48
|
+
from app.core.LLM.google_provider import create_google_model
|
|
49
|
+
return create_google_model(
|
|
50
|
+
model_name=config.model_name,
|
|
51
|
+
temperature=config.temperature,
|
|
52
|
+
api_key=config.api_key,
|
|
53
|
+
**config.extra_params
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
elif provider == "ollama":
|
|
57
|
+
from app.core.LLM.ollama_provider import create_ollama_model
|
|
58
|
+
return create_ollama_model(
|
|
59
|
+
model_name=config.model_name,
|
|
60
|
+
temperature=config.temperature,
|
|
61
|
+
base_url=config.base_url,
|
|
62
|
+
**config.extra_params
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
else:
|
|
66
|
+
raise ValueError(f"Provider '{provider}' hiện chưa được hỗ trợ bởi Factory.")
|
|
67
|
+
|
|
68
|
+
except ImportError as e:
|
|
69
|
+
raise ImportError(
|
|
70
|
+
f"Lỗi: Thiếu thư viện cho provider '{provider}' hoặc lỗi cấu trúc file. "
|
|
71
|
+
f"Chi tiết: {str(e)}"
|
|
72
|
+
)
|
|
73
|
+
except Exception as e:
|
|
74
|
+
raise Exception(f"Lỗi hệ thống khi khởi tạo LLM ({provider}): {str(e)}")
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from typing import Optional, Any
|
|
3
|
+
|
|
4
|
+
from langchain_google_genai import ChatGoogleGenerativeAI
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def create_google_model(model_name: str, temperature: float, api_key: Optional[str],
|
|
8
|
+
**extra_params: Any) -> ChatGoogleGenerativeAI:
|
|
9
|
+
"""
|
|
10
|
+
Khởi tạo và trả về instance ChatGoogleGenerativeAI.
|
|
11
|
+
"""
|
|
12
|
+
if os.getenv("GOOGLE_API_KEY") is None and api_key:
|
|
13
|
+
os.environ["GOOGLE_API_KEY"] = api_key
|
|
14
|
+
|
|
15
|
+
return ChatGoogleGenerativeAI(
|
|
16
|
+
model=model_name,
|
|
17
|
+
temperature=temperature,
|
|
18
|
+
**extra_params
|
|
19
|
+
)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from langchain_ollama.chat_models import ChatOllama
|
|
2
|
+
from typing import Any, Dict, Optional
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def create_ollama_model(model_name: str, temperature: float, base_url: Optional[str],
|
|
6
|
+
**extra_params: Any) -> ChatOllama:
|
|
7
|
+
return ChatOllama(
|
|
8
|
+
model=model_name,
|
|
9
|
+
temperature=temperature,
|
|
10
|
+
base_url=base_url,
|
|
11
|
+
**extra_params
|
|
12
|
+
)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from langchain_openai import ChatOpenAI
|
|
2
|
+
from typing import Any, Dict, Optional
|
|
3
|
+
|
|
4
|
+
def create_openai_model(model_name: str, temperature: float, api_key: Optional[str], **extra_params: Any) -> ChatOpenAI:
|
|
5
|
+
return ChatOpenAI(
|
|
6
|
+
model=model_name,
|
|
7
|
+
temperature=temperature,
|
|
8
|
+
api_key=api_key,
|
|
9
|
+
**extra_params
|
|
10
|
+
)
|
app/core/__init__.py
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
import yaml
|
|
4
|
+
from typing import Any, Optional, Dict
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
from deepagents import create_deep_agent, SubAgent
|
|
7
|
+
from deepagents.backends import FilesystemBackend
|
|
8
|
+
|
|
9
|
+
from app.core.LLM import LLMFactory, LLMConfig
|
|
10
|
+
from app.core.checkpoint import CheckpointFactory, CheckpointConfig
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class DeepAgentConfig(BaseModel):
|
|
14
|
+
"""Cấu hình toàn diện cho việc khởi tạo DeepAgent, hỗ trợ mở rộng các thuộc tính khác sau này."""
|
|
15
|
+
subagent_config_path: str = Field(default="config/subagent.yml",
|
|
16
|
+
description="Đường dẫn tới file config của subagent")
|
|
17
|
+
llm_config: LLMConfig = Field(..., description="Cấu hình LLM cho agent")
|
|
18
|
+
checkpoint_config: Optional[CheckpointConfig] = Field(default=None, description="Cấu hình checkpointer cho agent")
|
|
19
|
+
extra_params: Dict[str, Any] = Field(default_factory=dict,
|
|
20
|
+
description="Các thuộc tính cấu hình mở rộng khác trong tương lai")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# from core.tools import ReadFileTool, WriteFileTool, ShellExecuteTool
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class DeepAgentFactory:
|
|
27
|
+
agent: dict = {}
|
|
28
|
+
|
|
29
|
+
config: dict = None
|
|
30
|
+
|
|
31
|
+
def __init__(self):
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
@staticmethod
|
|
35
|
+
def load_subagents(config_path: str = "config/v1/subagent.yml") -> list[SubAgent]:
|
|
36
|
+
with open(config_path) as f:
|
|
37
|
+
config = yaml.safe_load(f)
|
|
38
|
+
|
|
39
|
+
sub_agents = []
|
|
40
|
+
for name, spec in config.items():
|
|
41
|
+
subagent = SubAgent(
|
|
42
|
+
name=name,
|
|
43
|
+
description=spec["description"],
|
|
44
|
+
system_prompt=spec["system_prompt"],
|
|
45
|
+
interrupt_on=spec["interrupt_on"],
|
|
46
|
+
)
|
|
47
|
+
if "model" in spec:
|
|
48
|
+
subagent["model"] = spec["model"]
|
|
49
|
+
|
|
50
|
+
sub_agents.append(subagent)
|
|
51
|
+
return sub_agents
|
|
52
|
+
|
|
53
|
+
@staticmethod
|
|
54
|
+
def get_config(config_path: str = None) -> Any:
|
|
55
|
+
if DeepAgentFactory.config is None and isinstance(config_path, str):
|
|
56
|
+
actual_path = config_path
|
|
57
|
+
if not os.path.exists(actual_path):
|
|
58
|
+
# Fallback: tìm kiếm dựa trên thư mục cài đặt của package
|
|
59
|
+
# deep_agent.py nằm ở <package_root>/app/core/agent/deep_agent.py
|
|
60
|
+
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
61
|
+
package_root = os.path.dirname(os.path.dirname(os.path.dirname(current_dir)))
|
|
62
|
+
fallback_path = os.path.join(package_root, config_path.lstrip("/\\"))
|
|
63
|
+
if os.path.exists(fallback_path):
|
|
64
|
+
actual_path = fallback_path
|
|
65
|
+
|
|
66
|
+
with open(actual_path, encoding="utf-8") as f:
|
|
67
|
+
config = yaml.safe_load(f)
|
|
68
|
+
DeepAgentFactory.config = config
|
|
69
|
+
return DeepAgentFactory.config
|
|
70
|
+
|
|
71
|
+
@staticmethod
|
|
72
|
+
def create_agent(
|
|
73
|
+
config_or_path: Any,
|
|
74
|
+
llm_config: Optional[LLMConfig] = None,
|
|
75
|
+
checkpoint_config: Optional[CheckpointConfig] = None
|
|
76
|
+
):
|
|
77
|
+
"""
|
|
78
|
+
Khởi tạo DeepAgent từ cấu hình.
|
|
79
|
+
Hỗ trợ nhận một instance DeepAgentConfig hoặc các tham số riêng lẻ (tương thích ngược).
|
|
80
|
+
"""
|
|
81
|
+
if isinstance(config_or_path, DeepAgentConfig):
|
|
82
|
+
agent_config = config_or_path
|
|
83
|
+
else:
|
|
84
|
+
# Hỗ trợ tương thích ngược với cách gọi cũ
|
|
85
|
+
agent_config = DeepAgentConfig(
|
|
86
|
+
subagent_config_path=config_or_path,
|
|
87
|
+
llm_config=llm_config,
|
|
88
|
+
checkpoint_config=checkpoint_config
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
config = DeepAgentFactory.get_config(os.path.join(os.getenv("CONFIG_PATH", "config/v0"), "config.yml"))
|
|
92
|
+
model = LLMFactory.create_llm(agent_config.llm_config)
|
|
93
|
+
sub_agent = DeepAgentFactory.load_subagents(agent_config.subagent_config_path)
|
|
94
|
+
|
|
95
|
+
backend = FilesystemBackend(virtual_mode=True)
|
|
96
|
+
sys_prompt = config["system_prompt"]
|
|
97
|
+
|
|
98
|
+
checkpoint_cfg = agent_config.checkpoint_config or CheckpointConfig(provider="inmemory")
|
|
99
|
+
checkpointer = CheckpointFactory.create_checkpoint(checkpoint_cfg)
|
|
100
|
+
|
|
101
|
+
return create_deep_agent(
|
|
102
|
+
model=model,
|
|
103
|
+
subagents=sub_agent,
|
|
104
|
+
# tools=tools,
|
|
105
|
+
system_prompt=sys_prompt,
|
|
106
|
+
# permissions=permissions,
|
|
107
|
+
backend=backend,
|
|
108
|
+
checkpointer=checkpointer,
|
|
109
|
+
)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from typing import Optional, Dict, Any
|
|
2
|
+
from pydantic import BaseModel, Field
|
|
3
|
+
from langgraph.checkpoint.base import BaseCheckpointSaver
|
|
4
|
+
|
|
5
|
+
class CheckpointConfig(BaseModel):
|
|
6
|
+
"""Cấu hình cho việc khởi tạo Checkpointer."""
|
|
7
|
+
provider: str = Field(..., description="Nhà cung cấp checkpoint ('inmemory' hoặc 'sqlite')")
|
|
8
|
+
database_path: Optional[str] = Field(default=None, description="Đường dẫn đến file cơ sở dữ liệu SQLite (bắt buộc nếu provider là sqlite)")
|
|
9
|
+
extra_params: Dict[str, Any] = Field(default_factory=dict, description="Các tham số bổ sung khác")
|
|
10
|
+
|
|
11
|
+
class CheckpointFactory:
|
|
12
|
+
"""
|
|
13
|
+
Factory class để quản lý việc khởi tạo các Checkpoint Savers từ nhiều nhà cung cấp khác nhau.
|
|
14
|
+
Chỉ cần hỗ trợ 'inmemory' và 'sqlite'.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
@staticmethod
|
|
18
|
+
def create_checkpoint(config: CheckpointConfig) -> BaseCheckpointSaver:
|
|
19
|
+
"""
|
|
20
|
+
Khởi tạo và trả về một checkpointer (BaseCheckpointSaver) tương ứng.
|
|
21
|
+
Sử dụng Lazy Import để tối ưu hiệu năng và tránh lỗi khi thiếu thư viện.
|
|
22
|
+
"""
|
|
23
|
+
provider = config.provider.lower()
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
if provider == "inmemory":
|
|
27
|
+
from langgraph.checkpoint.memory import MemorySaver
|
|
28
|
+
return MemorySaver(**config.extra_params)
|
|
29
|
+
|
|
30
|
+
elif provider == "sqlite":
|
|
31
|
+
if not config.database_path:
|
|
32
|
+
raise ValueError("SQLite checkpointer yêu cầu 'database_path' trong cấu hình.")
|
|
33
|
+
|
|
34
|
+
import sqlite3
|
|
35
|
+
from langgraph.checkpoint.sqlite import SqliteSaver
|
|
36
|
+
|
|
37
|
+
# check_same_thread=False là cần thiết cho SQLite trong môi trường đa luồng của Python
|
|
38
|
+
conn = sqlite3.connect(
|
|
39
|
+
config.database_path,
|
|
40
|
+
check_same_thread=False,
|
|
41
|
+
**config.extra_params
|
|
42
|
+
)
|
|
43
|
+
return SqliteSaver(conn)
|
|
44
|
+
|
|
45
|
+
else:
|
|
46
|
+
raise ValueError(f"Provider '{provider}' hiện chưa được hỗ trợ bởi CheckpointFactory.")
|
|
47
|
+
|
|
48
|
+
except ImportError as e:
|
|
49
|
+
raise ImportError(
|
|
50
|
+
f"Lỗi: Thiếu thư viện cho provider '{provider}' hoặc lỗi cấu trúc file. "
|
|
51
|
+
f"Chi tiết: {str(e)}"
|
|
52
|
+
)
|
|
53
|
+
except Exception as e:
|
|
54
|
+
raise Exception(f"Lỗi hệ thống khi khởi tạo Checkpointer ({provider}): {str(e)}")
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from typing import Any, Optional, Dict, List, Generator
|
|
3
|
+
from langgraph.types import Command
|
|
4
|
+
from app.service.ConfigService import ConfigService
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ChatService:
|
|
8
|
+
"""
|
|
9
|
+
Service quản lý các cuộc hội thoại chat với agent của ConfigService.
|
|
10
|
+
Xử lý stream luồng suy nghĩ bằng phương thức agent.stream, ngắt human_in_the_loop và lưu lịch sử chat.
|
|
11
|
+
Áp dụng mẫu thiết kế Singleton để đảm bảo tính nhất quán của trạng thái phiên chat.
|
|
12
|
+
"""
|
|
13
|
+
_instance = None
|
|
14
|
+
|
|
15
|
+
def __new__(cls, *args, **kwargs):
|
|
16
|
+
if not cls._instance:
|
|
17
|
+
cls._instance = super(ChatService, cls).__new__(cls, *args, **kwargs)
|
|
18
|
+
cls._instance._initialized = False
|
|
19
|
+
return cls._instance
|
|
20
|
+
|
|
21
|
+
def __init__(self):
|
|
22
|
+
if self._initialized:
|
|
23
|
+
return
|
|
24
|
+
|
|
25
|
+
# Lưu trữ trạng thái của các phiên hội thoại
|
|
26
|
+
# Cấu trúc: { thread_id: { "status": str, "pending_actions": list, "review_configs": list, "last_result": Any } }
|
|
27
|
+
self._sessions: Dict[str, Dict[str, Any]] = {}
|
|
28
|
+
self._initialized = True
|
|
29
|
+
|
|
30
|
+
def send_message(self, thread_id: str, message: str) -> Generator[Dict[str, Any], None, None]:
|
|
31
|
+
"""
|
|
32
|
+
Gửi tin nhắn của người dùng đến Agent và stream luồng suy nghĩ.
|
|
33
|
+
Thực hiện kiểm tra lỗi đầu vào ngay lập tức và trả về một Generator sinh ra các chunk từ agent.stream.
|
|
34
|
+
Chunk cuối cùng được yield là kết quả tổng hợp của phiên.
|
|
35
|
+
"""
|
|
36
|
+
agent = ConfigService().get_agent()
|
|
37
|
+
if agent is None:
|
|
38
|
+
raise ValueError("Không thể lấy Agent từ ConfigService")
|
|
39
|
+
|
|
40
|
+
def _stream_logic():
|
|
41
|
+
# Chuẩn bị dữ liệu đầu vào và cấu hình cho agent
|
|
42
|
+
input_data = {"messages": [{"role": "user", "content": message}]}
|
|
43
|
+
config = {"configurable": {"thread_id": thread_id}}
|
|
44
|
+
|
|
45
|
+
# Khởi tạo session nếu chưa tồn tại
|
|
46
|
+
if thread_id not in self._sessions:
|
|
47
|
+
self._sessions[thread_id] = {
|
|
48
|
+
"status": "completed",
|
|
49
|
+
"pending_actions": [],
|
|
50
|
+
"review_configs": [],
|
|
51
|
+
"last_result": None
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
# Thực thi agent.stream với cơ chế chống lỗi try-except
|
|
55
|
+
try:
|
|
56
|
+
|
|
57
|
+
for chunk in agent.stream(input_data, config=config, stream_mode="updates"):
|
|
58
|
+
yield chunk
|
|
59
|
+
except Exception as e:
|
|
60
|
+
print(f"[ChatService Error] Loi khi stream agent: {e}")
|
|
61
|
+
raise e
|
|
62
|
+
|
|
63
|
+
# Lấy trạng thái mới nhất từ agent sau khi kết thúc stream
|
|
64
|
+
try:
|
|
65
|
+
state = agent.get_state(config=config)
|
|
66
|
+
except Exception as e:
|
|
67
|
+
print(f"[ChatService Error] Loi khi lay state cuoi cung: {e}")
|
|
68
|
+
raise e
|
|
69
|
+
|
|
70
|
+
self._sessions[thread_id]["last_result"] = state
|
|
71
|
+
|
|
72
|
+
# Trích xuất interrupts từ state
|
|
73
|
+
interrupts = []
|
|
74
|
+
if state and hasattr(state, "tasks"):
|
|
75
|
+
for task in state.tasks:
|
|
76
|
+
if hasattr(task, "interrupts") and task.interrupts:
|
|
77
|
+
interrupts.extend(task.interrupts)
|
|
78
|
+
|
|
79
|
+
if interrupts:
|
|
80
|
+
interrupt = interrupts[0]
|
|
81
|
+
pending_actions = []
|
|
82
|
+
|
|
83
|
+
# Trích xuất pending actions từ interrupt value
|
|
84
|
+
if hasattr(interrupt, "value") and isinstance(interrupt.value, dict):
|
|
85
|
+
pending_actions = interrupt.value.get("action_requests", [])
|
|
86
|
+
elif isinstance(interrupt, dict):
|
|
87
|
+
pending_actions = interrupt.get("action_requests", [])
|
|
88
|
+
|
|
89
|
+
self._sessions[thread_id]["status"] = "waiting_approval"
|
|
90
|
+
self._sessions[thread_id]["pending_actions"] = pending_actions
|
|
91
|
+
|
|
92
|
+
final_res = {
|
|
93
|
+
"status": "waiting_approval",
|
|
94
|
+
"pending_actions": pending_actions
|
|
95
|
+
}
|
|
96
|
+
yield final_res
|
|
97
|
+
else:
|
|
98
|
+
# Trích xuất nội dung tin nhắn phản hồi cuối cùng từ agent
|
|
99
|
+
response_content = ""
|
|
100
|
+
messages = []
|
|
101
|
+
if state and hasattr(state, "values") and isinstance(state.values, dict):
|
|
102
|
+
messages = state.values.get("messages", [])
|
|
103
|
+
|
|
104
|
+
if messages:
|
|
105
|
+
last_msg = messages[-1]
|
|
106
|
+
if hasattr(last_msg, "content"):
|
|
107
|
+
response_content = last_msg.content
|
|
108
|
+
elif isinstance(last_msg, dict):
|
|
109
|
+
response_content = last_msg.get("content", "")
|
|
110
|
+
|
|
111
|
+
self._sessions[thread_id]["status"] = "completed"
|
|
112
|
+
self._sessions[thread_id]["pending_actions"] = []
|
|
113
|
+
|
|
114
|
+
final_res = {
|
|
115
|
+
"status": "completed",
|
|
116
|
+
"response": response_content
|
|
117
|
+
}
|
|
118
|
+
yield final_res
|
|
119
|
+
|
|
120
|
+
return _stream_logic()
|
|
121
|
+
|
|
122
|
+
def handle_approval(self, thread_id: str, decisions: List[Dict[str, Any]]) -> Generator[Dict[str, Any], None, None]:
|
|
123
|
+
"""
|
|
124
|
+
Xử lý quyết định phê duyệt (approve/reject) của người dùng đối với các tool call bị ngắt và stream phản hồi tiếp theo.
|
|
125
|
+
Thực hiện kiểm tra lỗi đầu vào ngay lập tức và trả về một Generator sinh ra các chunk từ agent.stream.
|
|
126
|
+
Chunk cuối cùng được yield là kết quả tổng hợp của phiên.
|
|
127
|
+
"""
|
|
128
|
+
if thread_id not in self._sessions:
|
|
129
|
+
raise ValueError("Không tìm thấy phiên hội thoại")
|
|
130
|
+
|
|
131
|
+
session = self._sessions[thread_id]
|
|
132
|
+
if session.get("status") != "waiting_approval":
|
|
133
|
+
raise ValueError("Phiên hội thoại không ở trạng thái chờ duyệt")
|
|
134
|
+
|
|
135
|
+
agent = ConfigService().get_agent()
|
|
136
|
+
if agent is None:
|
|
137
|
+
raise ValueError("Không thể lấy Agent từ ConfigService")
|
|
138
|
+
|
|
139
|
+
def _stream_logic():
|
|
140
|
+
# Tạo command resume gửi cho agent
|
|
141
|
+
cmd = Command(resume={"decisions": decisions})
|
|
142
|
+
config = {"configurable": {"thread_id": thread_id}}
|
|
143
|
+
|
|
144
|
+
# Thực thi agent.stream tiếp tục với cơ chế chống lỗi try-except
|
|
145
|
+
try:
|
|
146
|
+
# stream_generator = agent.stream(cmd, config=config,stream_mode="values")
|
|
147
|
+
for chunk in agent.stream(cmd, config=config,stream_mode="updates"):
|
|
148
|
+
yield chunk
|
|
149
|
+
except Exception as e:
|
|
150
|
+
print(f"[ChatService Error] Loi khi stream tiep tuc agent: {e}")
|
|
151
|
+
raise e
|
|
152
|
+
|
|
153
|
+
# Lấy trạng thái mới nhất từ agent sau khi kết thúc stream
|
|
154
|
+
try:
|
|
155
|
+
state = agent.get_state(config=config)
|
|
156
|
+
except Exception as e:
|
|
157
|
+
print(f"[ChatService Error] Loi khi lay state cuoi cung: {e}")
|
|
158
|
+
raise e
|
|
159
|
+
|
|
160
|
+
self._sessions[thread_id]["last_result"] = state
|
|
161
|
+
|
|
162
|
+
# Trích xuất interrupts từ state
|
|
163
|
+
interrupts = []
|
|
164
|
+
if state and hasattr(state, "tasks"):
|
|
165
|
+
for task in state.tasks:
|
|
166
|
+
if hasattr(task, "interrupts") and task.interrupts:
|
|
167
|
+
interrupts.extend(task.interrupts)
|
|
168
|
+
|
|
169
|
+
if interrupts:
|
|
170
|
+
interrupt = interrupts[0]
|
|
171
|
+
pending_actions = []
|
|
172
|
+
if hasattr(interrupt, "value") and isinstance(interrupt.value, dict):
|
|
173
|
+
pending_actions = interrupt.value.get("action_requests", [])
|
|
174
|
+
elif isinstance(interrupt, dict):
|
|
175
|
+
pending_actions = interrupt.get("action_requests", [])
|
|
176
|
+
|
|
177
|
+
self._sessions[thread_id]["status"] = "waiting_approval"
|
|
178
|
+
self._sessions[thread_id]["pending_actions"] = pending_actions
|
|
179
|
+
|
|
180
|
+
final_res = {
|
|
181
|
+
"status": "waiting_approval",
|
|
182
|
+
"pending_actions": pending_actions
|
|
183
|
+
}
|
|
184
|
+
yield final_res
|
|
185
|
+
else:
|
|
186
|
+
response_content = ""
|
|
187
|
+
messages = []
|
|
188
|
+
if state and hasattr(state, "values") and isinstance(state.values, dict):
|
|
189
|
+
messages = state.values.get("messages", [])
|
|
190
|
+
|
|
191
|
+
if messages:
|
|
192
|
+
last_msg = messages[-1]
|
|
193
|
+
if hasattr(last_msg, "content"):
|
|
194
|
+
response_content = last_msg.content
|
|
195
|
+
elif isinstance(last_msg, dict):
|
|
196
|
+
response_content = last_msg.get("content", "")
|
|
197
|
+
|
|
198
|
+
self._sessions[thread_id]["status"] = "completed"
|
|
199
|
+
self._sessions[thread_id]["pending_actions"] = []
|
|
200
|
+
|
|
201
|
+
final_res = {
|
|
202
|
+
"status": "completed",
|
|
203
|
+
"response": response_content
|
|
204
|
+
}
|
|
205
|
+
yield final_res
|
|
206
|
+
|
|
207
|
+
return _stream_logic()
|
|
208
|
+
|
|
209
|
+
def get_history(self, thread_id: str) -> list:
|
|
210
|
+
"""
|
|
211
|
+
Lấy toàn bộ lịch sử tin nhắn của phiên hội thoại (thread_id) từ agent state.
|
|
212
|
+
"""
|
|
213
|
+
agent = ConfigService().get_agent()
|
|
214
|
+
if agent is None:
|
|
215
|
+
raise ValueError("Không thể lấy Agent từ ConfigService")
|
|
216
|
+
|
|
217
|
+
# Truy xuất state hiện tại của agent với cơ chế chống lỗi try-except
|
|
218
|
+
try:
|
|
219
|
+
state = agent.get_state(config={"configurable": {"thread_id": thread_id}})
|
|
220
|
+
except Exception as e:
|
|
221
|
+
print(f"[ChatService Error] Loi khi lay lich su tu agent: {e}")
|
|
222
|
+
return []
|
|
223
|
+
|
|
224
|
+
if not state or not hasattr(state, "values") or not state.values:
|
|
225
|
+
return []
|
|
226
|
+
|
|
227
|
+
return state.values.get("messages", [])
|
|
228
|
+
|
|
229
|
+
def get_session_status(self, thread_id: str) -> Optional[str]:
|
|
230
|
+
"""
|
|
231
|
+
Lấy trạng thái hiện tại của phiên hội thoại (thread_id).
|
|
232
|
+
"""
|
|
233
|
+
session = self._sessions.get(thread_id)
|
|
234
|
+
if session:
|
|
235
|
+
return session.get("status")
|
|
236
|
+
return None
|
|
237
|
+
|
|
238
|
+
def get_pending_actions(self, thread_id: str) -> List[Dict[str, Any]]:
|
|
239
|
+
"""
|
|
240
|
+
Lấy danh sách các action đang chờ duyệt của phiên hội thoại (thread_id).
|
|
241
|
+
"""
|
|
242
|
+
session = self._sessions.get(thread_id)
|
|
243
|
+
if session:
|
|
244
|
+
return session.get("pending_actions", [])
|
|
245
|
+
return []
|