greydgl--pentestgpt
6a767aae02
* feat: 🎸 add new models * minor readme update * style: format code with Black This commit fixes the style issues introduced in dafa18b according to the output from Black. Details: https://github.com/GreyDGL/PentestGPT/pull/274 --------- Co-authored-by: deepsource-autofix[bot] <62050782+deepsource-autofix[bot]@users.noreply.github.com>
196 行
7.2 KiB
Python
196 行
7.2 KiB
Python
import asyncio
|
|
import json
|
|
import os
|
|
import re
|
|
import time
|
|
from datetime import UTC, datetime
|
|
from typing import Dict
|
|
|
|
import tiktoken
|
|
import yaml
|
|
from loguru import logger
|
|
|
|
from llm_generation.conversation_manager import ConversationManager
|
|
from llm_generation.models import get_model
|
|
from llm_generation.models.base import BaseModel
|
|
|
|
|
|
class TaskProcessor:
|
|
|
|
def __init__(
|
|
self, prompt_template_config_path: str, model_name: str, streaming_callback=None
|
|
):
|
|
self.conversation_manager: ConversationManager = ConversationManager()
|
|
self.model_name = model_name
|
|
# Load the task config -> the whole config file
|
|
self.task_config: Dict = self._load_prompt_template_config(
|
|
prompt_template_config_path, model_name
|
|
)
|
|
self._validate_config()
|
|
|
|
# Load model task config -> the specific model config
|
|
self.model_task_config: Dict = self.task_config["model"][model_name]
|
|
|
|
# Load system prompt
|
|
self.system_prompt = self.model_task_config.get("system_prompt", None)
|
|
|
|
# Load the model
|
|
self.model: BaseModel = get_model(model_name)
|
|
|
|
# Callback function for streaming
|
|
self.streaming_callback = streaming_callback
|
|
if self.streaming_callback:
|
|
self.model.set_streaming_callback(self.streaming_callback)
|
|
|
|
def init_system_prompt(self, **kwargs):
|
|
if self.system_prompt:
|
|
self.system_prompt = self.system_prompt.format(**kwargs)
|
|
else:
|
|
logger.warning("System prompt not found in the model task config")
|
|
|
|
def _validate_config(self):
|
|
if "model" not in self.task_config:
|
|
raise ValueError(
|
|
f"`model` property not found in the prompt template config"
|
|
)
|
|
if self.model_name not in self.task_config["model"]:
|
|
raise ValueError(
|
|
f"Model {self.model_name} not found in the prompt template config"
|
|
)
|
|
if "rounds" not in self.task_config["model"][self.model_name]:
|
|
raise ValueError(
|
|
f"`rounds` property not found in the prompt template config"
|
|
)
|
|
if not isinstance(self.task_config["model"][self.model_name]["rounds"], dict):
|
|
raise ValueError(
|
|
f"`rounds` property should be a dict in the prompt template config"
|
|
)
|
|
for round_count in self.task_config["model"][self.model_name]["rounds"]:
|
|
if (
|
|
"prompt"
|
|
not in self.task_config["model"][self.model_name]["rounds"][round_count]
|
|
):
|
|
raise ValueError(
|
|
f"`prompt` property not found in the prompt template config for conversation round {round}"
|
|
)
|
|
|
|
def _load_prompt_template_config(
|
|
self, prompt_template_config_path: str, model_name: str
|
|
):
|
|
base_folder = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
|
template_config_path = os.path.join(base_folder, prompt_template_config_path)
|
|
logger.info(f"Loading prompt template config from {template_config_path}")
|
|
with open(template_config_path, "r") as f:
|
|
prompt_template_config = yaml.safe_load(f)
|
|
return prompt_template_config
|
|
|
|
async def _call_model(self, user_prompt: str, conversation_round: int):
|
|
conversation = []
|
|
# Check if the system message is needed
|
|
if self.system_prompt and len(self.conversation_manager.get_history()) == 0:
|
|
system_message = self.system_prompt
|
|
self.conversation_manager.add_system_message(system_message)
|
|
|
|
# Add existing conversation
|
|
conversation.extend(self.conversation_manager.get_history())
|
|
|
|
# Generation parameters
|
|
generation_parameters = self.model_task_config["rounds"][
|
|
conversation_round
|
|
].get("generation_parameters", {})
|
|
|
|
# Generate response
|
|
response = await self.model.generate_response(
|
|
user_prompt, conversation, **generation_parameters
|
|
)
|
|
|
|
# Add assistant message to the conversation
|
|
self.conversation_manager.add_user_message(user_prompt)
|
|
self.conversation_manager.add_assistant_message(response)
|
|
return response
|
|
|
|
def _format_prompt(self, conversation_round: int, **kwargs):
|
|
if conversation_round not in self.model_task_config["rounds"]:
|
|
raise ValueError(
|
|
f"Conversation round {conversation_round} not found in the model task config"
|
|
)
|
|
if "prompt" not in self.model_task_config["rounds"][conversation_round]:
|
|
raise ValueError(
|
|
f"`prompt` property not found in the model task config for conversation round {conversation_round}"
|
|
)
|
|
prompt_template = self.model_task_config["rounds"][conversation_round]["prompt"]
|
|
return prompt_template.format(**kwargs)
|
|
|
|
def _extract_json(self, raw_text):
|
|
# Regular expression to match JSON content inside triple backticks
|
|
match = re.search(r"```(?:json)?\n?(.*?)\n?```", raw_text, re.DOTALL)
|
|
if match:
|
|
json_str = match.group(1)
|
|
try:
|
|
return json.loads(json_str)
|
|
except json.JSONDecodeError:
|
|
logger.exception(
|
|
f"Error: Extracted content is not valid JSON. {json_str}"
|
|
)
|
|
else:
|
|
return json.loads(raw_text)
|
|
|
|
def _remove_thinking_text(self, raw_text):
|
|
# Remove the thinking text from the generated response by deepseek-r1
|
|
if "</think>" in raw_text:
|
|
return raw_text[raw_text.index("</think>") + len("</think>") :].strip()
|
|
return raw_text
|
|
|
|
async def run(
|
|
self,
|
|
conversation_round=1,
|
|
is_json=False,
|
|
should_remove_thinking=False,
|
|
**kwargs,
|
|
):
|
|
user_prompt = self._format_prompt(
|
|
conversation_round=conversation_round, **kwargs
|
|
)
|
|
|
|
generation_start_time = time.time()
|
|
response = await self._call_model(
|
|
user_prompt=user_prompt, conversation_round=conversation_round
|
|
)
|
|
|
|
# Calculate token per second
|
|
tokenizer = tiktoken.encoding_for_model("gpt-4o")
|
|
response_token_count = len(tokenizer.encode(response))
|
|
logger.debug(
|
|
f"Response token count {(response_token_count)}, with {(response_token_count)/(time.time()-generation_start_time)} tps"
|
|
)
|
|
|
|
# Extract JSON from the response
|
|
if is_json:
|
|
return self._extract_json(response)
|
|
|
|
# Remove the thinking text from the generated response by reasoning models
|
|
if should_remove_thinking:
|
|
return self._remove_thinking_text(response)
|
|
return response
|
|
|
|
|
|
async def main():
|
|
def callback_func(content, delta):
|
|
print(f"Content: {content}")
|
|
print(delta)
|
|
|
|
task_processor = TaskProcessor(
|
|
prompt_template_config_path="prompt_template/public_terminal/reasoning_based_general_response.yml",
|
|
model_name="sonar-reasoning-pro",
|
|
streaming_callback=callback_func,
|
|
)
|
|
response = await task_processor.run(
|
|
current_date=f'{datetime.now(UTC).strftime("%YYYY-%mm-%dd")}',
|
|
user_question="what happened to bybit?",
|
|
)
|
|
print(response)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
asyncio.run(main())
|