Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Agentica #14

Merged
merged 9 commits into from
Jul 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
# model type can be "openai" or "azure"
MODEL_TYPE="openai"

# Agent type can be "function_call" or "react"
AGENT_TYPE="function_call"

# framework for agentica, can be "agentica" or "langchain"
FRAMEWORK="agentica"

# for openai LLM api, can be multiple keys, split by comma(,)
OPENAI_API_KEYS="sk-xxx1,sk-xxx2"
OPENAI_API_BASE_URLS="https://api.openai.com/v1,https://api.openai.com/v1"

# for azure openai api
OPENAI_API_VERSION=""
AZURE_OPENAI_API_KEY=
AZURE_OPENAI_API_VERSION=
AZURE_OPENAI_ENDPOINT=

# for ollama
OLLAMA_BASE_URL='http://localhost:11434'
Expand Down
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ __pycache__/

# Distribution / packaging
.Python
build/
# build/
develop-eggs/
dist/
downloads/
Expand Down Expand Up @@ -184,6 +184,6 @@ cython_debug/
.pnp.*
web/.svelte-kit/
web/.svelte-kit
web/build
# web/build
web/node_modules
web/.yarn
2 changes: 1 addition & 1 deletion chatpilot/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
@author:XuMing(xuming624@qq.com)
@description:
"""
from chatpilot.chat_agent import ChatAgent
from chatpilot.langchain_assistant import LangchainAssistant
from chatpilot.config import (
OPENAI_API_KEY,
OPENAI_API_BASE,
Expand Down
106 changes: 106 additions & 0 deletions chatpilot/agentica_assistant.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# -*- coding: utf-8 -*-
"""
@author:XuMing(xuming624@qq.com)
@description:
"""
from typing import Optional

from agentica import PythonAssistant, AzureOpenAILLM, OpenAILLM, Assistant
from agentica.tools.search_serper import SearchSerperTool
from agentica.tools.url_crawler import UrlCrawlerTool

from chatpilot.config import (
ENABLE_SEARCH_TOOL,
ENABLE_URL_CRAWLER_TOOL,
ENABLE_RUN_PYTHON_CODE_TOOL,
)


class AgenticaAssistant:
def __init__(
self,
model_type: str = "openai",
model_name: str = "gpt-3.5-turbo-1106",
enable_search_tool: Optional[bool] = None,
enable_url_crawler_tool: Optional[bool] = None,
enable_run_python_code_tool: Optional[bool] = None,
verbose: bool = True,
**kwargs
):
"""
Initializes the ChatAgent with the given parameters.

:param model_type: The type of the model, such as "openai" / "azure".
:param model_name: The model name of OpenAI.
:param model_api_key: The API keys for the OpenAI API.
:param model_api_base: The base URLs for the OpenAI API.
:param search_name: The name of the search engine to use, such as "serper" or "duckduckgo".
:param agent_type: The type of the agent, such as "react" or "tools".
:param enable_search_tool: If True, enables the search tool.
:param enable_url_crawler_tool: If True, enables the web URL crawler tool.
:param enable_run_python_code_tool: If True, enables the run Python code tool.
:param verbose: If True, enables verbose logging.
:param kwargs: Additional keyword arguments.
"""
if model_type == 'azure':
self.llm = AzureOpenAILLM(
model=model_name,
**kwargs
)
elif model_type == 'openai':
self.llm = OpenAILLM(
model=model_name,
**kwargs
)
else:
raise ValueError(f"Unsupported model type: {model_type}")
self.model_type = model_type
self.model_name = model_name
self.verbose = verbose

# Define tools
enable_search_tool = enable_search_tool if enable_search_tool is not None else ENABLE_SEARCH_TOOL
enable_url_crawler_tool = (
enable_url_crawler_tool if enable_url_crawler_tool is not None else ENABLE_URL_CRAWLER_TOOL
)
enable_run_python_code_tool = (
enable_run_python_code_tool if enable_run_python_code_tool is not None else ENABLE_RUN_PYTHON_CODE_TOOL
)
self.tools = []
if enable_search_tool:
self.tools.append(SearchSerperTool())
if enable_url_crawler_tool:
self.tools.append(UrlCrawlerTool())
if enable_run_python_code_tool:
self.model = PythonAssistant(
llm=self.llm,
tools=self.tools,
description="你是一个有用的AI助手,请用中文回答问题",
add_datetime_to_instructions=True,
show_tool_calls=True,
read_chat_history=True,
debug_mode=True,
)
else:
self.model = Assistant(
llm=self.llm,
tools=self.tools,
description="你是一个有用的AI助手,请用中文回答问题",
add_datetime_to_instructions=True,
show_tool_calls=True,
read_chat_history=True,
debug_mode=True,
)

def __repr__(self):
return f"AgenticaAssistant(llm={self.llm}, tools={self.tools})"

def stream_run(self, input_str: str):
"""
runs the given input string through the ChatAgent and returns the result.

