diff --git a/README.md b/README.md
index fb40a61..0e60418 100644
--- a/README.md
+++ b/README.md
@@ -15,13 +15,11 @@
https://github.com/dbpunk-labs/octogen/assets/8623385/7445cc4d-567e-4d1a-bedc-b5b566329c41
-
|Supported OSs|Supported Interpreters|Supported Dev Enviroment|
|----|-----|-----|
| | | |
-
## Getting Started
Requirement
@@ -100,7 +98,6 @@ Use /help for help
## Supported API Service
-
|name|type|status| installation|
|----|-----|----------------|---|
|[Openai GPT 3.5/4](https://openai.com/product#made-for-developers) |LLM| ✅ fully supported|use `og_up` then choose the `OpenAI`|
@@ -133,4 +130,3 @@ if you have any feature suggestion. please create a discuession to talk about it
* [roadmap for v0.5.0](https://github.com/dbpunk-labs/octogen/issues/64)
-
diff --git a/agent/src/og_agent/codellama_agent.py b/agent/src/og_agent/codellama_agent.py
index 2f28159..bd99a58 100644
--- a/agent/src/og_agent/codellama_agent.py
+++ b/agent/src/og_agent/codellama_agent.py
@@ -67,16 +67,18 @@ async def handle_show_sample_code(
"language": json_response.get("language", "text"),
})
await queue.put(
- TaskRespond(
- state=task_context.to_task_state_proto(),
- respond_type=TaskRespond.OnAgentActionType,
- on_agent_action=OnAgentAction(
+ TaskResponse(
+ state=task_context.to_context_state_proto(),
+ response_type=TaskResponse.OnStepActionStart,
+ on_step_agent_start=OnStepActionStart(
input=tool_input, tool="show_sample_code"
),
)
)
- async def handle_bash_code(self, json_response, queue, context, task_context):
+ async def handle_bash_code(
+ self, json_response, queue, context, task_context, task_opt
+ ):
commands = json_response["action_input"]
code = f"%%bash\n {commands}"
explanation = json_response["explanation"]
@@ -88,10 +90,10 @@ async def handle_bash_code(self, json_response, queue, context, task_context):
"language": json_response.get("language"),
})
await queue.put(
- TaskRespond(
- state=task_context.to_task_state_proto(),
- respond_type=TaskRespond.OnAgentActionType,
- on_agent_action=OnAgentAction(
+ TaskResponse(
+ state=task_context.to_context_state_proto(),
+ response_type=TaskResponse.OnStepActionStart,
+ on_step_action_start=OnStepActionStart(
input=tool_input, tool="execute_bash_code"
),
)
@@ -102,7 +104,7 @@ async def handle_bash_code(self, json_response, queue, context, task_context):
logger.debug("the client has cancelled the request")
break
function_result = result
- if respond:
+ if respond and task_opt.streaming:
await queue.put(respond)
return function_result
diff --git a/agent/src/og_agent/mock_agent.py b/agent/src/og_agent/mock_agent.py
index 5996573..96b4723 100644
--- a/agent/src/og_agent/mock_agent.py
+++ b/agent/src/og_agent/mock_agent.py
@@ -8,7 +8,7 @@
import time
import logging
from .base_agent import BaseAgent, TypingState, TaskContext
-from og_proto.agent_server_pb2 import OnStepActionStart, TaskResponse, OnStepActionEnd, FinalAnswer,TypingContent
+from og_proto.agent_server_pb2 import OnStepActionStart, TaskResponse, OnStepActionEnd, FinalAnswer, TypingContent
from .tokenizer import tokenize
logger = logging.getLogger(__name__)
@@ -33,7 +33,9 @@ async def call_ai(self, prompt, queue, iteration, task_context):
TaskResponse(
state=task_context.to_context_state_proto(),
response_type=TaskResponse.OnModelTypeText,
- typing_content=TypingContent(content = message["explanation"], language="text"),
+ typing_content=TypingContent(
+ content=message["explanation"], language="text"
+ ),
)
)
if message.get("code", None):
@@ -41,7 +43,9 @@ async def call_ai(self, prompt, queue, iteration, task_context):
TaskResponse(
state=task_context.to_context_state_proto(),
response_type=TaskResponse.OnModelTypeCode,
- typing_content=TypingContent(content = message["code"], language="python"),
+ typing_content=TypingContent(
+ content=message["code"], language="python"
+ ),
)
)
return message
diff --git a/agent/src/og_agent/openai_agent.py b/agent/src/og_agent/openai_agent.py
index cd93f72..fc506ff 100644
--- a/agent/src/og_agent/openai_agent.py
+++ b/agent/src/og_agent/openai_agent.py
@@ -139,6 +139,7 @@ async def call_openai(self, messages, queue, context, task_context, task_opt):
"""
call the openai api
"""
+ logger.debug(f"call openai with messages {messages}")
input_token_count = 0
for message in messages:
if not message["content"]:
@@ -212,10 +213,10 @@ async def call_openai(self, messages, queue, context, task_context, task_opt):
code_content = code_str
if task_opt.streaming and len(typed_chars) > 0:
typing_language = "text"
- if (
- delta["function_call"].get("name", "")
- == "execute_python_code"
- ):
+ if delta["function_call"].get("name", "") in [
+ "execute_python_code",
+ "python",
+ ]:
typing_language = "python"
elif (
delta["function_call"].get("name", "")
@@ -357,6 +358,8 @@ async def arun(self, task, queue, context, task_opt):
if "function_call" in chat_message:
if "content" not in chat_message:
chat_message["content"] = None
+ if "role" not in chat_message:
+ chat_message["role"] = "assistant"
messages.append(chat_message)
function_name = chat_message["function_call"]["name"]
if function_name not in [
diff --git a/agent/tests/codellama_agent_tests.py b/agent/tests/codellama_agent_tests.py
index f4d2a1e..ee73bc2 100644
--- a/agent/tests/codellama_agent_tests.py
+++ b/agent/tests/codellama_agent_tests.py
@@ -10,7 +10,8 @@
import json
import logging
import pytest
-from og_sdk.agent_sdk import AgentSDK
+
+from og_sdk.kernel_sdk import KernelSDK
from og_agent.codellama_agent import CodellamaAgent
from og_proto.agent_server_pb2 import ProcessOptions, TaskResponse
import asyncio
@@ -21,6 +22,14 @@
logger = logging.getLogger(__name__)
+@pytest.fixture
+def kernel_sdk():
+ endpoint = (
+ "localhost:9527" # Replace with the actual endpoint of your test gRPC server
+ )
+ return KernelSDK(endpoint, "ZCeI9cYtOCyLISoi488BgZHeBkHWuFUH")
+
+
class PayloadStream:
def __init__(self, payload):
@@ -49,24 +58,157 @@ def done(self):
class CodellamaMockClient:
- def __init__(self, payload):
- self.payload = payload
+ def __init__(self, payloads):
+ self.payloads = payloads
+ self.index = 0
async def prompt(self, question, chat_history=[]):
- async for line in PayloadStream(self.payload):
+ if self.index >= len(self.payloads):
+ raise StopAsyncIteration
+ self.index += 1
+ payload = self.payloads[self.index - 1]
+ async for line in PayloadStream(payload):
yield line
-@pytest_asyncio.fixture
-async def agent_sdk():
- sdk = AgentSDK(api_base, api_key)
- sdk.connect()
- yield sdk
- await sdk.close()
+@pytest.mark.asyncio
+async def test_codellama_agent_execute_bash_code(kernel_sdk):
+ kernel_sdk.connect()
+ sentence1 = {
+ "explanation": "print a hello world using python",
+ "action": "execute_bash_code",
+ "action_input": "echo 'hello world'",
+ "saved_filenames": [],
+ "language": "python",
+ "is_final_answer": False,
+ }
+ sentence2 = {
+ "explanation": "the output matchs the goal",
+ "action": "no_action",
+ "action_input": "",
+ "saved_filenames": [],
+ "language": "en",
+ "is_final_answer": False,
+ }
+ client = CodellamaMockClient([json.dumps(sentence1), json.dumps(sentence2)])
+ agent = CodellamaAgent(client, kernel_sdk)
+ task_opt = ProcessOptions(
+ streaming=True,
+ llm_name="codellama",
+ input_token_limit=100000,
+ output_token_limit=100000,
+ timeout=5,
+ )
+ queue = asyncio.Queue()
+ await agent.arun("write a hello world in bash", queue, MockContext(), task_opt)
+ responses = []
+ while True:
+ try:
+ response = await queue.get()
+ if not response:
+ break
+ responses.append(response)
+ except asyncio.QueueEmpty:
+ break
+ logger.info(responses)
+ console_output = list(
+ filter(
+ lambda x: x.response_type == TaskResponse.OnStepActionStreamStdout,
+ responses,
+ )
+ )
+ assert len(console_output) == 1, "bad console output count"
+ assert console_output[0].console_stdout == "hello world\n", "bad console output"
+
+
+@pytest.mark.asyncio
+async def test_codellama_agent_execute_python_code(kernel_sdk):
+ kernel_sdk.connect()
+ sentence1 = {
+ "explanation": "print a hello world using python",
+ "action": "execute_python_code",
+ "action_input": "print('hello world')",
+ "saved_filenames": [],
+ "language": "python",
+ "is_final_answer": False,
+ }
+ sentence2 = {
+ "explanation": "the output matchs the goal",
+ "action": "no_action",
+ "action_input": "",
+ "saved_filenames": [],
+ "language": "en",
+ "is_final_answer": False,
+ }
+ client = CodellamaMockClient([json.dumps(sentence1), json.dumps(sentence2)])
+ agent = CodellamaAgent(client, kernel_sdk)
+ task_opt = ProcessOptions(
+ streaming=True,
+ llm_name="codellama",
+ input_token_limit=100000,
+ output_token_limit=100000,
+ timeout=5,
+ )
+ queue = asyncio.Queue()
+ await agent.arun("write a hello world in python", queue, MockContext(), task_opt)
+ responses = []
+ while True:
+ try:
+ response = await queue.get()
+ if not response:
+ break
+ responses.append(response)
+ except asyncio.QueueEmpty:
+ break
+ logger.info(responses)
+ console_output = list(
+ filter(
+ lambda x: x.response_type == TaskResponse.OnStepActionStreamStdout,
+ responses,
+ )
+ )
+ assert len(console_output) == 1, "bad console output count"
+ assert console_output[0].console_stdout == "hello world\n", "bad console output"
+
+
+@pytest.mark.asyncio
+async def test_codellama_agent_show_demo_code(kernel_sdk):
+ sentence = {
+ "explanation": "Hello, how can I help you?",
+ "action": "show_demo_code",
+ "action_input": "echo 'hello world'",
+ "saved_filenames": [],
+ "language": "shell",
+ "is_final_answer": True,
+ }
+ client = CodellamaMockClient([json.dumps(sentence)])
+ agent = CodellamaAgent(client, kernel_sdk)
+ task_opt = ProcessOptions(
+ streaming=True,
+ llm_name="codellama",
+ input_token_limit=100000,
+ output_token_limit=100000,
+ timeout=5,
+ )
+ queue = asyncio.Queue()
+ await agent.arun("hello", queue, MockContext(), task_opt)
+ responses = []
+ while True:
+ try:
+ response = await queue.get()
+ if not response:
+ break
+ responses.append(response)
+ except asyncio.QueueEmpty:
+ break
+ logger.info(responses)
+ assert (
+ responses[-1].response_type == TaskResponse.OnFinalAnswer
+ ), "bad response type"
@pytest.mark.asyncio
-async def test_codellama_agent_smoke_test(agent_sdk):
+async def test_codellama_agent_smoke_test(kernel_sdk):
sentence = {
"explanation": "Hello, how can I help you?",
"action": "no_action",
@@ -75,8 +217,8 @@ async def test_codellama_agent_smoke_test(agent_sdk):
"language": "en",
"is_final_answer": True,
}
- client = CodellamaMockClient(json.dumps(sentence))
- agent = CodellamaAgent(client, agent_sdk)
+ client = CodellamaMockClient([json.dumps(sentence)])
+ agent = CodellamaAgent(client, kernel_sdk)
task_opt = ProcessOptions(
streaming=True,
llm_name="codellama",
diff --git a/agent/tests/openai_agent_tests.py b/agent/tests/openai_agent_tests.py
index 1433231..d7e92cc 100644
--- a/agent/tests/openai_agent_tests.py
+++ b/agent/tests/openai_agent_tests.py
@@ -7,9 +7,10 @@
""" """
+import json
import logging
import pytest
-from og_sdk.agent_sdk import AgentSDK
+from og_sdk.kernel_sdk import KernelSDK
from og_agent import openai_agent
from og_proto.agent_server_pb2 import ProcessOptions, TaskResponse
from openai.openai_object import OpenAIObject
@@ -19,7 +20,6 @@
api_base = "127.0.0.1:9528"
api_key = "ZCeI9cYtOCyLISoi488BgZHeBkHWuFUH"
-
logger = logging.getLogger(__name__)
@@ -48,28 +48,166 @@ async def __anext__(self):
raise StopAsyncIteration
+class FunctionCallPayloadStream:
+
+ def __init__(self, name, arguments):
+ self.name = name
+ self.arguments = arguments
+
+ def __aiter__(self):
+ # create an iterator of the input keys
+ self.iter_keys = iter(self.arguments)
+ return self
+
+ async def __anext__(self):
+ try:
+ k = next(self.iter_keys)
+ obj = OpenAIObject()
+ delta = OpenAIObject()
+ function_para = OpenAIObject()
+ function_para.name = self.name
+ function_para.arguments = k
+ function_call = OpenAIObject()
+ function_call.function_call = function_para
+ delta.delta = function_call
+ obj.choices = [delta]
+ return obj
+ except StopIteration:
+ # raise stopasynciteration at the end of iterator
+ raise StopAsyncIteration
+
+
class MockContext:
def done(self):
return False
-@pytest_asyncio.fixture
-async def agent_sdk():
- sdk = AgentSDK(api_base, api_key)
- sdk.connect()
- yield sdk
- await sdk.close()
+class MultiCallMock:
+
+ def __init__(self, responses):
+ self.responses = responses
+ self.index = 0
+
+ def call(self, *args, **kwargs):
+ if self.index >= len(self.responses):
+ raise Exception("no more response")
+ self.index += 1
+ logger.debug("call index %d", self.index)
+ return self.responses[self.index - 1]
+
+
+@pytest.fixture
+def kernel_sdk():
+ endpoint = (
+ "localhost:9527" # Replace with the actual endpoint of your test gRPC server
+ )
+ return KernelSDK(endpoint, "ZCeI9cYtOCyLISoi488BgZHeBkHWuFUH")
+
+
+@pytest.mark.asyncio
+async def test_openai_agent_call_execute_bash_code(mocker, kernel_sdk):
+ kernel_sdk.connect()
+ arguments = {
+ "explanation": "the hello world in bash",
+ "code": "echo 'hello world'",
+ "saved_filenames": [],
+ }
+ stream1 = FunctionCallPayloadStream("execute_bash_code", json.dumps(arguments))
+ sentence = "The output 'hello world' is the result"
+ stream2 = PayloadStream(sentence)
+ call_mock = MultiCallMock([stream1, stream2])
+ with mocker.patch(
+ "og_agent.openai_agent.openai.ChatCompletion.acreate",
+ side_effect=call_mock.call,
+ ) as mock_openai:
+ agent = openai_agent.OpenaiAgent("gpt4", "prompt", kernel_sdk, is_azure=False)
+ queue = asyncio.Queue()
+ task_opt = ProcessOptions(
+ streaming=True,
+ llm_name="gpt4",
+ input_token_limit=100000,
+ output_token_limit=100000,
+ timeout=5,
+ )
+ await agent.arun("write a hello world in bash", queue, MockContext(), task_opt)
+ responses = []
+ while True:
+ try:
+ response = await queue.get()
+ if not response:
+ break
+ responses.append(response)
+ except asyncio.QueueEmpty:
+ break
+ logger.info(responses)
+ console_output = list(
+ filter(
+ lambda x: x.response_type == TaskResponse.OnStepActionStreamStdout,
+ responses,
+ )
+ )
+ assert len(console_output) == 1, "bad console output count"
+ assert console_output[0].console_stdout == "hello world\n", "bad console output"
+
+
+@pytest.mark.asyncio
+async def test_openai_agent_call_execute_python_code(mocker, kernel_sdk):
+ kernel_sdk.connect()
+ arguments = {
+ "explanation": "the hello world in python",
+ "code": "print('hello world')",
+ "saved_filenames": [],
+ }
+ stream1 = FunctionCallPayloadStream("execute_python_code", json.dumps(arguments))
+ sentence = "The output 'hello world' is the result"
+ stream2 = PayloadStream(sentence)
+ call_mock = MultiCallMock([stream1, stream2])
+ with mocker.patch(
+ "og_agent.openai_agent.openai.ChatCompletion.acreate",
+ side_effect=call_mock.call,
+ ) as mock_openai:
+ agent = openai_agent.OpenaiAgent("gpt4", "prompt", kernel_sdk, is_azure=False)
+ queue = asyncio.Queue()
+ task_opt = ProcessOptions(
+ streaming=True,
+ llm_name="gpt4",
+ input_token_limit=100000,
+ output_token_limit=100000,
+ timeout=5,
+ )
+
+ await agent.arun(
+ "write a hello world in python", queue, MockContext(), task_opt
+ )
+ responses = []
+ while True:
+ try:
+ response = await queue.get()
+ if not response:
+ break
+ responses.append(response)
+ except asyncio.QueueEmpty:
+ break
+ logger.info(responses)
+ console_output = list(
+ filter(
+ lambda x: x.response_type == TaskResponse.OnStepActionStreamStdout,
+ responses,
+ )
+ )
+ assert len(console_output) == 1, "bad console output count"
+ assert console_output[0].console_stdout == "hello world\n", "bad console output"
@pytest.mark.asyncio
-async def test_openai_agent_smoke_test(mocker, agent_sdk):
+async def test_openai_agent_smoke_test(mocker, kernel_sdk):
sentence = "Hello, how can I help you?"
stream = PayloadStream(sentence)
with mocker.patch(
"og_agent.openai_agent.openai.ChatCompletion.acreate", return_value=stream
) as mock_openai:
- agent = openai_agent.OpenaiAgent("gpt4", "prompt", agent_sdk, is_azure=False)
+ agent = openai_agent.OpenaiAgent("gpt4", "prompt", kernel_sdk, is_azure=False)
queue = asyncio.Queue()
task_opt = ProcessOptions(
streaming=True,
diff --git a/chat/src/og_terminal/terminal_chat.py b/chat/src/og_terminal/terminal_chat.py
index c500b49..d3895e3 100644
--- a/chat/src/og_terminal/terminal_chat.py
+++ b/chat/src/og_terminal/terminal_chat.py
@@ -305,6 +305,31 @@ def render_image(images, sdk, image_dir, console):
return False
+def upload_file(prompt, console, history_prompt, sdk, values):
+ filepaths = parse_file_path(prompt)
+ if not filepaths:
+ return prompt
+ task_blocks = TaskBlocks(values)
+ task_blocks.begin()
+ real_prompt = prompt.replace("/up", "")
+ with Live(Group(*[]), console=console) as live:
+ mk = """The following files will be uploaded
+"""
+ task_blocks.add_markdown(mk)
+ refresh(live, task_blocks, title=SYSTEM_TITLE)
+ for file in filepaths:
+ filename = file.split("/")[-1]
+ sdk.upload_file(file, filename)
+ mk = "* ✅%s\n" % file
+ task_blocks.add_markdown(mk)
+ real_prompt = real_prompt.replace(file, "uploaded %s" % filename)
+ refresh(live, task_blocks, title=SYSTEM_TITLE)
+ task_blocks.get_last_block().finish()
+ refresh(live, task_blocks, title=SYSTEM_TITLE)
+ history_prompt.append_string(real_prompt)
+ return real_prompt
+
+
def run_chat(prompt, sdk, session, console, values, filedir=None):
"""
run the chat
@@ -406,26 +431,7 @@ def app(octogen_dir):
console.print(f"❌ /cc{number} was not found!")
continue
# try to upload first⌛⏳❌
- filepaths = parse_file_path(real_prompt)
- if filepaths:
- real_prompt = real_prompt.replace("/up", "")
- spinner = Spinner(octopus_config.get("spinner", "dots2"), text="Upload...")
- segments = [spinner]
- mk = """The following files will be uploaded
-"""
- with Live(Group(*segments), console=console) as live:
- live.update(spinner)
- for file in filepaths:
- filename = file.split("/")[-1]
- sdk.upload_file(file, filename)
- mk += "* ✅%s\n" % file
- live.update(Group(*[Markdown(mk), spinner]))
- real_prompt = real_prompt.replace(file, "uploaded %s" % filename)
- # clear the spinner
- live.update(Group(*[Markdown(mk)]))
- # add the prompt to history
- # TODO remove the last prompt
- history.append_string(real_prompt)
+ real_prompt = upload_file(real_prompt, console, history, sdk, values)
run_chat(
real_prompt,
sdk,
diff --git a/chat/tests/test_chat_function.py b/chat/tests/test_chat_function.py
index 827a171..3e1a29d 100644
--- a/chat/tests/test_chat_function.py
+++ b/chat/tests/test_chat_function.py
@@ -35,7 +35,9 @@ def test_handle_final_answer_smoke_test():
respond_content = agent_server_pb2.TaskResponse(
state=task_state,
response_type=agent_server_pb2.TaskResponse.OnModelTypeText,
- typing_content=agent_server_pb2.TypingContent(content="hello world!", language="text")
+ typing_content=agent_server_pb2.TypingContent(
+ content="hello world!", language="text"
+ ),
)
respond_final = agent_server_pb2.TaskResponse(
state=task_state,
@@ -82,6 +84,7 @@ def test_handle_action_end_boundary_test():
assert len(images) == 1000
assert all(image == "test.png" for image in images)
+
def test_handle_action_end_smoke_test():
images = []
values = []
@@ -152,6 +155,7 @@ def test_error_handle_action_end():
assert len(images) == 0
assert values[0] == "\nerror"
+
def test_handle_action_end_performance_test():
# Setup
images = []
@@ -173,7 +177,9 @@ def test_handle_action_end_performance_test():
response_type=agent_server_pb2.TaskResponse.OnStepActionEnd,
on_step_action_end=agent_server_pb2.OnStepActionEnd(
output="",
- output_files=[f"test{i}.png"], # Modify this line to create unique filenames
+ output_files=[
+ f"test{i}.png"
+ ], # Modify this line to create unique filenames
has_error=False,
),
)