bioguider 0.2.52__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.
- bioguider/__init__.py +0 -0
- bioguider/agents/__init__.py +0 -0
- bioguider/agents/agent_task.py +92 -0
- bioguider/agents/agent_tools.py +176 -0
- bioguider/agents/agent_utils.py +504 -0
- bioguider/agents/collection_execute_step.py +182 -0
- bioguider/agents/collection_observe_step.py +125 -0
- bioguider/agents/collection_plan_step.py +156 -0
- bioguider/agents/collection_task.py +184 -0
- bioguider/agents/collection_task_utils.py +142 -0
- bioguider/agents/common_agent.py +137 -0
- bioguider/agents/common_agent_2step.py +215 -0
- bioguider/agents/common_conversation.py +61 -0
- bioguider/agents/common_step.py +85 -0
- bioguider/agents/consistency_collection_step.py +102 -0
- bioguider/agents/consistency_evaluation_task.py +57 -0
- bioguider/agents/consistency_evaluation_task_utils.py +14 -0
- bioguider/agents/consistency_observe_step.py +110 -0
- bioguider/agents/consistency_query_step.py +77 -0
- bioguider/agents/dockergeneration_execute_step.py +186 -0
- bioguider/agents/dockergeneration_observe_step.py +154 -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_installation_task.py +270 -0
- bioguider/agents/evaluation_readme_task.py +767 -0
- bioguider/agents/evaluation_submission_requirements_task.py +172 -0
- bioguider/agents/evaluation_task.py +206 -0
- bioguider/agents/evaluation_tutorial_task.py +169 -0
- bioguider/agents/evaluation_tutorial_task_prompts.py +187 -0
- bioguider/agents/evaluation_userguide_prompts.py +179 -0
- bioguider/agents/evaluation_userguide_task.py +154 -0
- bioguider/agents/evaluation_utils.py +127 -0
- bioguider/agents/identification_execute_step.py +181 -0
- bioguider/agents/identification_observe_step.py +104 -0
- bioguider/agents/identification_plan_step.py +140 -0
- bioguider/agents/identification_task.py +270 -0
- bioguider/agents/identification_task_utils.py +22 -0
- bioguider/agents/peo_common_step.py +64 -0
- bioguider/agents/prompt_utils.py +253 -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/code_structure_db.py +500 -0
- bioguider/database/summarized_file_db.py +146 -0
- bioguider/generation/__init__.py +39 -0
- bioguider/generation/benchmark_metrics.py +610 -0
- bioguider/generation/change_planner.py +189 -0
- bioguider/generation/document_renderer.py +157 -0
- bioguider/generation/llm_cleaner.py +67 -0
- bioguider/generation/llm_content_generator.py +1128 -0
- bioguider/generation/llm_injector.py +809 -0
- bioguider/generation/models.py +85 -0
- bioguider/generation/output_manager.py +74 -0
- bioguider/generation/repo_reader.py +37 -0
- bioguider/generation/report_loader.py +166 -0
- bioguider/generation/style_analyzer.py +36 -0
- bioguider/generation/suggestion_extractor.py +436 -0
- bioguider/generation/test_metrics.py +189 -0
- bioguider/managers/benchmark_manager.py +785 -0
- bioguider/managers/evaluation_manager.py +215 -0
- bioguider/managers/generation_manager.py +686 -0
- bioguider/managers/generation_test_manager.py +107 -0
- bioguider/managers/generation_test_manager_v2.py +525 -0
- bioguider/rag/__init__.py +0 -0
- bioguider/rag/config.py +117 -0
- bioguider/rag/data_pipeline.py +651 -0
- bioguider/rag/embedder.py +24 -0
- bioguider/rag/rag.py +138 -0
- bioguider/settings.py +103 -0
- bioguider/utils/code_structure_builder.py +59 -0
- bioguider/utils/constants.py +135 -0
- bioguider/utils/default.gitignore +140 -0
- bioguider/utils/file_utils.py +215 -0
- bioguider/utils/gitignore_checker.py +175 -0
- bioguider/utils/notebook_utils.py +117 -0
- bioguider/utils/pyphen_utils.py +73 -0
- bioguider/utils/python_file_handler.py +65 -0
- bioguider/utils/r_file_handler.py +551 -0
- bioguider/utils/utils.py +163 -0
- bioguider-0.2.52.dist-info/LICENSE +21 -0
- bioguider-0.2.52.dist-info/METADATA +51 -0
- bioguider-0.2.52.dist-info/RECORD +84 -0
- bioguider-0.2.52.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
|
|
2
|
+
from typing import Any, Callable, Optional
|
|
3
|
+
from langchain_core.prompts import ChatPromptTemplate
|
|
4
|
+
from langchain_openai.chat_models.base import BaseChatOpenAI
|
|
5
|
+
from langchain_community.callbacks.openai_info import OpenAICallbackHandler
|
|
6
|
+
from langchain_core.messages import SystemMessage, HumanMessage
|
|
7
|
+
from pydantic import BaseModel, Field
|
|
8
|
+
from tenacity import retry, stop_after_attempt, wait_incrementing
|
|
9
|
+
import logging
|
|
10
|
+
|
|
11
|
+
from bioguider.utils.utils import escape_braces, increase_token_usage
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
class RetryException(Exception):
|
|
16
|
+
"""Exception need to retry"""
|
|
17
|
+
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
class CommonAgentResult(BaseModel):
|
|
21
|
+
reasoning_process: str = Field(
|
|
22
|
+
description="A detailed explanation of the thought process or reasoning steps taken to reach a conclusion."
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
class CommonAgent:
|
|
26
|
+
def __init__(self, llm: BaseChatOpenAI):
|
|
27
|
+
self.llm = llm
|
|
28
|
+
self.exception: RetryException | None = None
|
|
29
|
+
self.token_usage: dict | None = None
|
|
30
|
+
|
|
31
|
+
def go(
|
|
32
|
+
self,
|
|
33
|
+
system_prompt: str,
|
|
34
|
+
instruction_prompt: str,
|
|
35
|
+
schema: any,
|
|
36
|
+
pre_process: Optional[Callable] = None,
|
|
37
|
+
post_process: Optional[Callable] = None,
|
|
38
|
+
**kwargs: Optional[Any],
|
|
39
|
+
):
|
|
40
|
+
"""
|
|
41
|
+
execute agent
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
system_prompt str: system prompt
|
|
45
|
+
instruction_prompt str: user prompt to guide how llm execute agent
|
|
46
|
+
schema pydantic.BaseModel or json schema: llm output result schema
|
|
47
|
+
pre_process Callable or None: pre-processor that would be executed before llm.invoke
|
|
48
|
+
post_process Callable or None: post-processor that would be executed after llm.invoke
|
|
49
|
+
kwargs None or dict: args for pre_proces and post_process
|
|
50
|
+
|
|
51
|
+
Return:
|
|
52
|
+
(output that comply with input args `schema`)
|
|
53
|
+
"""
|
|
54
|
+
self._initialize()
|
|
55
|
+
if pre_process is not None:
|
|
56
|
+
is_OK = pre_process(**kwargs)
|
|
57
|
+
if not is_OK: # skip
|
|
58
|
+
return
|
|
59
|
+
|
|
60
|
+
return self._invoke_agent(
|
|
61
|
+
system_prompt,
|
|
62
|
+
instruction_prompt,
|
|
63
|
+
schema,
|
|
64
|
+
post_process,
|
|
65
|
+
**kwargs,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
def _initialize(self):
|
|
69
|
+
self.exception = None
|
|
70
|
+
self.token_usage = None
|
|
71
|
+
|
|
72
|
+
def _process_retryexception_message(
|
|
73
|
+
self, prompt: ChatPromptTemplate
|
|
74
|
+
) -> ChatPromptTemplate:
|
|
75
|
+
if self.exception is None:
|
|
76
|
+
return prompt
|
|
77
|
+
|
|
78
|
+
existing_messages = prompt.messages
|
|
79
|
+
updated_messages = existing_messages + [("human", str(self.exception))]
|
|
80
|
+
self.exception = None
|
|
81
|
+
updated_prompt = ChatPromptTemplate.from_messages(updated_messages)
|
|
82
|
+
return updated_prompt
|
|
83
|
+
|
|
84
|
+
def _incre_token_usage(self, token_usage):
|
|
85
|
+
incremental_token_usage = token_usage
|
|
86
|
+
if not isinstance(token_usage, dict):
|
|
87
|
+
incremental_token_usage = vars(incremental_token_usage)
|
|
88
|
+
self.token_usage = increase_token_usage(
|
|
89
|
+
self.token_usage, incremental_token_usage
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
@retry(
|
|
93
|
+
stop=stop_after_attempt(5),
|
|
94
|
+
wait=wait_incrementing(start=1.0, increment=3, max=10),
|
|
95
|
+
)
|
|
96
|
+
def _invoke_agent(
|
|
97
|
+
self,
|
|
98
|
+
system_prompt: str,
|
|
99
|
+
instruction_prompt: str,
|
|
100
|
+
schema: any,
|
|
101
|
+
post_process: Optional[Callable] = None,
|
|
102
|
+
**kwargs: Optional[Any],
|
|
103
|
+
) -> tuple[Any, Any, dict | None, Any | None]:
|
|
104
|
+
system_prompt = escape_braces(system_prompt)
|
|
105
|
+
prompt = ChatPromptTemplate.from_messages([
|
|
106
|
+
("system", system_prompt),
|
|
107
|
+
("human", instruction_prompt),
|
|
108
|
+
])
|
|
109
|
+
# Initialize the callback handler
|
|
110
|
+
callback_handler = OpenAICallbackHandler()
|
|
111
|
+
|
|
112
|
+
updated_prompt = self._process_retryexception_message(prompt)
|
|
113
|
+
agent = updated_prompt | self.llm.with_structured_output(schema)
|
|
114
|
+
try:
|
|
115
|
+
res = agent.invoke(
|
|
116
|
+
input={},
|
|
117
|
+
config={
|
|
118
|
+
"callbacks": [callback_handler],
|
|
119
|
+
},
|
|
120
|
+
)
|
|
121
|
+
self._incre_token_usage(callback_handler)
|
|
122
|
+
except Exception as e:
|
|
123
|
+
logger.error(str(e))
|
|
124
|
+
raise e
|
|
125
|
+
processed_res = res
|
|
126
|
+
if post_process is not None:
|
|
127
|
+
try:
|
|
128
|
+
processed_res = post_process(res, **kwargs)
|
|
129
|
+
except RetryException as e:
|
|
130
|
+
logger.error(str(e))
|
|
131
|
+
self.exception = e
|
|
132
|
+
raise e
|
|
133
|
+
except Exception as e:
|
|
134
|
+
logger.error(str(e))
|
|
135
|
+
raise e
|
|
136
|
+
return res, processed_res, self.token_usage, None
|
|
137
|
+
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
from typing import Any, Callable, Optional
|
|
2
|
+
from langchain_core.prompts import ChatPromptTemplate
|
|
3
|
+
from langchain_openai.chat_models.base import BaseChatOpenAI
|
|
4
|
+
from langchain_community.callbacks.openai_info import OpenAICallbackHandler
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
from tenacity import retry, stop_after_attempt, wait_incrementing
|
|
7
|
+
import logging
|
|
8
|
+
|
|
9
|
+
from bioguider.utils.utils import escape_braces
|
|
10
|
+
from bioguider.agents.common_agent import (
|
|
11
|
+
CommonAgent,
|
|
12
|
+
RetryException,
|
|
13
|
+
)
|
|
14
|
+
from bioguider.agents.prompt_utils import COT_USER_INSTRUCTION
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class CommonAgentTwoSteps(CommonAgent):
|
|
20
|
+
def __init__(self, llm: BaseChatOpenAI):
|
|
21
|
+
super().__init__(llm)
|
|
22
|
+
|
|
23
|
+
def _initialize(self):
|
|
24
|
+
self.exceptions = None
|
|
25
|
+
self.token_usage = None
|
|
26
|
+
|
|
27
|
+
def _get_retryexception_message(self) -> list[tuple[str, str]]:
|
|
28
|
+
if self.exceptions is None:
|
|
29
|
+
return None
|
|
30
|
+
return [("human", str(excp)) for excp in self.exceptions]
|
|
31
|
+
|
|
32
|
+
def _build_prompt_for_cot_step(
|
|
33
|
+
self,
|
|
34
|
+
system_prompt: str,
|
|
35
|
+
instruction_prompt: str,
|
|
36
|
+
):
|
|
37
|
+
# system_prompt = system_prompt.replace("{", "{{").replace("}", "}}")
|
|
38
|
+
system_prompt = escape_braces(system_prompt)
|
|
39
|
+
instruction_prompt = instruction_prompt.replace("{", "{{").replace("}", "}}")
|
|
40
|
+
msgs = [("system", system_prompt)]
|
|
41
|
+
msgs = msgs + [("human", instruction_prompt)]
|
|
42
|
+
exception_msgs = self._get_retryexception_message()
|
|
43
|
+
if exception_msgs is not None:
|
|
44
|
+
msgs = msgs + exception_msgs
|
|
45
|
+
msgs = msgs + [("human", COT_USER_INSTRUCTION)]
|
|
46
|
+
return ChatPromptTemplate.from_messages(msgs)
|
|
47
|
+
|
|
48
|
+
def _build_prompt_for_final_step(
|
|
49
|
+
self,
|
|
50
|
+
system_prompt: str,
|
|
51
|
+
cot_msg: str,
|
|
52
|
+
):
|
|
53
|
+
system_prompt = system_prompt.replace("{", "{{").replace("}", "}}")
|
|
54
|
+
msgs = [("system", system_prompt)]
|
|
55
|
+
cot_msg = cot_msg.replace("{", "{{").replace("}", "}}")
|
|
56
|
+
msgs = msgs + [(
|
|
57
|
+
"human",
|
|
58
|
+
f"Please review the following step-by-step reasoning and provide the answer based on it: ```{cot_msg}```"
|
|
59
|
+
)]
|
|
60
|
+
return ChatPromptTemplate.from_messages(msgs)
|
|
61
|
+
|
|
62
|
+
@retry(
|
|
63
|
+
stop=stop_after_attempt(5),
|
|
64
|
+
wait=wait_incrementing(start=1.0, increment=3, max=10),
|
|
65
|
+
)
|
|
66
|
+
def _invoke_agent(
|
|
67
|
+
self,
|
|
68
|
+
system_prompt: str,
|
|
69
|
+
instruction_prompt: str,
|
|
70
|
+
schema: any,
|
|
71
|
+
post_process: Optional[Callable] = None,
|
|
72
|
+
**kwargs: Optional[Any],
|
|
73
|
+
):
|
|
74
|
+
# Initialize the callback handler
|
|
75
|
+
callback_handler = OpenAICallbackHandler()
|
|
76
|
+
cot_prompt = self._build_prompt_for_cot_step(
|
|
77
|
+
system_prompt=system_prompt,
|
|
78
|
+
instruction_prompt=instruction_prompt
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
# First, use llm to do CoT
|
|
83
|
+
msgs = cot_prompt.invoke(input={}).to_messages()
|
|
84
|
+
|
|
85
|
+
cot_res = self.llm.generate(messages=[msgs])
|
|
86
|
+
reasoning_process = cot_res.generations[0][0].text
|
|
87
|
+
token_usage = cot_res.llm_output.get("token_usage")
|
|
88
|
+
cot_tokens = {
|
|
89
|
+
"total_tokens": token_usage.get("total_tokens", 0),
|
|
90
|
+
"prompt_tokens": token_usage.get("prompt_tokens", 0),
|
|
91
|
+
"completion_tokens": token_usage.get("completion_tokens", 0),
|
|
92
|
+
}
|
|
93
|
+
self._incre_token_usage(cot_tokens)
|
|
94
|
+
except Exception as e:
|
|
95
|
+
logger.error(str(e))
|
|
96
|
+
raise e
|
|
97
|
+
|
|
98
|
+
# Then use the reasoning process to do the structured output
|
|
99
|
+
updated_prompt = self._build_prompt_for_final_step(
|
|
100
|
+
system_prompt=system_prompt,
|
|
101
|
+
cot_msg=reasoning_process,
|
|
102
|
+
)
|
|
103
|
+
agent = updated_prompt | self.llm.with_structured_output(schema)
|
|
104
|
+
try:
|
|
105
|
+
res = agent.invoke(
|
|
106
|
+
input={},
|
|
107
|
+
config={
|
|
108
|
+
"callbacks": [callback_handler],
|
|
109
|
+
},
|
|
110
|
+
)
|
|
111
|
+
self._incre_token_usage(callback_handler)
|
|
112
|
+
except Exception as e:
|
|
113
|
+
logger.error(str(e))
|
|
114
|
+
raise e
|
|
115
|
+
processed_res = None
|
|
116
|
+
if post_process is not None:
|
|
117
|
+
try:
|
|
118
|
+
processed_res = post_process(res, **kwargs)
|
|
119
|
+
except RetryException as e:
|
|
120
|
+
logger.error(str(e))
|
|
121
|
+
self.exceptions = [e] if self.exceptions is None else self.exceptions + [e]
|
|
122
|
+
raise e
|
|
123
|
+
except Exception as e:
|
|
124
|
+
logger.error(str(e))
|
|
125
|
+
raise e
|
|
126
|
+
return res, processed_res, self.token_usage, reasoning_process
|
|
127
|
+
|
|
128
|
+
FINAL_STEP_SYSTEM_PROMPTS = ChatPromptTemplate.from_template("""
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
You will be given a response generated by a LLM, which includes a **step-by-step reasoning process** followed by a clearly marked **final answer**.
|
|
132
|
+
|
|
133
|
+
### **Your Task:**
|
|
134
|
+
|
|
135
|
+
Extract and return only the content of the **final answer**.
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
### **Important Instructions:**
|
|
140
|
+
1. Your task is to **extract only the final answer** from the provided reasoning process.
|
|
141
|
+
**Do not** make any judgments, interpretations, or modifications to the content.
|
|
142
|
+
|
|
143
|
+
### **Input:**
|
|
144
|
+
|
|
145
|
+
{llm_response}
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
""")
|
|
149
|
+
|
|
150
|
+
class CommonAgentTwoChainSteps(CommonAgentTwoSteps):
|
|
151
|
+
def __init__(self, llm):
|
|
152
|
+
super().__init__(llm)
|
|
153
|
+
|
|
154
|
+
def _invoke_agent(self, system_prompt, instruction_prompt, schema, post_process = None, **kwargs):
|
|
155
|
+
# Initialize the callback handler
|
|
156
|
+
callback_handler = OpenAICallbackHandler()
|
|
157
|
+
processed_system_prompt = system_prompt.replace("{", "{{").replace("}", "}}")
|
|
158
|
+
cot_prompt = self._build_prompt_for_cot_step(
|
|
159
|
+
system_prompt=processed_system_prompt,
|
|
160
|
+
instruction_prompt=instruction_prompt
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
try:
|
|
164
|
+
# First, use llm to do CoT
|
|
165
|
+
msgs = cot_prompt.invoke(input={}).to_messages()
|
|
166
|
+
|
|
167
|
+
cot_res = self.llm.generate(messages=[msgs])
|
|
168
|
+
if cot_res is None or cot_res.llm_output is None:
|
|
169
|
+
raise Exception("llm generate invalid output")
|
|
170
|
+
reasoning_process = cot_res.generations[0][0].text
|
|
171
|
+
token_usage: Any = cot_res.llm_output.get("token_usage")
|
|
172
|
+
cot_tokens = {
|
|
173
|
+
"total_tokens": token_usage.get("total_tokens", 0),
|
|
174
|
+
"prompt_tokens": token_usage.get("prompt_tokens", 0),
|
|
175
|
+
"completion_tokens": token_usage.get("completion_tokens", 0),
|
|
176
|
+
}
|
|
177
|
+
self._incre_token_usage(cot_tokens)
|
|
178
|
+
except Exception as e:
|
|
179
|
+
logger.error(str(e))
|
|
180
|
+
raise e
|
|
181
|
+
|
|
182
|
+
try:
|
|
183
|
+
# Then use the reasoning process to do the structured output
|
|
184
|
+
processed_reasoning_process = reasoning_process.replace("{", "{{").replace("}", "}}")
|
|
185
|
+
final_msg = FINAL_STEP_SYSTEM_PROMPTS.format(
|
|
186
|
+
llm_response=processed_reasoning_process,
|
|
187
|
+
)
|
|
188
|
+
msgs = [(
|
|
189
|
+
"human",
|
|
190
|
+
final_msg,
|
|
191
|
+
)]
|
|
192
|
+
final_prompt = ChatPromptTemplate.from_messages(msgs)
|
|
193
|
+
agent = final_prompt | self.llm.with_structured_output(schema)
|
|
194
|
+
res = agent.invoke(
|
|
195
|
+
input={},
|
|
196
|
+
config={
|
|
197
|
+
"callbacks": [callback_handler],
|
|
198
|
+
},
|
|
199
|
+
)
|
|
200
|
+
self._incre_token_usage(callback_handler)
|
|
201
|
+
except Exception as e:
|
|
202
|
+
logger.error(str(e))
|
|
203
|
+
raise e
|
|
204
|
+
processed_res = None
|
|
205
|
+
if post_process is not None:
|
|
206
|
+
try:
|
|
207
|
+
processed_res = post_process(res, **kwargs)
|
|
208
|
+
except RetryException as e:
|
|
209
|
+
logger.error(str(e))
|
|
210
|
+
self.exceptions = [e] if self.exceptions is None else self.exceptions + [e]
|
|
211
|
+
raise e
|
|
212
|
+
except Exception as e:
|
|
213
|
+
logger.error(str(e))
|
|
214
|
+
raise e
|
|
215
|
+
return res, processed_res, self.token_usage, reasoning_process
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from langchain_core.messages import SystemMessage, HumanMessage
|
|
2
|
+
from langchain_core.prompts import ChatPromptTemplate
|
|
3
|
+
from langchain_openai.chat_models.base import BaseChatOpenAI
|
|
4
|
+
from langchain_community.callbacks.openai_info import OpenAICallbackHandler
|
|
5
|
+
from bioguider.utils.utils import escape_braces
|
|
6
|
+
|
|
7
|
+
class CommonConversation:
|
|
8
|
+
def __init__(self, llm: BaseChatOpenAI):
|
|
9
|
+
self.llm = llm
|
|
10
|
+
|
|
11
|
+
def generate(self, system_prompt: str, instruction_prompt: str):
|
|
12
|
+
msgs = [
|
|
13
|
+
SystemMessage(system_prompt),
|
|
14
|
+
HumanMessage(instruction_prompt),
|
|
15
|
+
]
|
|
16
|
+
callback_handler = OpenAICallbackHandler()
|
|
17
|
+
result = self.llm.generate(
|
|
18
|
+
messages=[msgs],
|
|
19
|
+
callbacks=[callback_handler]
|
|
20
|
+
)
|
|
21
|
+
response = result.generations[0][0].text
|
|
22
|
+
# Try to normalize token usage across providers
|
|
23
|
+
token_usage = {}
|
|
24
|
+
try:
|
|
25
|
+
if hasattr(result, "llm_output") and result.llm_output is not None:
|
|
26
|
+
raw = result.llm_output.get("token_usage") or result.llm_output.get("usage")
|
|
27
|
+
if isinstance(raw, dict):
|
|
28
|
+
token_usage = {
|
|
29
|
+
"total_tokens": raw.get("total_tokens") or raw.get("total"),
|
|
30
|
+
"prompt_tokens": raw.get("prompt_tokens") or raw.get("prompt"),
|
|
31
|
+
"completion_tokens": raw.get("completion_tokens") or raw.get("completion"),
|
|
32
|
+
}
|
|
33
|
+
except Exception:
|
|
34
|
+
pass
|
|
35
|
+
if not token_usage:
|
|
36
|
+
token_usage = {
|
|
37
|
+
"total_tokens": getattr(callback_handler, "total_tokens", 0),
|
|
38
|
+
"prompt_tokens": getattr(callback_handler, "prompt_tokens", 0),
|
|
39
|
+
"completion_tokens": getattr(callback_handler, "completion_tokens", 0),
|
|
40
|
+
}
|
|
41
|
+
return response, token_usage
|
|
42
|
+
|
|
43
|
+
def generate_with_schema(self, system_prompt: str, instruction_prompt: str, schema: any):
|
|
44
|
+
system_prompt = escape_braces(system_prompt)
|
|
45
|
+
instruction_prompt = escape_braces(instruction_prompt)
|
|
46
|
+
msgs = [
|
|
47
|
+
SystemMessage(system_prompt),
|
|
48
|
+
HumanMessage(instruction_prompt),
|
|
49
|
+
]
|
|
50
|
+
msgs_template = ChatPromptTemplate.from_messages(messages=msgs)
|
|
51
|
+
callback_handler = OpenAICallbackHandler()
|
|
52
|
+
agent = msgs_template | self.llm.with_structured_output(schema)
|
|
53
|
+
result = agent.invoke(
|
|
54
|
+
input={},
|
|
55
|
+
config={
|
|
56
|
+
"callbacks": [callback_handler],
|
|
57
|
+
},
|
|
58
|
+
)
|
|
59
|
+
token_usage = vars(callback_handler)
|
|
60
|
+
return result, token_usage
|
|
61
|
+
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
|
+
from typing import Any, Callable, Dict, Optional, TypedDict
|
|
4
|
+
import logging
|
|
5
|
+
from langchain_openai.chat_models.base import BaseChatOpenAI
|
|
6
|
+
|
|
7
|
+
from bioguider.utils.constants import DEFAULT_TOKEN_USAGE
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
class CommonState(TypedDict):
|
|
12
|
+
"""
|
|
13
|
+
CommonState is a TypedDict that defines the structure of the state
|
|
14
|
+
used in the CommonStep class.
|
|
15
|
+
"""
|
|
16
|
+
llm: Optional[BaseChatOpenAI]
|
|
17
|
+
step_output_callback: Optional[Callable]
|
|
18
|
+
|
|
19
|
+
class CommonStep(ABC):
|
|
20
|
+
"""
|
|
21
|
+
CommonStep is a base class for defining common steps in a workflow.
|
|
22
|
+
It provides methods to execute the step and handle exceptions.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self):
|
|
26
|
+
super().__init__()
|
|
27
|
+
self.step_name = ""
|
|
28
|
+
|
|
29
|
+
def enter_step(self, state):
|
|
30
|
+
if state["step_output_callback"] is None:
|
|
31
|
+
return
|
|
32
|
+
state["step_output_callback"](
|
|
33
|
+
step_name=self.step_name,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
def leave_step(self, state, token_usage: Optional[dict[str, int]] = None):
|
|
37
|
+
if state["step_output_callback"] is None:
|
|
38
|
+
return
|
|
39
|
+
if token_usage is not None:
|
|
40
|
+
state["step_output_callback"](token_usage=token_usage)
|
|
41
|
+
|
|
42
|
+
def execute(self, state):
|
|
43
|
+
"""
|
|
44
|
+
Execute the step. This method should be overridden by subclasses.
|
|
45
|
+
"""
|
|
46
|
+
self.enter_step(state)
|
|
47
|
+
state, token_usage = self._execute_directly(state)
|
|
48
|
+
self.leave_step(state, token_usage)
|
|
49
|
+
return state
|
|
50
|
+
|
|
51
|
+
def _print_step(
|
|
52
|
+
self,
|
|
53
|
+
state,
|
|
54
|
+
step_name: str | None = None,
|
|
55
|
+
step_output: str | None = None,
|
|
56
|
+
token_usage: dict | object | None = None,
|
|
57
|
+
):
|
|
58
|
+
step_callback = state["step_output_callback"]
|
|
59
|
+
if step_callback is None:
|
|
60
|
+
return
|
|
61
|
+
# convert token_usage to dict
|
|
62
|
+
if token_usage is not None and not isinstance(token_usage, dict):
|
|
63
|
+
token_usage = vars(token_usage)
|
|
64
|
+
# In case token_usage.total_tokens is 0
|
|
65
|
+
token_usage = { **DEFAULT_TOKEN_USAGE, **token_usage }
|
|
66
|
+
step_callback(
|
|
67
|
+
step_name=step_name,
|
|
68
|
+
step_output=step_output,
|
|
69
|
+
token_usage=token_usage,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
@abstractmethod
|
|
73
|
+
def _execute_directly(self, state) -> tuple[dict, dict[str, int]]:
|
|
74
|
+
"""
|
|
75
|
+
Execute the step directly. This method should be overridden by subclasses.
|
|
76
|
+
Args:
|
|
77
|
+
state (CommonState): The state of the workflow.
|
|
78
|
+
Returns:
|
|
79
|
+
tuple[dict, dict[str, int]]: The updated state and token usage.
|
|
80
|
+
"""
|
|
81
|
+
pass
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
from langchain.prompts import ChatPromptTemplate
|
|
5
|
+
from langchain_openai.chat_models.base import BaseChatOpenAI
|
|
6
|
+
from pydantic import BaseModel, Field
|
|
7
|
+
from bioguider.agents.common_agent_2step import CommonAgentTwoSteps
|
|
8
|
+
from bioguider.agents.consistency_evaluation_task_utils import ConsistencyEvaluationState
|
|
9
|
+
from bioguider.agents.peo_common_step import PEOCommonStep
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
CONSISTANCY_COLLECTION_SYSTEM_PROMPT = """
|
|
13
|
+
### **Goal**
|
|
14
|
+
You are an expert developer specializing in the biomedical domain.
|
|
15
|
+
You will be given a {domain} documentation. Your task is to collect all the functions, classes, and methods that the {domain} documentation mentions.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
### **Input {domain} Documentation**
|
|
20
|
+
{documentation}
|
|
21
|
+
|
|
22
|
+
### **Output Format**
|
|
23
|
+
The collected functions, classes, and methods **must exactly match** the following format, **do not** make up anything:
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
name: <function/class/method name>
|
|
27
|
+
file_path: <file path, if not sure, just put "N/A">
|
|
28
|
+
parameters: <parameters, if not sure, just put "N/A">
|
|
29
|
+
parent: <parent name, if it is a class method, put the class name as the parent name, if not sure, just put "N/A">
|
|
30
|
+
|
|
31
|
+
...
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
### **Output Example**
|
|
38
|
+
```
|
|
39
|
+
name: __init__
|
|
40
|
+
file_path: src/agents/common_agent.py
|
|
41
|
+
parameters: llm, step_output_callback, summarized_files_db
|
|
42
|
+
parent: CommonAgent
|
|
43
|
+
|
|
44
|
+
name: _invoke_agent
|
|
45
|
+
file_path: src/agents/common_agent.py
|
|
46
|
+
parameters: system_prompt, instruction_prompt, schema, post_process
|
|
47
|
+
parent: CommonAgent
|
|
48
|
+
|
|
49
|
+
...
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
class ConsistencyCollectionResult(BaseModel):
|
|
55
|
+
functions_and_classes: list[dict] = Field(description="A list of functions and classes that the documentation mentions")
|
|
56
|
+
|
|
57
|
+
ConsistencyCollectionResultJsonSchema = {
|
|
58
|
+
"properties": {
|
|
59
|
+
"functions_and_classes": {
|
|
60
|
+
"description": "A list of functions and classes that the documentation mentions",
|
|
61
|
+
"items": {
|
|
62
|
+
"type": "object"
|
|
63
|
+
},
|
|
64
|
+
"title": "Functions And Classes",
|
|
65
|
+
"type": "array"
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
"required": [
|
|
69
|
+
"functions_and_classes"
|
|
70
|
+
],
|
|
71
|
+
"title": "ConsistencyCollectionResult",
|
|
72
|
+
"type": "object"
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
class ConsistencyCollectionStep(PEOCommonStep):
|
|
76
|
+
def __init__(self, llm: BaseChatOpenAI):
|
|
77
|
+
super().__init__(llm)
|
|
78
|
+
self.step_name = "Consistency Collection Step"
|
|
79
|
+
|
|
80
|
+
def _prepare_system_prompt(self, state: ConsistencyEvaluationState) -> str:
|
|
81
|
+
documentation = state["documentation"]
|
|
82
|
+
domain = state["domain"]
|
|
83
|
+
return ChatPromptTemplate.from_template(CONSISTANCY_COLLECTION_SYSTEM_PROMPT).format(
|
|
84
|
+
domain=domain,
|
|
85
|
+
documentation=documentation,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
def _execute_directly(self, state: ConsistencyEvaluationState) -> tuple[dict, dict[str, int]]:
|
|
89
|
+
system_prompt = self._prepare_system_prompt(state)
|
|
90
|
+
agent = CommonAgentTwoSteps(llm=self.llm)
|
|
91
|
+
res, _, token_usage, reasoning_process = agent.go(
|
|
92
|
+
system_prompt=system_prompt,
|
|
93
|
+
instruction_prompt="Now, let's begin the consistency collection step.",
|
|
94
|
+
schema=ConsistencyCollectionResultJsonSchema,
|
|
95
|
+
)
|
|
96
|
+
res: ConsistencyCollectionResult = ConsistencyCollectionResult.model_validate(res)
|
|
97
|
+
state["functions_and_classes"] = res.functions_and_classes
|
|
98
|
+
self._print_step(state, step_output=f"Consistency Collection Result: {res.functions_and_classes}")
|
|
99
|
+
self._print_step(state, step_output=f"Consistency Collection Reasoning Process: {reasoning_process}")
|
|
100
|
+
|
|
101
|
+
return state, token_usage
|
|
102
|
+
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
from typing import Callable
|
|
5
|
+
from langchain_openai.chat_models.base import BaseChatOpenAI
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
|
|
8
|
+
from bioguider.agents.consistency_evaluation_task_utils import ConsistencyEvaluationState
|
|
9
|
+
from bioguider.database.code_structure_db import CodeStructureDb
|
|
10
|
+
from .consistency_collection_step import ConsistencyCollectionStep
|
|
11
|
+
from .consistency_query_step import ConsistencyQueryStep
|
|
12
|
+
from .consistency_observe_step import ConsistencyObserveStep
|
|
13
|
+
|
|
14
|
+
class ConsistencyEvaluationResult(BaseModel):
|
|
15
|
+
score: int
|
|
16
|
+
assessment: str
|
|
17
|
+
development: list[str]
|
|
18
|
+
strengths: list[str]
|
|
19
|
+
|
|
20
|
+
class ConsistencyEvaluationTask:
|
|
21
|
+
def __init__(
|
|
22
|
+
self,
|
|
23
|
+
llm: BaseChatOpenAI,
|
|
24
|
+
code_structure_db: CodeStructureDb,
|
|
25
|
+
step_callback: Callable | None = None
|
|
26
|
+
):
|
|
27
|
+
self.llm = llm
|
|
28
|
+
self.code_structure_db = code_structure_db
|
|
29
|
+
self.step_callback = step_callback
|
|
30
|
+
|
|
31
|
+
def evaluate(self, domain: str, documentation: str) -> ConsistencyEvaluationResult:
|
|
32
|
+
collection_step = ConsistencyCollectionStep(llm=self.llm)
|
|
33
|
+
query_step = ConsistencyQueryStep(code_structure_db=self.code_structure_db)
|
|
34
|
+
observe_step = ConsistencyObserveStep(llm=self.llm)
|
|
35
|
+
|
|
36
|
+
state = ConsistencyEvaluationState(
|
|
37
|
+
domain=domain,
|
|
38
|
+
documentation=documentation,
|
|
39
|
+
step_output_callback=self.step_callback,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
state = collection_step.execute(state)
|
|
43
|
+
state = query_step.execute(state)
|
|
44
|
+
state = observe_step.execute(state)
|
|
45
|
+
|
|
46
|
+
score = state["consistency_score"]
|
|
47
|
+
assessment = state["consistency_assessment"]
|
|
48
|
+
development = state["consistency_development"]
|
|
49
|
+
strengths = state["consistency_strengths"]
|
|
50
|
+
|
|
51
|
+
return ConsistencyEvaluationResult(
|
|
52
|
+
score=score,
|
|
53
|
+
assessment=assessment,
|
|
54
|
+
development=development,
|
|
55
|
+
strengths=strengths,
|
|
56
|
+
)
|
|
57
|
+
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
|
|
2
|
+
from typing import Callable, Optional, TypedDict
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class ConsistencyEvaluationState(TypedDict):
|
|
6
|
+
domain: str
|
|
7
|
+
documentation: str
|
|
8
|
+
step_output_callback: Optional[Callable]
|
|
9
|
+
functions_and_classes: Optional[list[dict]]
|
|
10
|
+
all_query_rows: Optional[list[any]]
|
|
11
|
+
consistency_score: Optional[int]
|
|
12
|
+
consistency_assessment: Optional[str]
|
|
13
|
+
consistency_development: Optional[list[str]]
|
|
14
|
+
consistency_strengths: Optional[list[str]]
|