ai-agent-book 精选快照(<2MB 代码与文档,来自 github.com/bojieli/ai-agent-book)
Build latest book artifacts / build (push) Canceled after 0s
dependency resolution / resolve (3.11) (push) Canceled after 0s
dependency resolution / resolve (3.13) (push) Canceled after 0s
deploy-pages / build (push) Canceled after 0s
deploy-pages / deploy (push) Canceled after 0s
i18n consistency check / check (push) Canceled after 0s
provider adoption tests / test (chapter2/context-compression) (push) Canceled after 0s
provider adoption tests / test (chapter2/prompt-injection) (push) Canceled after 0s
provider adoption tests / test (chapter2/system-hint) (push) Canceled after 0s
provider adoption tests / test (chapter3/log-sanitization) (push) Canceled after 0s
web-search-agent tests / test (push) Canceled after 0s
web-search-agent tests / agentbook (push) Canceled after 0s
Build latest book artifacts / build (push) Canceled after 0s
dependency resolution / resolve (3.11) (push) Canceled after 0s
dependency resolution / resolve (3.13) (push) Canceled after 0s
deploy-pages / build (push) Canceled after 0s
deploy-pages / deploy (push) Canceled after 0s
i18n consistency check / check (push) Canceled after 0s
provider adoption tests / test (chapter2/context-compression) (push) Canceled after 0s
provider adoption tests / test (chapter2/prompt-injection) (push) Canceled after 0s
provider adoption tests / test (chapter2/system-hint) (push) Canceled after 0s
provider adoption tests / test (chapter3/log-sanitization) (push) Canceled after 0s
web-search-agent tests / test (push) Canceled after 0s
web-search-agent tests / agentbook (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,259 @@
|
||||
import inspect
|
||||
import json
|
||||
import inspect
|
||||
import json
|
||||
import logging
|
||||
import time
|
||||
import uuid
|
||||
from typing import Generator, Iterator, AsyncGenerator, Optional
|
||||
|
||||
from aworld.core.task import Task
|
||||
from aworld.utils.common import get_local_ip
|
||||
from fastapi import status, HTTPException
|
||||
from fastapi.concurrency import run_in_threadpool
|
||||
from pydantic import BaseModel
|
||||
from starlette.responses import StreamingResponse
|
||||
|
||||
from aworldspace.base import AGENT_SPACE
|
||||
from aworldspace.utils.utils import get_last_user_message
|
||||
from base import OpenAIChatCompletionForm
|
||||
|
||||
async def generate_openai_chat_completion(form_data: OpenAIChatCompletionForm):
|
||||
messages = [message.model_dump() for message in form_data.messages]
|
||||
user_message = get_last_user_message(messages)
|
||||
PIPELINES = await AGENT_SPACE.get_agents_meta()
|
||||
PIPELINE_MODULES = await AGENT_SPACE.get_agent_modules()
|
||||
if (
|
||||
form_data.model not in PIPELINES
|
||||
or PIPELINES[form_data.model]["type"] == "filter"
|
||||
):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"Pipeline {form_data.model} not found",
|
||||
)
|
||||
|
||||
def job():
|
||||
pipeline = PIPELINES[form_data.model]
|
||||
pipeline_id = form_data.model
|
||||
|
||||
if pipeline["type"] == "manifold":
|
||||
manifold_id, pipeline_id = pipeline_id.split(".", 1)
|
||||
pipe = PIPELINE_MODULES[manifold_id].pipe
|
||||
else:
|
||||
pipe = PIPELINE_MODULES[pipeline_id].pipe
|
||||
|
||||
def process_line(model, line):
|
||||
if isinstance(line, Task):
|
||||
task_output_meta = line.outputs._metadata
|
||||
line = openai_chat_chunk_message_template(model, "", task_output_meta=task_output_meta)
|
||||
return f"data: {json.dumps(line)}\n\n"
|
||||
if isinstance(line, BaseModel):
|
||||
line = line.model_dump_json()
|
||||
line = f"data: {line}"
|
||||
if isinstance(line, dict):
|
||||
line = f"data: {json.dumps(line)}"
|
||||
|
||||
try:
|
||||
line = line.decode("utf-8")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if line.startswith("data:"):
|
||||
return f"{line}\n\n"
|
||||
else:
|
||||
line = openai_chat_chunk_message_template(model, line)
|
||||
return f"data: {json.dumps(line)}\n\n"
|
||||
|
||||
if form_data.stream:
|
||||
async def stream_content():
|
||||
async def execute_pipe(_pipe):
|
||||
if inspect.iscoroutinefunction(_pipe):
|
||||
return await _pipe(user_message=user_message,
|
||||
model_id=pipeline_id,
|
||||
messages=messages,
|
||||
body=form_data.model_dump())
|
||||
else:
|
||||
return _pipe(user_message=user_message,
|
||||
model_id=pipeline_id,
|
||||
messages=messages,
|
||||
body=form_data.model_dump())
|
||||
|
||||
try:
|
||||
res = await execute_pipe(pipe)
|
||||
|
||||
# Directly return if the response is a StreamingResponse
|
||||
if isinstance(res, StreamingResponse):
|
||||
async for data in res.body_iterator:
|
||||
yield data
|
||||
return
|
||||
if isinstance(res, dict):
|
||||
yield f"data: {json.dumps(res)}\n\n"
|
||||
return
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Error: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
yield f"data: {json.dumps({'error': {'detail': str(e)}})}\n\n"
|
||||
return
|
||||
|
||||
if isinstance(res, str):
|
||||
message = openai_chat_chunk_message_template(form_data.model, res)
|
||||
yield f"data: {json.dumps(message)}\n\n"
|
||||
|
||||
if isinstance(res, Iterator):
|
||||
for line in res:
|
||||
yield process_line(form_data.model, line)
|
||||
|
||||
if isinstance(res, AsyncGenerator):
|
||||
async for line in res:
|
||||
yield process_line(form_data.model, line)
|
||||
logging.info(f"AsyncGenerator end...")
|
||||
|
||||
if isinstance(res, str) or isinstance(res, Generator) or isinstance(res, AsyncGenerator):
|
||||
finish_message = openai_chat_chunk_message_template(
|
||||
form_data.model, ""
|
||||
)
|
||||
finish_message["choices"][0]["finish_reason"] = "stop"
|
||||
print(f"Pipe-Dataline:::: DONE")
|
||||
yield f"data: {json.dumps(finish_message)}\n\n"
|
||||
yield "data: [DONE]"
|
||||
|
||||
return StreamingResponse(stream_content(), media_type="text/event-stream")
|
||||
else:
|
||||
res = pipe(
|
||||
user_message=user_message,
|
||||
model_id=pipeline_id,
|
||||
messages=messages,
|
||||
body=form_data.model_dump(),
|
||||
)
|
||||
logging.info(f"stream:false:{res}")
|
||||
|
||||
if isinstance(res, dict):
|
||||
return res
|
||||
elif isinstance(res, BaseModel):
|
||||
return res.model_dump()
|
||||
else:
|
||||
|
||||
message = ""
|
||||
|
||||
if isinstance(res, str):
|
||||
message = res
|
||||
|
||||
if isinstance(res, Generator):
|
||||
for stream in res:
|
||||
message = f"{message}{stream}"
|
||||
|
||||
logging.info(f"stream:false:{message}")
|
||||
return {
|
||||
"id": f"{form_data.model}-{str(uuid.uuid4())}",
|
||||
"object": "chat.completion",
|
||||
"created": int(time.time()),
|
||||
"model": form_data.model,
|
||||
"choices": [
|
||||
{
|
||||
"index": 0,
|
||||
"message": {
|
||||
"role": "assistant",
|
||||
"content": message,
|
||||
},
|
||||
"logprobs": None,
|
||||
"finish_reason": "stop",
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
return await run_in_threadpool(job)
|
||||
|
||||
|
||||
async def call_pipeline(form_data: OpenAIChatCompletionForm):
|
||||
messages = [message.model_dump() for message in form_data.messages]
|
||||
user_message = get_last_user_message(messages)
|
||||
PIPELINES = await AGENT_SPACE.get_agents_meta()
|
||||
PIPELINE_MODULES = await AGENT_SPACE.get_agent_modules()
|
||||
if (
|
||||
form_data.model not in PIPELINES
|
||||
or PIPELINES[form_data.model]["type"] == "filter"
|
||||
):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"Pipeline {form_data.model} not found",
|
||||
)
|
||||
|
||||
pipeline = PIPELINES[form_data.model]
|
||||
pipeline_id = form_data.model
|
||||
|
||||
if pipeline["type"] == "manifold":
|
||||
manifold_id, pipeline_id = pipeline_id.split(".", 1)
|
||||
pipe = PIPELINE_MODULES[manifold_id].pipe
|
||||
else:
|
||||
pipe = PIPELINE_MODULES[pipeline_id].pipe
|
||||
|
||||
if form_data.stream:
|
||||
async def execute_pipe(_pipe):
|
||||
if inspect.iscoroutinefunction(_pipe):
|
||||
return await _pipe(user_message=user_message,
|
||||
model_id=pipeline_id,
|
||||
messages=messages,
|
||||
body=form_data.model_dump())
|
||||
else:
|
||||
return _pipe(user_message=user_message,
|
||||
model_id=pipeline_id,
|
||||
messages=messages,
|
||||
body=form_data.model_dump())
|
||||
|
||||
res = await execute_pipe(pipe)
|
||||
return res
|
||||
else:
|
||||
if not inspect.iscoroutinefunction(pipe):
|
||||
return await run_in_threadpool(
|
||||
pipe,
|
||||
user_message=user_message,
|
||||
model_id=pipeline_id,
|
||||
messages=messages,
|
||||
body=form_data.model_dump()
|
||||
)
|
||||
else:
|
||||
return await pipe(
|
||||
user_message=user_message,
|
||||
model_id=pipeline_id,
|
||||
messages=messages,
|
||||
body=form_data.model_dump()
|
||||
)
|
||||
|
||||
def openai_chat_chunk_message_template(
|
||||
model: str,
|
||||
content: Optional[str] = None,
|
||||
tool_calls: Optional[list[dict]] = None,
|
||||
usage: Optional[dict] = None,
|
||||
**kwargs
|
||||
) -> dict:
|
||||
template = openai_chat_message_template(model, **kwargs)
|
||||
template["object"] = "chat.completion.chunk"
|
||||
|
||||
template["choices"][0]["index"] = 0
|
||||
template["choices"][0]["delta"] = {}
|
||||
|
||||
if content:
|
||||
template["choices"][0]["delta"]["content"] = content
|
||||
|
||||
if tool_calls:
|
||||
template["choices"][0]["delta"]["tool_calls"] = tool_calls
|
||||
|
||||
if not content and not tool_calls:
|
||||
template["choices"][0]["finish_reason"] = "stop"
|
||||
|
||||
if usage:
|
||||
template["usage"] = usage
|
||||
return template
|
||||
|
||||
def openai_chat_message_template(model: str, **kwargs):
|
||||
return {
|
||||
"id": f"{model}-{str(uuid.uuid4())}",
|
||||
"created": int(time.time()),
|
||||
"model": model,
|
||||
"node_id": get_local_ip(),
|
||||
"task_output_meta": kwargs.get("task_output_meta"),
|
||||
"choices": [{"index": 0, "logprobs": None, "finish_reason": None}],
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
import importlib.util
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import traceback
|
||||
from aworldspace.base import AGENT_SPACE
|
||||
import aworld.trace as trace # noqa
|
||||
|
||||
from config import AGENTS_DIR
|
||||
|
||||
if not os.path.exists(AGENTS_DIR):
|
||||
os.makedirs(AGENTS_DIR)
|
||||
|
||||
PIPELINES = {}
|
||||
PIPELINE_MODULES = {}
|
||||
|
||||
def get_all_pipelines():
|
||||
pipelines = {}
|
||||
for pipeline_id in PIPELINE_MODULES.keys():
|
||||
pipeline = PIPELINE_MODULES[pipeline_id]
|
||||
|
||||
if hasattr(pipeline, "type"):
|
||||
if pipeline.type == "manifold":
|
||||
manifold_pipelines = []
|
||||
|
||||
# Check if pipelines is a function or a list
|
||||
if callable(pipeline.pipelines):
|
||||
manifold_pipelines = pipeline.pipelines()
|
||||
else:
|
||||
manifold_pipelines = pipeline.pipelines
|
||||
|
||||
for p in manifold_pipelines:
|
||||
manifold_pipeline_id = f'{pipeline_id}.{p["id"]}'
|
||||
|
||||
manifold_pipeline_name = p["name"]
|
||||
if hasattr(pipeline, "name"):
|
||||
manifold_pipeline_name = (
|
||||
f"{pipeline.name}{manifold_pipeline_name}"
|
||||
)
|
||||
|
||||
pipelines[manifold_pipeline_id] = {
|
||||
"module": pipeline_id,
|
||||
"type": pipeline.type if hasattr(pipeline, "type") else "pipe",
|
||||
"id": manifold_pipeline_id,
|
||||
"name": manifold_pipeline_name,
|
||||
"valves": (
|
||||
pipeline.valves if hasattr(pipeline, "valves") else None
|
||||
),
|
||||
}
|
||||
if pipeline.type == "filter":
|
||||
pipelines[pipeline_id] = {
|
||||
"module": pipeline_id,
|
||||
"type": (pipeline.type if hasattr(pipeline, "type") else "pipe"),
|
||||
"id": pipeline_id,
|
||||
"name": (
|
||||
pipeline.name if hasattr(pipeline, "name") else pipeline_id
|
||||
),
|
||||
"pipelines": (
|
||||
pipeline.valves.pipelines
|
||||
if hasattr(pipeline, "valves")
|
||||
and hasattr(pipeline.valves, "pipelines")
|
||||
else []
|
||||
),
|
||||
"priority": (
|
||||
pipeline.valves.priority
|
||||
if hasattr(pipeline, "valves")
|
||||
and hasattr(pipeline.valves, "priority")
|
||||
else 0
|
||||
),
|
||||
"valves": pipeline.valves if hasattr(pipeline, "valves") else None,
|
||||
}
|
||||
else:
|
||||
pipelines[pipeline_id] = {
|
||||
"module": pipeline_id,
|
||||
"type": (pipeline.type if hasattr(pipeline, "type") else "pipe"),
|
||||
"id": pipeline_id,
|
||||
"name": (pipeline.name if hasattr(pipeline, "name") else pipeline_id),
|
||||
"valves": pipeline.valves if hasattr(pipeline, "valves") else None,
|
||||
}
|
||||
|
||||
return pipelines
|
||||
|
||||
|
||||
def parse_frontmatter(content):
|
||||
frontmatter = {}
|
||||
for line in content.split("\n"):
|
||||
if ":" in line:
|
||||
key, value = line.split(":", 1)
|
||||
frontmatter[key.strip().lower()] = value.strip()
|
||||
return frontmatter
|
||||
|
||||
|
||||
def install_frontmatter_requirements(requirements):
|
||||
if requirements:
|
||||
req_list = [req.strip() for req in requirements.split(",")]
|
||||
for req in req_list:
|
||||
print(f"Installing requirement: {req}")
|
||||
subprocess.check_call([sys.executable, "-m", "pip", "install", req])
|
||||
else:
|
||||
print("No requirements found in frontmatter.")
|
||||
|
||||
|
||||
async def load_module_from_path(module_name, module_path):
|
||||
|
||||
try:
|
||||
# Read the module content
|
||||
with open(module_path, "r") as file:
|
||||
content = file.read()
|
||||
|
||||
# Parse frontmatter
|
||||
frontmatter = {}
|
||||
if content.startswith('"""'):
|
||||
end = content.find('"""', 3)
|
||||
if end != -1:
|
||||
frontmatter_content = content[3:end]
|
||||
frontmatter = parse_frontmatter(frontmatter_content)
|
||||
|
||||
# Install requirements if specified
|
||||
if "requirements" in frontmatter:
|
||||
install_frontmatter_requirements(frontmatter["requirements"])
|
||||
|
||||
# Load the module
|
||||
spec = importlib.util.spec_from_file_location(module_name, module_path)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
logging.info(f"Loaded module start: {module.__name__}")
|
||||
if hasattr(module, "Pipeline"):
|
||||
return module.Pipeline()
|
||||
else:
|
||||
logging.info(f"Loaded module failed: {module.__name__ } No Pipeline class found")
|
||||
raise Exception("No Pipeline class found")
|
||||
except Exception as e:
|
||||
logging.info(f"Error loading module: {module_name}, error is {e}")
|
||||
traceback.print_exc()
|
||||
# Move the file to the error folder
|
||||
failed_pipelines_folder = os.path.join(AGENTS_DIR, "failed")
|
||||
if not os.path.exists(failed_pipelines_folder):
|
||||
os.makedirs(failed_pipelines_folder)
|
||||
|
||||
# failed_file_path = os.path.join(failed_pipelines_folder, f"{module_name}.py")
|
||||
# if module_path.__contains__(PIPELINES_DIR):
|
||||
# os.rename(module_path, failed_file_path)
|
||||
print(e)
|
||||
return None
|
||||
|
||||
|
||||
async def load_modules_from_directory(directory):
|
||||
logging.info(f"load_modules_from_directory: {directory}")
|
||||
global PIPELINE_MODULES
|
||||
|
||||
for filename in os.listdir(directory):
|
||||
if filename.endswith(".py"):
|
||||
module_name = filename[:-3] # Remove the .py extension
|
||||
module_path = os.path.join(directory, filename)
|
||||
|
||||
# Create subfolder matching the filename without the .py extension
|
||||
subfolder_path = os.path.join(directory, module_name)
|
||||
if not os.path.exists(subfolder_path):
|
||||
os.makedirs(subfolder_path)
|
||||
logging.info(f"Created subfolder: {subfolder_path}")
|
||||
|
||||
# Create a valves.json file if it doesn't exist
|
||||
valves_json_path = os.path.join(subfolder_path, "valves.json")
|
||||
if not os.path.exists(valves_json_path):
|
||||
with open(valves_json_path, "w") as f:
|
||||
json.dump({}, f)
|
||||
logging.info(f"Created valves.json in: {subfolder_path}")
|
||||
|
||||
pipeline = await load_module_from_path(module_name, module_path)
|
||||
if pipeline:
|
||||
# Overwrite pipeline.valves with values from valves.json
|
||||
if os.path.exists(valves_json_path):
|
||||
with open(valves_json_path, "r") as f:
|
||||
valves_json = json.load(f)
|
||||
if hasattr(pipeline, "valves"):
|
||||
ValvesModel = pipeline.valves.__class__
|
||||
# Create a ValvesModel instance using default values and overwrite with valves_json
|
||||
combined_valves = {
|
||||
**pipeline.valves.model_dump(),
|
||||
**valves_json,
|
||||
}
|
||||
valves = ValvesModel(**combined_valves)
|
||||
pipeline.valves = valves
|
||||
|
||||
logging.info(f"Updated valves for module: {module_name}")
|
||||
|
||||
pipeline_id = pipeline.id if hasattr(pipeline, "id") else module_name
|
||||
PIPELINE_MODULES[pipeline_id] = pipeline
|
||||
|
||||
logging.info(f"Loaded module success: {module_name}")
|
||||
else:
|
||||
logging.warning(f"No Pipeline class found in {module_name}")
|
||||
|
||||
AGENT_SPACE.agent_modules = PIPELINE_MODULES
|
||||
AGENT_SPACE.agents_meta = get_all_pipelines()
|
||||
@@ -0,0 +1,75 @@
|
||||
import logging
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
from aworld.models.model_response import ModelResponse
|
||||
|
||||
from base import AworldTask, AworldTaskResult
|
||||
from config import ROOT_LOG
|
||||
|
||||
|
||||
class TaskLogger:
|
||||
"""任务提交日志记录器"""
|
||||
|
||||
def __init__(self, log_file: str = "aworld_task_submissions.log"):
|
||||
self.log_file = os.path.join(ROOT_LOG, 'task_logs' , log_file)
|
||||
self._ensure_log_file_exists()
|
||||
|
||||
def _ensure_log_file_exists(self):
|
||||
"""确保日志文件存在"""
|
||||
if not os.path.exists(self.log_file):
|
||||
os.makedirs(os.path.dirname(self.log_file), exist_ok=True)
|
||||
with open(self.log_file, 'w', encoding='utf-8') as f:
|
||||
f.write("# Aworld Task Submission Log\n")
|
||||
f.write(
|
||||
"# Format: [timestamp] task_id | agent_id | server | status | agent_answer | correct_answer | is_correct | details\n\n")
|
||||
|
||||
def log_task_submission(self, task: AworldTask, status: str, details: str = "",
|
||||
task_result: AworldTaskResult = None):
|
||||
"""记录任务提交日志"""
|
||||
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
log_entry = f"[{timestamp}] {task.task_id} | {task.agent_id} | {task.node_id} | {status} | {task_result.data.get('agent_answer') if task_result and task_result.data else None} | {task_result.data.get('correct_answer') if task_result and task_result.data else None} | {task_result.data.get('gaia_correct') if task_result and task_result.data else None} |{details}\n"
|
||||
|
||||
try:
|
||||
with open(self.log_file, 'a', encoding='utf-8') as f:
|
||||
f.write(log_entry)
|
||||
except Exception as e:
|
||||
logging.error(f"Failed to write task submission log: {e}")
|
||||
|
||||
def log_task_result(self, task: AworldTask, result: ModelResponse):
|
||||
try:
|
||||
date_str = datetime.now().strftime("%Y%m%d")
|
||||
result_dir = os.path.join(ROOT_LOG, 'task_logs', 'result', date_str)
|
||||
os.makedirs(result_dir, exist_ok=True)
|
||||
|
||||
md_file = f"{result_dir}/{task.task_id}.md"
|
||||
|
||||
content_parts = []
|
||||
if hasattr(result, 'content') and result.content:
|
||||
if isinstance(result.content, list):
|
||||
content_parts.extend(result.content)
|
||||
else:
|
||||
content_parts.append(str(result.content))
|
||||
|
||||
file_exists = os.path.exists(md_file)
|
||||
with open(md_file, 'a', encoding='utf-8') as f:
|
||||
if not file_exists:
|
||||
f.write(f"# Task Result: {task.task_id}\n\n")
|
||||
f.write(f"**Agent ID:** {task.agent_id}\n\n")
|
||||
f.write(f"**Timestamp:** {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n")
|
||||
f.write("## Content\n\n")
|
||||
|
||||
if content_parts:
|
||||
for i, content in enumerate(content_parts, 1):
|
||||
f.write(f"{content}\n\n")
|
||||
else:
|
||||
f.write("No content available.\n\n")
|
||||
|
||||
return md_file
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Failed to write task result log: {e}")
|
||||
return None
|
||||
|
||||
|
||||
task_logger = TaskLogger(log_file=f"aworld_task_submissions_{datetime.now().strftime('%Y%m%d')}.log")
|
||||
@@ -0,0 +1,199 @@
|
||||
import os
|
||||
|
||||
|
||||
def load_all_mcp_config():
|
||||
return {
|
||||
"mcpServers": {
|
||||
"e2b-server": {
|
||||
"command": "npx",
|
||||
"args": [
|
||||
"-y",
|
||||
"@e2b/mcp-server"
|
||||
],
|
||||
"env": {
|
||||
"E2B_API_KEY": os.environ["E2B_API_KEY"],
|
||||
"SESSION_REQUEST_CONNECT_TIMEOUT": "60"
|
||||
}
|
||||
},
|
||||
"filesystem": {
|
||||
"command": "npx",
|
||||
"args": [
|
||||
"-y",
|
||||
"@modelcontextprotocol/server-filesystem",
|
||||
"${FILESYSTEM_SERVER_WORKDIR}"
|
||||
]
|
||||
},
|
||||
"terminal-controller": {
|
||||
"command": "python",
|
||||
"args": [
|
||||
"-m",
|
||||
"terminal_controller"
|
||||
],
|
||||
"env": {
|
||||
"SESSION_REQUEST_CONNECT_TIMEOUT": "300"
|
||||
}
|
||||
},
|
||||
"calculator": {
|
||||
"command": "python",
|
||||
"args": [
|
||||
"-m",
|
||||
"mcp_server_calculator"
|
||||
],
|
||||
"env": {
|
||||
"SESSION_REQUEST_CONNECT_TIMEOUT": "20"
|
||||
}
|
||||
},
|
||||
"excel": {
|
||||
"command": "uvx",
|
||||
"args": ["excel-mcp-server", "stdio"],
|
||||
"env": {
|
||||
"EXCEL_MCP_PAGING_CELLS_LIMIT": "4000",
|
||||
"SESSION_REQUEST_CONNECT_TIMEOUT": "120"
|
||||
}
|
||||
},
|
||||
"google-search": {
|
||||
"command": "npx",
|
||||
"args": [
|
||||
"-y",
|
||||
"@adenot/mcp-google-search"
|
||||
],
|
||||
"env": {
|
||||
"GOOGLE_API_KEY": os.environ["GOOGLE_API_KEY"],
|
||||
"GOOGLE_SEARCH_ENGINE_ID": os.environ["GOOGLE_CSE_ID"],
|
||||
"SESSION_REQUEST_CONNECT_TIMEOUT": "60"
|
||||
}
|
||||
},
|
||||
"ms-playwright": {
|
||||
"command": "npx",
|
||||
"args": [
|
||||
"@playwright/mcp@latest",
|
||||
"--no-sandbox",
|
||||
"--headless",
|
||||
"--isolated"
|
||||
],
|
||||
"env": {
|
||||
"PLAYWRIGHT_TIMEOUT": "120000",
|
||||
"SESSION_REQUEST_CONNECT_TIMEOUT": "120"
|
||||
}
|
||||
},
|
||||
"audio_server": {
|
||||
"command": "python",
|
||||
"args": [
|
||||
"-m",
|
||||
"mcp_servers.audio_server"
|
||||
],
|
||||
"env": {
|
||||
"AUDIO_LLM_API_KEY": os.environ["AUDIO_LLM_API_KEY"],
|
||||
"AUDIO_LLM_BASE_URL": os.environ["AUDIO_LLM_BASE_URL"],
|
||||
"AUDIO_LLM_MODEL_NAME": os.environ["AUDIO_LLM_MODEL_NAME"],
|
||||
"SESSION_REQUEST_CONNECT_TIMEOUT": "60"
|
||||
}
|
||||
},
|
||||
"image_server": {
|
||||
"command": "python",
|
||||
"args": [
|
||||
"-m",
|
||||
"mcp_servers.image_server"
|
||||
],
|
||||
"env": {
|
||||
"LLM_API_KEY": os.environ.get("LLM_API_KEY"),
|
||||
"LLM_MODEL_NAME": os.environ.get("LLM_MODEL_NAME"),
|
||||
"LLM_BASE_URL": os.environ.get("LLM_BASE_URL"),
|
||||
"SESSION_REQUEST_CONNECT_TIMEOUT": "60"
|
||||
}
|
||||
},
|
||||
"youtube_server": {
|
||||
"command": "python",
|
||||
"args": [
|
||||
"-m",
|
||||
"mcp_servers.youtube_server"
|
||||
],
|
||||
"env": {
|
||||
"CHROME_DRIVER_PATH": os.environ['CHROME_DRIVER_PATH'],
|
||||
"SESSION_REQUEST_CONNECT_TIMEOUT": "120"
|
||||
}
|
||||
},
|
||||
"video_server": {
|
||||
"command": "python",
|
||||
"args": [
|
||||
"-m",
|
||||
"mcp_servers.video_server"
|
||||
],
|
||||
"env": {
|
||||
"LLM_API_KEY": os.environ.get("LLM_API_KEY"),
|
||||
"LLM_MODEL_NAME": os.environ.get("LLM_MODEL_NAME"),
|
||||
"LLM_BASE_URL": os.environ.get("LLM_BASE_URL"),
|
||||
"SESSION_REQUEST_CONNECT_TIMEOUT": "60"
|
||||
}
|
||||
},
|
||||
"search_server": {
|
||||
"command": "python",
|
||||
"args": [
|
||||
"-m",
|
||||
"mcp_servers.search_server"
|
||||
],
|
||||
"env": {
|
||||
"GOOGLE_API_KEY": os.environ["GOOGLE_API_KEY"],
|
||||
"GOOGLE_CSE_ID": os.environ["GOOGLE_CSE_ID"],
|
||||
"SESSION_REQUEST_CONNECT_TIMEOUT": "60"
|
||||
}
|
||||
},
|
||||
"download_server": {
|
||||
"command": "python",
|
||||
"args": [
|
||||
"-m",
|
||||
"mcp_servers.download_server"
|
||||
],
|
||||
"env": {
|
||||
"SESSION_REQUEST_CONNECT_TIMEOUT": "120"
|
||||
}
|
||||
},
|
||||
"document_server": {
|
||||
"command": "python",
|
||||
"args": [
|
||||
"-m",
|
||||
"mcp_servers.document_server"
|
||||
],
|
||||
"env": {
|
||||
"SESSION_REQUEST_CONNECT_TIMEOUT": "120"
|
||||
}
|
||||
},
|
||||
"browser_server": {
|
||||
"command": "python",
|
||||
"args": [
|
||||
"-m",
|
||||
"mcp_servers.browser_server"
|
||||
],
|
||||
"env": {
|
||||
"LLM_API_KEY": os.environ.get("LLM_API_KEY"),
|
||||
"LLM_MODEL_NAME": os.environ.get("LLM_MODEL_NAME"),
|
||||
"LLM_BASE_URL": os.environ.get("LLM_BASE_URL"),
|
||||
"SESSION_REQUEST_CONNECT_TIMEOUT": "120"
|
||||
}
|
||||
},
|
||||
"reasoning_server": {
|
||||
"command": "python",
|
||||
"args": [
|
||||
"-m",
|
||||
"mcp_servers.reasoning_server"
|
||||
],
|
||||
"env": {
|
||||
"LLM_API_KEY": os.environ.get("LLM_API_KEY"),
|
||||
"LLM_MODEL_NAME": os.environ.get("LLM_MODEL_NAME"),
|
||||
"LLM_BASE_URL": os.environ.get("LLM_BASE_URL"),
|
||||
"SESSION_REQUEST_CONNECT_TIMEOUT": "120"
|
||||
}
|
||||
},
|
||||
"e2b-code-server": {
|
||||
"command": "python",
|
||||
"args": [
|
||||
"-m",
|
||||
"mcp_servers.e2b_code_server"
|
||||
],
|
||||
"env": {
|
||||
"E2B_API_KEY": os.environ["E2B_API_KEY"],
|
||||
"SESSION_REQUEST_CONNECT_TIMEOUT": "120"
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,344 @@
|
||||
import json
|
||||
import re
|
||||
import string
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from loguru import logger
|
||||
from tabulate import tabulate
|
||||
|
||||
|
||||
def normalize_str(input_str, remove_punct=True) -> str:
|
||||
no_spaces = re.sub(r"\s", "", input_str)
|
||||
if remove_punct:
|
||||
translator = str.maketrans("", "", string.punctuation)
|
||||
return no_spaces.lower().translate(translator)
|
||||
else:
|
||||
return no_spaces.lower()
|
||||
|
||||
|
||||
def split_string(s: str, char_list: Optional[List[str]] = None) -> list[str]:
|
||||
if char_list is None:
|
||||
char_list = [",", ";"]
|
||||
pattern = f"[{''.join(char_list)}]"
|
||||
return re.split(pattern, s)
|
||||
|
||||
|
||||
def normalize_number_str(number_str: str) -> float:
|
||||
for char in ["$", "%", ","]:
|
||||
number_str = number_str.replace(char, "")
|
||||
try:
|
||||
return float(number_str)
|
||||
except ValueError:
|
||||
logger.error(f"String {number_str} cannot be normalized to number str.")
|
||||
return float("inf")
|
||||
|
||||
|
||||
def question_scorer(model_answer: str, ground_truth: str) -> bool:
|
||||
def is_float(element: Any) -> bool:
|
||||
try:
|
||||
float(element)
|
||||
return True
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
try:
|
||||
if is_float(ground_truth):
|
||||
logger.info(f"Evaluating {model_answer} as a number.")
|
||||
normalized_answer = normalize_number_str(model_answer)
|
||||
return normalized_answer == float(ground_truth)
|
||||
|
||||
elif any(char in ground_truth for char in [",", ";"]):
|
||||
logger.info(f"Evaluating {model_answer} as a comma separated list.")
|
||||
gt_elems = split_string(ground_truth)
|
||||
ma_elems = split_string(model_answer)
|
||||
|
||||
if len(gt_elems) != len(ma_elems):
|
||||
logger.warning("Answer lists have different lengths, returning False.")
|
||||
return False
|
||||
|
||||
comparisons = []
|
||||
for ma_elem, gt_elem in zip(ma_elems, gt_elems):
|
||||
if is_float(gt_elem):
|
||||
normalized_ma_elem = normalize_number_str(ma_elem)
|
||||
comparisons.append(normalized_ma_elem == float(gt_elem))
|
||||
else:
|
||||
ma_elem = normalize_str(ma_elem, remove_punct=False)
|
||||
gt_elem = normalize_str(gt_elem, remove_punct=False)
|
||||
comparisons.append(ma_elem == gt_elem)
|
||||
return all(comparisons)
|
||||
else:
|
||||
logger.info(f"Evaluating {model_answer} as a string.")
|
||||
ma_elem = normalize_str(model_answer)
|
||||
gt_elem = normalize_str(ground_truth)
|
||||
return ma_elem == gt_elem
|
||||
except Exception as e:
|
||||
logger.error(f"Error during evaluation: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def load_dataset_meta(path: str, split: str = "validation"):
|
||||
data_dir = Path(path) / split
|
||||
|
||||
dataset = []
|
||||
with open(data_dir / "metadata.jsonl", "r", encoding="utf-8") as metaf:
|
||||
lines = metaf.readlines()
|
||||
for line in lines:
|
||||
data = json.loads(line)
|
||||
if data["task_id"] == "0-0-0-0-0":
|
||||
continue
|
||||
if data["file_name"]:
|
||||
data["file_name"] = data_dir / data["file_name"]
|
||||
dataset.append(data)
|
||||
return dataset
|
||||
|
||||
|
||||
def load_dataset_meta_dict(path: str, split: str = "validation"):
|
||||
data_dir = Path(path) / split
|
||||
|
||||
dataset = {}
|
||||
with open(data_dir / "metadata.jsonl", "r", encoding="utf-8") as metaf:
|
||||
lines = metaf.readlines()
|
||||
for line in lines:
|
||||
data = json.loads(line)
|
||||
if data["task_id"] == "0-0-0-0-0":
|
||||
continue
|
||||
if data["file_name"]:
|
||||
data["file_name"] = data_dir / data["file_name"]
|
||||
dataset[data["task_id"]] = data
|
||||
return dataset
|
||||
|
||||
|
||||
def add_file_path(
|
||||
task: Dict[str, Any], file_path: str = "./gaia_dataset", split: str = "validation"
|
||||
):
|
||||
if task["file_name"]:
|
||||
file_path = Path(f"{file_path}/{split}") / task["file_name"]
|
||||
if file_path.suffix in [".pdf", ".docx", ".doc", ".txt"]:
|
||||
task["Question"] += f" Here are the necessary document files: {file_path}"
|
||||
|
||||
elif file_path.suffix in [".jpg", ".jpeg", ".png"]:
|
||||
task["Question"] += f" Here are the necessary image files: {file_path}"
|
||||
|
||||
elif file_path.suffix in [".xlsx", "xls", ".csv"]:
|
||||
task["Question"] += (
|
||||
f" Here are the necessary table files: {file_path}, for processing excel file,"
|
||||
" you can use the excel tool or write python code to process the file"
|
||||
" step-by-step and get the information."
|
||||
)
|
||||
elif file_path.suffix in [".py"]:
|
||||
task["Question"] += f" Here are the necessary python files: {file_path}"
|
||||
|
||||
else:
|
||||
task["Question"] += f" Here are the necessary files: {file_path}"
|
||||
|
||||
return task
|
||||
|
||||
|
||||
def report_results(entries):
|
||||
# Initialize counters
|
||||
total_entries = len(entries)
|
||||
total_correct = 0
|
||||
|
||||
# Initialize level statistics
|
||||
level_stats = {}
|
||||
|
||||
# Process each entry
|
||||
for entry in entries:
|
||||
level = entry.get("level")
|
||||
is_correct = entry.get("is_correct", False)
|
||||
|
||||
# Initialize level stats if not already present
|
||||
if level not in level_stats:
|
||||
level_stats[level] = {"total": 0, "correct": 0, "accuracy": 0}
|
||||
|
||||
# Update counters
|
||||
level_stats[level]["total"] += 1
|
||||
if is_correct:
|
||||
total_correct += 1
|
||||
level_stats[level]["correct"] += 1
|
||||
|
||||
# Calculate accuracy for each level
|
||||
for level, stats in level_stats.items():
|
||||
if stats["total"] > 0:
|
||||
stats["accuracy"] = (stats["correct"] / stats["total"]) * 100
|
||||
|
||||
# Print overall statistics with colorful logging
|
||||
logger.info("Overall Statistics:")
|
||||
overall_accuracy = (total_correct / total_entries) * 100
|
||||
|
||||
# Create overall statistics table
|
||||
overall_table = [
|
||||
["Total Entries", total_entries],
|
||||
["Total Correct", total_correct],
|
||||
["Overall Accuracy", f"{overall_accuracy:.2f}%"],
|
||||
]
|
||||
logger.success(tabulate(overall_table, tablefmt="grid"))
|
||||
logger.info("")
|
||||
|
||||
# Create level statistics table
|
||||
logger.info("Statistics by Level:")
|
||||
level_table = []
|
||||
headers = ["Level", "Total Entries", "Correct Answers", "Accuracy"]
|
||||
|
||||
for level in sorted(level_stats.keys()):
|
||||
stats = level_stats[level]
|
||||
level_table.append(
|
||||
[level, stats["total"], stats["correct"], f"{stats['accuracy']:.2f}%"]
|
||||
)
|
||||
|
||||
logger.success(tabulate(level_table, headers=headers, tablefmt="grid"))
|
||||
|
||||
|
||||
import uuid
|
||||
import time
|
||||
|
||||
from typing import List
|
||||
|
||||
import inspect
|
||||
from typing import get_type_hints, Tuple
|
||||
|
||||
|
||||
def stream_message_template(model: str, message: str):
|
||||
return {
|
||||
"id": f"{model}-{str(uuid.uuid4())}",
|
||||
"object": "chat.completion.chunk",
|
||||
"created": int(time.time()),
|
||||
"model": model,
|
||||
"choices": [
|
||||
{
|
||||
"index": 0,
|
||||
"delta": {"content": message},
|
||||
"logprobs": None,
|
||||
"finish_reason": None,
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def get_last_user_message(messages: List[dict]) -> str:
|
||||
for message in reversed(messages):
|
||||
if message["role"] == "user":
|
||||
if isinstance(message["content"], list):
|
||||
for item in message["content"]:
|
||||
if item["type"] == "text":
|
||||
return item["text"]
|
||||
return message["content"]
|
||||
return None
|
||||
|
||||
|
||||
def get_last_assistant_message(messages: List[dict]) -> str:
|
||||
for message in reversed(messages):
|
||||
if message["role"] == "assistant":
|
||||
if isinstance(message["content"], list):
|
||||
for item in message["content"]:
|
||||
if item["type"] == "text":
|
||||
return item["text"]
|
||||
return message["content"]
|
||||
return None
|
||||
|
||||
|
||||
def get_system_message(messages: List[dict]) -> dict:
|
||||
for message in messages:
|
||||
if message["role"] == "system":
|
||||
return message
|
||||
return None
|
||||
|
||||
|
||||
def remove_system_message(messages: List[dict]) -> List[dict]:
|
||||
return [message for message in messages if message["role"] != "system"]
|
||||
|
||||
|
||||
def pop_system_message(messages: List[dict]) -> Tuple[dict, List[dict]]:
|
||||
return get_system_message(messages), remove_system_message(messages)
|
||||
|
||||
|
||||
def add_or_update_system_message(content: str, messages: List[dict]) -> List[dict]:
|
||||
"""
|
||||
Adds a new system message at the beginning of the messages list
|
||||
or updates the existing system message at the beginning.
|
||||
|
||||
:param msg: The message to be added or appended.
|
||||
:param messages: The list of message dictionaries.
|
||||
:return: The updated list of message dictionaries.
|
||||
"""
|
||||
|
||||
if messages and messages[0].get("role") == "system":
|
||||
messages[0]["content"] += f"{content}\n{messages[0]['content']}"
|
||||
else:
|
||||
# Insert at the beginning
|
||||
messages.insert(0, {"role": "system", "content": content})
|
||||
|
||||
return messages
|
||||
|
||||
|
||||
def doc_to_dict(docstring):
|
||||
lines = docstring.split("\n")
|
||||
description = lines[1].strip()
|
||||
param_dict = {}
|
||||
|
||||
for line in lines:
|
||||
if ":param" in line:
|
||||
line = line.replace(":param", "").strip()
|
||||
param, desc = line.split(":", 1)
|
||||
param_dict[param.strip()] = desc.strip()
|
||||
ret_dict = {"description": description, "params": param_dict}
|
||||
return ret_dict
|
||||
|
||||
|
||||
def get_tools_specs(tools) -> List[dict]:
|
||||
function_list = [
|
||||
{"name": func, "function": getattr(tools, func)}
|
||||
for func in dir(tools)
|
||||
if callable(getattr(tools, func)) and not func.startswith("__")
|
||||
]
|
||||
|
||||
specs = []
|
||||
|
||||
for function_item in function_list:
|
||||
function_name = function_item["name"]
|
||||
function = function_item["function"]
|
||||
|
||||
function_doc = doc_to_dict(function.__doc__ or function_name)
|
||||
specs.append(
|
||||
{
|
||||
"name": function_name,
|
||||
# TODO: multi-line desc?
|
||||
"description": function_doc.get("description", function_name),
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
param_name: {
|
||||
"type": param_annotation.__name__.lower(),
|
||||
**(
|
||||
{
|
||||
"enum": (
|
||||
param_annotation.__args__
|
||||
if hasattr(param_annotation, "__args__")
|
||||
else None
|
||||
)
|
||||
}
|
||||
if hasattr(param_annotation, "__args__")
|
||||
else {}
|
||||
),
|
||||
"description": function_doc.get("params", {}).get(
|
||||
param_name, param_name
|
||||
),
|
||||
}
|
||||
for param_name, param_annotation in get_type_hints(
|
||||
function
|
||||
).items()
|
||||
if param_name != "return"
|
||||
},
|
||||
"required": [
|
||||
name
|
||||
for name, param in inspect.signature(
|
||||
function
|
||||
).parameters.items()
|
||||
if param.default is param.empty
|
||||
],
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
return specs
|
||||
Reference in New Issue
Block a user