bioguider 0.2.3__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.
Potentially problematic release.
This version of bioguider might be problematic. Click here for more details.
- bioguider/__init__.py +0 -0
- bioguider/agents/__init__.py +0 -0
- bioguider/agents/agent_task.py +88 -0
- bioguider/agents/agent_tools.py +147 -0
- bioguider/agents/agent_utils.py +357 -0
- bioguider/agents/collection_execute_step.py +180 -0
- bioguider/agents/collection_observe_step.py +113 -0
- bioguider/agents/collection_plan_step.py +154 -0
- bioguider/agents/collection_task.py +179 -0
- bioguider/agents/collection_task_utils.py +109 -0
- bioguider/agents/common_agent.py +159 -0
- bioguider/agents/common_agent_2step.py +126 -0
- bioguider/agents/common_step.py +85 -0
- bioguider/agents/dockergeneration_execute_step.py +186 -0
- bioguider/agents/dockergeneration_observe_step.py +153 -0
- bioguider/agents/dockergeneration_plan_step.py +158 -0
- bioguider/agents/dockergeneration_task.py +158 -0
- bioguider/agents/dockergeneration_task_utils.py +220 -0
- bioguider/agents/evaluation_task.py +269 -0
- bioguider/agents/identification_execute_step.py +179 -0
- bioguider/agents/identification_observe_step.py +92 -0
- bioguider/agents/identification_plan_step.py +135 -0
- bioguider/agents/identification_task.py +220 -0
- bioguider/agents/identification_task_utils.py +18 -0
- bioguider/agents/peo_common_step.py +64 -0
- bioguider/agents/prompt_utils.py +190 -0
- bioguider/agents/python_ast_repl_tool.py +69 -0
- bioguider/agents/rag_collection_task.py +130 -0
- bioguider/conversation.py +67 -0
- bioguider/database/summarized_file_db.py +140 -0
- bioguider/managers/evaluation_manager.py +108 -0
- bioguider/rag/__init__.py +0 -0
- bioguider/rag/config.py +117 -0
- bioguider/rag/data_pipeline.py +648 -0
- bioguider/rag/embedder.py +24 -0
- bioguider/rag/rag.py +134 -0
- bioguider/settings.py +103 -0
- bioguider/utils/constants.py +40 -0
- bioguider/utils/default.gitignore +140 -0
- bioguider/utils/file_utils.py +126 -0
- bioguider/utils/gitignore_checker.py +175 -0
- bioguider/utils/pyphen_utils.py +73 -0
- bioguider/utils/utils.py +27 -0
- bioguider-0.2.3.dist-info/LICENSE +21 -0
- bioguider-0.2.3.dist-info/METADATA +44 -0
- bioguider-0.2.3.dist-info/RECORD +47 -0
- bioguider-0.2.3.dist-info/WHEEL +4 -0
bioguider/__init__.py
ADDED
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
|
|
2
|
+
from typing import Callable
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
|
|
5
|
+
from langchain_openai.chat_models.base import BaseChatOpenAI
|
|
6
|
+
from langgraph.graph.graph import CompiledGraph
|
|
7
|
+
|
|
8
|
+
from bioguider.utils.constants import DEFAULT_TOKEN_USAGE
|
|
9
|
+
from bioguider.database.summarized_file_db import SummarizedFilesDb
|
|
10
|
+
|
|
11
|
+
class AgentTask(ABC):
|
|
12
|
+
"""
|
|
13
|
+
A class representing a step in an agent's process.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, llm: BaseChatOpenAI, step_callback: Callable | None = None):
|
|
17
|
+
"""
|
|
18
|
+
Initialize the AgentStep with a language model and a callback function.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
llm (BaseChatOpenAI): The language model to use.
|
|
22
|
+
step_callback (Callable): A callback function to handle step results.
|
|
23
|
+
"""
|
|
24
|
+
self.llm = llm
|
|
25
|
+
self.step_callback = step_callback
|
|
26
|
+
self.summary_file_db = None
|
|
27
|
+
self.graph: CompiledGraph | None = None
|
|
28
|
+
|
|
29
|
+
def _print_step(
|
|
30
|
+
self,
|
|
31
|
+
step_name: str | None = None,
|
|
32
|
+
step_output: str | None = None,
|
|
33
|
+
token_usage: dict | object | None = None,
|
|
34
|
+
):
|
|
35
|
+
if self.step_callback is None:
|
|
36
|
+
return
|
|
37
|
+
# convert token_usage to dict
|
|
38
|
+
if token_usage is not None and not isinstance(token_usage, dict):
|
|
39
|
+
token_usage = vars(token_usage)
|
|
40
|
+
token_usage = {**DEFAULT_TOKEN_USAGE, **token_usage}
|
|
41
|
+
step_callback = self.step_callback
|
|
42
|
+
step_callback(
|
|
43
|
+
step_name=step_name,
|
|
44
|
+
step_output=step_output,
|
|
45
|
+
token_usage=token_usage,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
def compile(self, repo_path: str, gitignore_path: str, db: SummarizedFilesDb | None = None, **kwargs):
|
|
49
|
+
"""
|
|
50
|
+
Compile the agent step with the given repository and gitignore paths.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
repo_path (str): The path to the repository.
|
|
54
|
+
gitignore_path (str): The path to the .gitignore file.
|
|
55
|
+
**kwargs: derived class may pass more arguments to implmented _compile(), that is,
|
|
56
|
+
what **kwargs is depends on derived class
|
|
57
|
+
"""
|
|
58
|
+
self.summary_file_db = db
|
|
59
|
+
self._compile(repo_path, gitignore_path, **kwargs)
|
|
60
|
+
|
|
61
|
+
@abstractmethod
|
|
62
|
+
def _compile(self, repo_path: str, gitignore_path: str, **kwargs):
|
|
63
|
+
"""
|
|
64
|
+
Abstract method to compile the agent step.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
repo_path (str): The path to the repository.
|
|
68
|
+
gitignore_path (str): The path to the .gitignore file.
|
|
69
|
+
"""
|
|
70
|
+
pass
|
|
71
|
+
|
|
72
|
+
def _go_graph(self, input: dict) -> dict:
|
|
73
|
+
input = {
|
|
74
|
+
**input,
|
|
75
|
+
"llm": self.llm,
|
|
76
|
+
"step_output_callback": self.step_callback,
|
|
77
|
+
}
|
|
78
|
+
for s in self.graph.stream(
|
|
79
|
+
input=input,
|
|
80
|
+
stream_mode="values",
|
|
81
|
+
config={"recursion_limit": 500},
|
|
82
|
+
):
|
|
83
|
+
print(s)
|
|
84
|
+
|
|
85
|
+
return s
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from typing import Callable
|
|
3
|
+
from langchain_openai.chat_models.base import BaseChatOpenAI
|
|
4
|
+
from bioguider.database.summarized_file_db import SummarizedFilesDb
|
|
5
|
+
from bioguider.utils.file_utils import get_file_type
|
|
6
|
+
from bioguider.agents.agent_utils import read_directory, read_file, summarize_file
|
|
7
|
+
|
|
8
|
+
class agent_tool:
|
|
9
|
+
def __init__(
|
|
10
|
+
self,
|
|
11
|
+
llm: BaseChatOpenAI | None = None,
|
|
12
|
+
output_callback:Callable[[dict], None] = None,
|
|
13
|
+
):
|
|
14
|
+
self.llm = llm
|
|
15
|
+
self.output_callback = output_callback
|
|
16
|
+
|
|
17
|
+
def _print_token_usage(self, token_usage: dict):
|
|
18
|
+
if self.output_callback is not None:
|
|
19
|
+
self.output_callback(token_usage=token_usage)
|
|
20
|
+
def _print_step_output(self, step_output: str):
|
|
21
|
+
if self.output_callback is not None:
|
|
22
|
+
self.output_callback(step_output=step_output)
|
|
23
|
+
|
|
24
|
+
class read_file_tool:
|
|
25
|
+
""" read file
|
|
26
|
+
Args:
|
|
27
|
+
file_path str: file path
|
|
28
|
+
Returns:
|
|
29
|
+
A string of file content, if the file does not exist, return None.
|
|
30
|
+
"""
|
|
31
|
+
def __init__(self, repo_path: str | None = None):
|
|
32
|
+
self.repo_path = repo_path if repo_path is not None else ""
|
|
33
|
+
|
|
34
|
+
def run(self, file_path: str) -> str | None:
|
|
35
|
+
if file_path is None:
|
|
36
|
+
return None
|
|
37
|
+
file_path = file_path.strip()
|
|
38
|
+
if self.repo_path is not None and self.repo_path not in file_path:
|
|
39
|
+
file_path = os.path.join(self.repo_path, file_path)
|
|
40
|
+
if not os.path.isfile(file_path):
|
|
41
|
+
return None
|
|
42
|
+
return read_file(file_path)
|
|
43
|
+
|
|
44
|
+
class summarize_file_tool(agent_tool):
|
|
45
|
+
""" read and summarize the file
|
|
46
|
+
Args:
|
|
47
|
+
file_path str: file path
|
|
48
|
+
Returns:
|
|
49
|
+
A string of summarized file content, if the file does not exist, return None.
|
|
50
|
+
"""
|
|
51
|
+
def __init__(
|
|
52
|
+
self,
|
|
53
|
+
llm: BaseChatOpenAI,
|
|
54
|
+
repo_path: str | None = None,
|
|
55
|
+
output_callback: Callable | None = None,
|
|
56
|
+
detailed_level: int | None = 6,
|
|
57
|
+
db: SummarizedFilesDb | None = None,
|
|
58
|
+
summaize_instruction: str = "",
|
|
59
|
+
):
|
|
60
|
+
super().__init__(llm=llm, output_callback=output_callback)
|
|
61
|
+
self.repo_path = repo_path
|
|
62
|
+
detailed_level = detailed_level if detailed_level is not None else 6
|
|
63
|
+
detailed_level = detailed_level if detailed_level > 0 else 1
|
|
64
|
+
detailed_level = detailed_level if detailed_level <= 10 else 10
|
|
65
|
+
self.detailed_level = detailed_level
|
|
66
|
+
self.summary_file_db = db
|
|
67
|
+
self.summarize_instruction = summaize_instruction
|
|
68
|
+
|
|
69
|
+
def _retrive_from_summary_file_db(self, file_path: str) -> str | None:
|
|
70
|
+
if self.summary_file_db is None:
|
|
71
|
+
return None
|
|
72
|
+
return self.summary_file_db.select_summarized_text(
|
|
73
|
+
file_path=file_path,
|
|
74
|
+
instruction=self.summarize_instruction,
|
|
75
|
+
summarize_level=self.detailed_level,
|
|
76
|
+
)
|
|
77
|
+
def _save_to_summary_file_db(self, file_path: str, summarized_text: str, token_usage: dict):
|
|
78
|
+
if self.summary_file_db is None:
|
|
79
|
+
return
|
|
80
|
+
self.summary_file_db.upsert_summarized_file(
|
|
81
|
+
file_path=file_path,
|
|
82
|
+
instruction=self.summarize_instruction,
|
|
83
|
+
summarize_level=self.detailed_level,
|
|
84
|
+
summarized_text=summarized_text,
|
|
85
|
+
token_usage=token_usage,
|
|
86
|
+
)
|
|
87
|
+
def run(self, file_path: str) -> str | None:
|
|
88
|
+
if file_path is None:
|
|
89
|
+
return None
|
|
90
|
+
|
|
91
|
+
file_path = file_path.strip()
|
|
92
|
+
abs_file_path = file_path
|
|
93
|
+
if self.repo_path is not None and self.repo_path not in abs_file_path:
|
|
94
|
+
abs_file_path = os.path.join(self.repo_path, abs_file_path)
|
|
95
|
+
if not os.path.isfile(abs_file_path):
|
|
96
|
+
return f"{file_path} is not a file."
|
|
97
|
+
summarized_content = self._retrive_from_summary_file_db(
|
|
98
|
+
file_path=file_path
|
|
99
|
+
)
|
|
100
|
+
if summarized_content is not None:
|
|
101
|
+
return f"summarized content of file {file_path}: " + summarized_content
|
|
102
|
+
|
|
103
|
+
file_content = read_file(abs_file_path)
|
|
104
|
+
file_content = file_content.replace("{", "{{").replace("}", "}}")
|
|
105
|
+
summarized_content, token_usage = summarize_file(
|
|
106
|
+
self.llm, abs_file_path, file_content, self.detailed_level,
|
|
107
|
+
summary_instructions=self.summarize_instruction,
|
|
108
|
+
)
|
|
109
|
+
self._save_to_summary_file_db(
|
|
110
|
+
file_path=file_path,
|
|
111
|
+
summarized_text=summarized_content,
|
|
112
|
+
token_usage=token_usage,
|
|
113
|
+
)
|
|
114
|
+
self._print_token_usage(token_usage)
|
|
115
|
+
return f"summarized content of file {file_path}: " + summarized_content
|
|
116
|
+
|
|
117
|
+
class read_directory_tool:
|
|
118
|
+
"""Reads the contents of a directory, including files and subdirectories in it..
|
|
119
|
+
Args:
|
|
120
|
+
dir_path (str): Path to the directory.
|
|
121
|
+
Returns:
|
|
122
|
+
a string containing file and subdirectory paths found within the specified depth.
|
|
123
|
+
"""
|
|
124
|
+
def __init__(
|
|
125
|
+
self,
|
|
126
|
+
repo_path: str | None = None,
|
|
127
|
+
gitignore_path: str | None = None,
|
|
128
|
+
):
|
|
129
|
+
self.repo_path = repo_path
|
|
130
|
+
self.gitignore_path = gitignore_path if gitignore_path is not None else ""
|
|
131
|
+
|
|
132
|
+
def run(self, dir_path):
|
|
133
|
+
dir_path = dir_path.strip()
|
|
134
|
+
full_path = dir_path
|
|
135
|
+
if full_path == "." or full_path == "..":
|
|
136
|
+
return f"Please skip this folder {dir_path}"
|
|
137
|
+
if self.repo_path not in full_path:
|
|
138
|
+
full_path = os.path.join(self.repo_path, full_path)
|
|
139
|
+
files = read_directory(full_path, gitignore_path=self.gitignore_path, level=1)
|
|
140
|
+
if files is None:
|
|
141
|
+
return "N/A"
|
|
142
|
+
file_pairs = [(f, get_file_type(os.path.join(full_path, f)).value) for f in files]
|
|
143
|
+
dir_structure = ""
|
|
144
|
+
for f, f_type in file_pairs:
|
|
145
|
+
dir_structure += f"{os.path.join(dir_path, f)} - {f_type}\n"
|
|
146
|
+
return f"The 2-level content of directory {dir_path}: \n" + \
|
|
147
|
+
f"{dir_structure if len(dir_structure) > 0 else 'No files and sub-directories in it'}"
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import re
|
|
5
|
+
import subprocess
|
|
6
|
+
from typing import List, Optional, Tuple, Union
|
|
7
|
+
from langchain_openai import AzureChatOpenAI
|
|
8
|
+
from langchain_deepseek import ChatDeepSeek
|
|
9
|
+
from langchain_core.utils.interactive_env import is_interactive_env
|
|
10
|
+
from langchain_core.messages.base import get_msg_title_repr
|
|
11
|
+
from langchain_core.prompts import ChatPromptTemplate, StringPromptTemplate
|
|
12
|
+
from langchain_core.messages import AIMessage
|
|
13
|
+
from langchain_openai.chat_models.base import BaseChatOpenAI
|
|
14
|
+
from langchain.tools import BaseTool
|
|
15
|
+
from langchain.schema import AgentAction, AgentFinish
|
|
16
|
+
from langchain.agents import AgentOutputParser
|
|
17
|
+
from langgraph.prebuilt import create_react_agent
|
|
18
|
+
import logging
|
|
19
|
+
|
|
20
|
+
from pydantic import BaseModel, Field
|
|
21
|
+
|
|
22
|
+
from bioguider.utils.constants import DEFAULT_TOKEN_USAGE
|
|
23
|
+
from bioguider.utils.file_utils import get_file_type
|
|
24
|
+
from ..utils.gitignore_checker import GitignoreChecker
|
|
25
|
+
from ..database.summarized_file_db import SummarizedFilesDb
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
class PlanAgentResult(BaseModel):
|
|
30
|
+
""" Identification Plan Result """
|
|
31
|
+
actions: list[dict] = Field(description="a list of action dictionary, e.g. [{'name': 'read_file', 'input': 'README.md'}, ...]")
|
|
32
|
+
|
|
33
|
+
PlanAgentResultJsonSchema = {
|
|
34
|
+
"title": "identification_plan_result",
|
|
35
|
+
"description": "plan result",
|
|
36
|
+
"type": "object",
|
|
37
|
+
"properties": {
|
|
38
|
+
"actions": {
|
|
39
|
+
"type": "array",
|
|
40
|
+
"description": """a list of action dictionary, e.g. [{'name': 'read_file', 'input': 'README.md'}, ...]""",
|
|
41
|
+
"title": "Actions",
|
|
42
|
+
"items": {"type": "object"}
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
"required": ["actions"],
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
def get_openai():
|
|
49
|
+
return get_llm(
|
|
50
|
+
api_key=os.environ.get("OPENAI_API_KEY"),
|
|
51
|
+
model_name=os.environ.get("OPENAI_MODEL"),
|
|
52
|
+
azure_endpoint=os.environ.get("AZURE_OPENAI_ENDPOINT"),
|
|
53
|
+
api_version=os.environ.get("OPENAI_API_VERSION"),
|
|
54
|
+
azure_deployment=os.environ.get("OPENAI_DEPLOYMENT_NAME"),
|
|
55
|
+
max_tokens=os.environ.get("OPENAI_MAX_OUTPUT_TOKEN"),
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
def get_llm(
|
|
59
|
+
api_key: str,
|
|
60
|
+
model_name: str="gpt-4o",
|
|
61
|
+
azure_endpoint: str=None,
|
|
62
|
+
api_version: str=None,
|
|
63
|
+
azure_deployment: str=None,
|
|
64
|
+
temperature: float = 0.0,
|
|
65
|
+
max_tokens: int = 4096,
|
|
66
|
+
):
|
|
67
|
+
if model_name.startswith("deepseek"):
|
|
68
|
+
chat = ChatDeepSeek(
|
|
69
|
+
api_key=api_key,
|
|
70
|
+
model=model_name,
|
|
71
|
+
temperature=temperature,
|
|
72
|
+
max_tokens=max_tokens,
|
|
73
|
+
)
|
|
74
|
+
elif model_name.startswith("gpt"):
|
|
75
|
+
chat = AzureChatOpenAI(
|
|
76
|
+
api_key=api_key,
|
|
77
|
+
azure_endpoint=azure_endpoint,
|
|
78
|
+
api_version=api_version,
|
|
79
|
+
azure_deployment=azure_deployment,
|
|
80
|
+
model=model_name,
|
|
81
|
+
temperature=temperature,
|
|
82
|
+
max_tokens=max_tokens,
|
|
83
|
+
)
|
|
84
|
+
else:
|
|
85
|
+
raise ValueError("Invalid model name")
|
|
86
|
+
# validate chat
|
|
87
|
+
try:
|
|
88
|
+
chat.invoke("Hi")
|
|
89
|
+
except Exception as e:
|
|
90
|
+
print(e)
|
|
91
|
+
return None
|
|
92
|
+
return chat
|
|
93
|
+
|
|
94
|
+
def pretty_print(message, printout = True):
|
|
95
|
+
if isinstance(message, tuple):
|
|
96
|
+
title = message
|
|
97
|
+
else:
|
|
98
|
+
if isinstance(message.content, list):
|
|
99
|
+
title = get_msg_title_repr(message.type.title().upper() + " Message", bold=is_interactive_env())
|
|
100
|
+
if message.name is not None:
|
|
101
|
+
title += f"\nName: {message.name}"
|
|
102
|
+
|
|
103
|
+
for i in message.content:
|
|
104
|
+
if i['type'] == 'text':
|
|
105
|
+
title += f"\n{i['text']}\n"
|
|
106
|
+
elif i['type'] == 'tool_use':
|
|
107
|
+
title += f"\nTool: {i['name']}"
|
|
108
|
+
title += f"\nInput: {i['input']}"
|
|
109
|
+
if printout:
|
|
110
|
+
print(f"{title}")
|
|
111
|
+
else:
|
|
112
|
+
title = get_msg_title_repr(message.type.title() + " Message", bold=is_interactive_env())
|
|
113
|
+
if message.name is not None:
|
|
114
|
+
title += f"\nName: {message.name}"
|
|
115
|
+
title += f"\n\n{message.content}"
|
|
116
|
+
if printout:
|
|
117
|
+
print(f"{title}")
|
|
118
|
+
return title
|
|
119
|
+
|
|
120
|
+
HUGE_FILE_LENGTH = 10 * 1024 # 10K
|
|
121
|
+
|
|
122
|
+
def read_file(
|
|
123
|
+
file_path: str,
|
|
124
|
+
) -> str | None:
|
|
125
|
+
if not os.path.isfile(file_path):
|
|
126
|
+
return None
|
|
127
|
+
with open(file_path, 'r') as f:
|
|
128
|
+
content = f.read()
|
|
129
|
+
return content
|
|
130
|
+
|
|
131
|
+
def write_file(file_path: str, content: str):
|
|
132
|
+
try:
|
|
133
|
+
with open(file_path, "w") as fobj:
|
|
134
|
+
fobj.write(content)
|
|
135
|
+
return True
|
|
136
|
+
except Exception as e:
|
|
137
|
+
logger.error(e)
|
|
138
|
+
return False
|
|
139
|
+
|
|
140
|
+
def read_directory(
|
|
141
|
+
dir_path: str,
|
|
142
|
+
gitignore_path: str,
|
|
143
|
+
level: int=1,
|
|
144
|
+
) -> list[str] | None:
|
|
145
|
+
if not os.path.isdir(dir_path):
|
|
146
|
+
return None
|
|
147
|
+
gitignore_checker = GitignoreChecker(
|
|
148
|
+
directory=dir_path,
|
|
149
|
+
gitignore_path=gitignore_path
|
|
150
|
+
)
|
|
151
|
+
files = gitignore_checker.check_files_and_folders(level=level)
|
|
152
|
+
return files
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
EVALUATION_SUMMARIZE_FILE_PROMPT = ChatPromptTemplate.from_template("""
|
|
156
|
+
You are provided with the content of the file **{file_name}**:
|
|
157
|
+
```
|
|
158
|
+
{file_content}
|
|
159
|
+
```
|
|
160
|
+
### **Summary Instructions**
|
|
161
|
+
{summary_instructions}
|
|
162
|
+
The content is lengthy. Please generate a concise summary ({sentence_num1}-{sentence_num2} sentences).
|
|
163
|
+
""")
|
|
164
|
+
|
|
165
|
+
MAX_FILE_LENGTH=20 *1024 # 20K
|
|
166
|
+
MAX_SENTENCE_NUM=20
|
|
167
|
+
def summarize_file(
|
|
168
|
+
llm: BaseChatOpenAI,
|
|
169
|
+
name: str,
|
|
170
|
+
content: str | None = None,
|
|
171
|
+
level: int = 3,
|
|
172
|
+
summary_instructions: str | None = None,
|
|
173
|
+
db: SummarizedFilesDb | None = None,
|
|
174
|
+
) -> Tuple[str, dict]:
|
|
175
|
+
if content is None:
|
|
176
|
+
try:
|
|
177
|
+
with open(name, "r") as fobj:
|
|
178
|
+
content = fobj.read()
|
|
179
|
+
except Exception as e:
|
|
180
|
+
logger.error(e)
|
|
181
|
+
return ""
|
|
182
|
+
# First, query from database
|
|
183
|
+
if db is not None:
|
|
184
|
+
res = db.select_summarized_text(name, summary_instructions, level)
|
|
185
|
+
if res is not None:
|
|
186
|
+
return res, {**DEFAULT_TOKEN_USAGE}
|
|
187
|
+
|
|
188
|
+
file_content = content
|
|
189
|
+
level = level if level > 0 else 1
|
|
190
|
+
level = level if level < MAX_SENTENCE_NUM+1 else MAX_SENTENCE_NUM
|
|
191
|
+
if len(file_content) > MAX_FILE_LENGTH:
|
|
192
|
+
file_content = content[:MAX_FILE_LENGTH] + " ..."
|
|
193
|
+
prompt = EVALUATION_SUMMARIZE_FILE_PROMPT.format(
|
|
194
|
+
file_name=name,
|
|
195
|
+
file_content=file_content,
|
|
196
|
+
sentence_num1=level,
|
|
197
|
+
sentence_num2=level+1,
|
|
198
|
+
summary_instructions=summary_instructions \
|
|
199
|
+
if summary_instructions is not None and len(summary_instructions) > 0 \
|
|
200
|
+
else "N/A",
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
config = {"recursion_limit": 500}
|
|
204
|
+
res: AIMessage = llm.invoke([("human", prompt)], config=config)
|
|
205
|
+
out = res.content
|
|
206
|
+
token_usage = {
|
|
207
|
+
"prompt_tokens": res.usage_metadata["input_tokens"],
|
|
208
|
+
"completion_tokens": res.usage_metadata["output_tokens"],
|
|
209
|
+
"total_tokens": res.usage_metadata["total_tokens"],
|
|
210
|
+
}
|
|
211
|
+
if db is not None:
|
|
212
|
+
db.upsert_summarized_file(
|
|
213
|
+
name, summary_instructions, level, token_usage
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
return out, token_usage
|
|
217
|
+
|
|
218
|
+
def increase_token_usage(
|
|
219
|
+
token_usage: Optional[dict] = None,
|
|
220
|
+
incremental: dict = {**DEFAULT_TOKEN_USAGE},
|
|
221
|
+
):
|
|
222
|
+
if token_usage is None:
|
|
223
|
+
token_usage = {**DEFAULT_TOKEN_USAGE}
|
|
224
|
+
token_usage["total_tokens"] += incremental["total_tokens"]
|
|
225
|
+
token_usage["completion_tokens"] += incremental["completion_tokens"]
|
|
226
|
+
token_usage["prompt_tokens"] += incremental["prompt_tokens"]
|
|
227
|
+
|
|
228
|
+
return token_usage
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
# Set up a prompt template
|
|
232
|
+
class CustomPromptTemplate(StringPromptTemplate):
|
|
233
|
+
# The template to use
|
|
234
|
+
template: str
|
|
235
|
+
# The list of tools available
|
|
236
|
+
tools: List[BaseTool]
|
|
237
|
+
# Plan
|
|
238
|
+
plan_actions: str
|
|
239
|
+
|
|
240
|
+
def format(self, **kwargs) -> str:
|
|
241
|
+
# Get the intermediate steps (AgentAction, Observation tuples)
|
|
242
|
+
# Format them in a particular way
|
|
243
|
+
intermediate_steps = kwargs.pop("intermediate_steps")
|
|
244
|
+
thoughts = ""
|
|
245
|
+
for action, observation in intermediate_steps:
|
|
246
|
+
thoughts += action.log
|
|
247
|
+
thoughts += f"\nObservation: {observation}\n"
|
|
248
|
+
# Set plan_step
|
|
249
|
+
kwargs["plan_actions"] = self.plan_actions
|
|
250
|
+
# Set the agent_scratchpad variable to that value
|
|
251
|
+
kwargs["agent_scratchpad"] = thoughts
|
|
252
|
+
# Create a tools variable from the list of tools provided
|
|
253
|
+
kwargs["tools"] = "\n".join([f"{tool.name}: {tool.description}" for tool in self.tools])
|
|
254
|
+
# Create a list of tool names for the tools provided
|
|
255
|
+
kwargs["tool_names"] = ", ".join([tool.name for tool in self.tools])
|
|
256
|
+
prompt = self.template.format(**kwargs)
|
|
257
|
+
# print([prompt])
|
|
258
|
+
return prompt
|
|
259
|
+
|
|
260
|
+
class CustomOutputParser(AgentOutputParser):
|
|
261
|
+
def parse(self, llm_output: str) -> Union[AgentAction, AgentFinish]:
|
|
262
|
+
# Check if agent should finish
|
|
263
|
+
if "Final Answer:" in llm_output:
|
|
264
|
+
return AgentFinish(
|
|
265
|
+
return_values={"output": llm_output},
|
|
266
|
+
log=llm_output,
|
|
267
|
+
)
|
|
268
|
+
# Parse out the action and action input
|
|
269
|
+
regex = r"Action\s*\d*\s*:(.*?)\nAction\s*\d*\s*Input\s*\d*\s*:[\s]*(.*)"
|
|
270
|
+
match = re.search(regex, llm_output, re.DOTALL)
|
|
271
|
+
if not match:
|
|
272
|
+
# raise ValueError(f"Could not parse LLM output: `{llm_output}`")
|
|
273
|
+
print(f"Warning: could not parse LLM output: `{llm_output}`, finishing chain...")
|
|
274
|
+
return AgentFinish(
|
|
275
|
+
return_values={"output": llm_output},
|
|
276
|
+
log=llm_output,
|
|
277
|
+
)
|
|
278
|
+
action = match.group(1).strip()
|
|
279
|
+
action_input = match.group(2)
|
|
280
|
+
# Return the action and action input
|
|
281
|
+
action_dict = None
|
|
282
|
+
action_input = action_input.strip(" ").strip('"')
|
|
283
|
+
action_input_replaced = action_input.replace("'", '"')
|
|
284
|
+
try:
|
|
285
|
+
action_dict = json.loads(action_input_replaced)
|
|
286
|
+
except json.JSONDecodeError:
|
|
287
|
+
pass
|
|
288
|
+
if action_dict is None:
|
|
289
|
+
# try using ast to parse input string
|
|
290
|
+
import ast
|
|
291
|
+
try:
|
|
292
|
+
action_dict = ast.literal_eval(action_input)
|
|
293
|
+
if not isinstance(action_dict, dict):
|
|
294
|
+
action_dict = None
|
|
295
|
+
except Exception as e:
|
|
296
|
+
pass
|
|
297
|
+
return AgentAction(
|
|
298
|
+
tool=action,
|
|
299
|
+
tool_input=action_dict if action_dict is not None else action_input,
|
|
300
|
+
log=llm_output
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
def get_tool_names_and_descriptions(tools: List[BaseTool]) -> str:
|
|
304
|
+
tool_names = []
|
|
305
|
+
tools_descriptions = ""
|
|
306
|
+
for tool in tools:
|
|
307
|
+
tools_descriptions += f"name: {tool.name}, description: {tool.description}\n"
|
|
308
|
+
tool_names.append(tool.name)
|
|
309
|
+
return str(tool_names), tools_descriptions
|
|
310
|
+
|
|
311
|
+
def generate_repo_structure_prompt(
|
|
312
|
+
files: List[str],
|
|
313
|
+
dir_path: str="",
|
|
314
|
+
) -> str:
|
|
315
|
+
# Convert the repo structure to a string
|
|
316
|
+
file_pairs = [(f, get_file_type(os.path.join(dir_path, f)).value) for f in files]
|
|
317
|
+
repo_structure = ""
|
|
318
|
+
for f, f_type in file_pairs:
|
|
319
|
+
repo_structure += f"{f} - {f_type}\n"
|
|
320
|
+
return repo_structure
|
|
321
|
+
|
|
322
|
+
class ObservationResult(BaseModel):
|
|
323
|
+
Analysis: Optional[str]=Field(description="Analyzing the goal, repository file structure and intermediate output.")
|
|
324
|
+
FinalAnswer: Optional[str]=Field(description="the final answer for the goal")
|
|
325
|
+
Thoughts: Optional[str]=Field(description="If the information is insufficient, the thoughts will be given and be taken into consideration in next round.")
|
|
326
|
+
|
|
327
|
+
def convert_plan_to_string(plan: PlanAgentResult) -> str:
|
|
328
|
+
plan_str = ""
|
|
329
|
+
for action in plan.actions:
|
|
330
|
+
action_str = f"Step: {action['name']}\n"
|
|
331
|
+
action_str += f"Step Input: {action['input']}\n"
|
|
332
|
+
plan_str += action_str
|
|
333
|
+
return plan_str
|
|
334
|
+
|
|
335
|
+
def run_command(command: list, cwd: str = None, timeout: int = None):
|
|
336
|
+
"""
|
|
337
|
+
Run a shell command with optional timeout and return stdout, stderr, and return code.
|
|
338
|
+
"""
|
|
339
|
+
try:
|
|
340
|
+
result = subprocess.run(
|
|
341
|
+
command,
|
|
342
|
+
cwd=cwd,
|
|
343
|
+
stdout=subprocess.PIPE,
|
|
344
|
+
stderr=subprocess.PIPE,
|
|
345
|
+
text=True,
|
|
346
|
+
timeout=timeout
|
|
347
|
+
)
|
|
348
|
+
return result.stdout, result.stderr, result.returncode
|
|
349
|
+
except subprocess.TimeoutExpired as e:
|
|
350
|
+
return e.stdout or "", e.stderr or f"Command timed out after {timeout} seconds", -1
|
|
351
|
+
|
|
352
|
+
def escape_braces(text: str) -> str:
|
|
353
|
+
# First replace single } not part of }} with }}
|
|
354
|
+
text = re.sub(r'(?<!})}(?!})', '}}', text)
|
|
355
|
+
# Then replace single { not part of {{
|
|
356
|
+
text = re.sub(r'(?<!{){(?!{)', '{{', text)
|
|
357
|
+
return text
|