:param input_str: The input string to process.
:return: An asynchronous generator of events.
"""

return self.model.run(input_str, stream=True, print_output=False)
181 changes: 117 additions & 64 deletions chatpilot/apps/openai_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@
from loguru import logger
from pydantic import BaseModel

from chatpilot.agentica_assistant import AgenticaAssistant
from chatpilot.apps.auth_utils import (
get_current_user,
get_admin_user,
)
from chatpilot.chat_agent import ChatAgent
from chatpilot.config import (
OPENAI_API_BASE_URLS,
OPENAI_API_KEYS,
Expand All @@ -39,8 +39,10 @@
RPM,
MODEL_TYPE,
AGENT_TYPE,
FRAMEWORK,
)
from chatpilot.constants import ERROR_MESSAGES
from chatpilot.langchain_assistant import LangchainAssistant

app = FastAPI()
app.add_middleware(
Expand All @@ -64,8 +66,13 @@
else:
app.state.CLIENT_MANAGER = None

# Get all models
app.state.MODELS = {}

# Agent for Assistant
app.state.AGENT = None
app.state.MODEL_NAME = None

# User request tracking
user_request_tracker = defaultdict(lambda: {"daily": [], "minute": []})

Expand Down Expand Up @@ -170,9 +177,7 @@ async def speech(
if file_path.is_file():
return FileResponse(file_path)

headers = {}
headers["Authorization"] = f"Bearer {api_key}"
headers["Content-Type"] = "application/json"
headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}

try:
r = requests.post(
Expand Down Expand Up @@ -340,9 +345,7 @@ def proxy_other_request(api_key, base_url, path, body, method):

target_url = f"{base_url}/{path}"

headers = {}
headers["Authorization"] = f"Bearer {api_key}"
headers["Content-Type"] = "application/json"
headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}

r = requests.request(
method=method,
Expand Down Expand Up @@ -389,6 +392,8 @@ async def proxy(
logger.debug(f"Using API key: {show_api_key}, base URL: {base_url}")

model_name = body_dict.get('model', DEFAULT_MODELS[0] if DEFAULT_MODELS else "gpt-3.5-turbo")
if app.state.MODEL_NAME is None:
app.state.MODEL_NAME = model_name
max_tokens = body_dict.get("max_tokens", 1024)
temperature = body_dict.get("temperature", 0.7)
num_ctx = body_dict.get('num_ctx', 1024)
Expand All @@ -414,63 +419,111 @@ async def proxy(
if not isinstance(user_question, str):
return proxy_other_request(api_key, base_url, path, body, method)

# Create a new ChatAgent instance for each request
chat_agent = ChatAgent(
model_type=MODEL_TYPE,
model_name=model_name,
model_api_key=api_key,
model_api_base=base_url,
search_name="serper" if SERPER_API_KEY else "duckduckgo",
verbose=True,
temperature=temperature,
max_tokens=max_tokens,
max_context_tokens=num_ctx,
streaming=True,
max_iterations=2,
max_execution_time=60,
system_prompt=system_prompt,
agent_type=AGENT_TYPE,
)
logger.debug(chat_agent)
events = await chat_agent.astream_run(user_question, chat_history=history)
created = int(time.time())

async def event_generator():
"""组装为OpenAI格式流式输出"""
async for event in events:
kind = event['event']
if kind in ['on_tool_start', 'on_chat_model_stream']:
if kind == "on_tool_start":
c = str(event['data'].get('input', ''))
else:
c = event['data']['chunk'].content
if not c:
tool_call_chunks = event['data'].get("tool_call_chunks", [])
if tool_call_chunks:
c = tool_call_chunks[0].get("args", "")
if c:
data_structure = {
"id": event.get('id', 'default_id'),
"object": "chat.completion.chunk",
"created": event.get('created', created),
"model": model_name,
"system_fingerprint": event.get('system_fingerprint', ''),
"choices": [
{
"index": 0,
"delta": {"content": c},
"logprobs": None,
"finish_reason": None
}
]
}
formatted_data = f"data: {json.dumps(data_structure)}\n\n"
yield formatted_data.encode()

formatted_data_done = f"data: [DONE]\n\n"
yield formatted_data_done.encode()

return StreamingResponse(event_generator(), media_type='text/event-stream')
if FRAMEWORK == "langchain":
# Create a new ChatAgent instance for each request
chat_agent = LangchainAssistant(
model_type=MODEL_TYPE,
model_name=model_name,
model_api_key=api_key,
model_api_base=base_url,
search_name="serper" if SERPER_API_KEY else "duckduckgo",
verbose=True,
temperature=temperature,
max_tokens=max_tokens,
max_context_tokens=num_ctx,
streaming=True,
max_iterations=2,
max_execution_time=60,
system_prompt=system_prompt,
agent_type=AGENT_TYPE,
)
logger.debug(chat_agent)
events = await chat_agent.astream_run(user_question, chat_history=history)
created = int(time.time())

async def event_generator():
"""组装为OpenAI格式流式输出"""
async for event in events:
kind = event['event']
if kind in ['on_tool_start', 'on_chat_model_stream']:
if kind == "on_tool_start":
c = str(event['data'].get('input', ''))
else:
c = event['data']['chunk'].content
if not c:
tool_call_chunks = event['data'].get("tool_call_chunks", [])
if tool_call_chunks:
c = tool_call_chunks[0].get("args", "")
if c:
data_structure = {
"id": event.get('id', 'default_id'),
"object": "chat.completion.chunk",
"created": event.get('created', created),
"model": model_name,
"system_fingerprint": event.get('system_fingerprint', ''),
"choices": [
{
"index": 0,
"delta": {"content": c},
"logprobs": None,
"finish_reason": None
}
]
}
formatted_data = f"data: {json.dumps(data_structure)}\n\n"
yield formatted_data.encode()

formatted_data_done = f"data: [DONE]\n\n"
yield formatted_data_done.encode()

return StreamingResponse(event_generator(), media_type='text/event-stream')
elif FRAMEWORK == "agentica":
# Init Agent when first request
if app.state.AGENT is None:
chat_agent = AgenticaAssistant(model_type=MODEL_TYPE, model_name=model_name)
app.state.AGENT = chat_agent
logger.debug(chat_agent)
elif app.state.MODEL_NAME != model_name:
chat_agent = AgenticaAssistant(model_type=MODEL_TYPE, model_name=model_name)
app.state.AGENT = chat_agent
app.state.MODEL_NAME = model_name
logger.debug(chat_agent)
else:
if history:
chat_agent = app.state.AGENT
else:
chat_agent = AgenticaAssistant(model_type=MODEL_TYPE, model_name=model_name)
app.state.AGENT = chat_agent
events = chat_agent.stream_run(user_question)
created = int(time.time())

def event_generator():
"""组装为OpenAI格式流式输出"""
for event in events:
data_structure = {
"id": 'default_id',
"object": "chat.completion.chunk",
"created": created,
"model": model_name,
"system_fingerprint": '',
"choices": [
{
"index": 0,
"delta": {"content": event},
"logprobs": None,
"finish_reason": None
}
]
}
formatted_data = f"data: {json.dumps(data_structure)}\n\n"
yield formatted_data.encode()

formatted_data_done = f"data: [DONE]\n\n"
yield formatted_data_done.encode()

return StreamingResponse(event_generator(), media_type='text/event-stream')
else:
raise ValueError(f"Not support: {FRAMEWORK}")
except Exception as e:
logger.error(e)
error_detail = "Server Connection Error"
Expand Down
Loading