Skip to content
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
21 changes: 21 additions & 0 deletions examples/github_notifier/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from openai import OpenAI
from npiai.core import Agent
from npiai.app.github import GitHub
from npiai.app.google import Gmail
from npiai.app.human_feedback import ConsoleFeedback


def main():
agent = Agent(
agent_name='github_notifier',
prompt='You are a Github Notifier that informs users when a new issue or pull request is open.',
description='Github Notifier that informs users when a new issue or pull request is open',
llm=OpenAI()
)
agent.use(GitHub(), Gmail(), ConsoleFeedback())

agent.when("idiotWu/npi-test has a new issue, send an email to [email protected] with the body of issue")


if __name__ == '__main__':
main()
14 changes: 14 additions & 0 deletions npi/app/github/__test__/watch_issues.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import asyncio

from npi.app.github import GitHub
from utils import test_init_github_cred


async def main():
github = GitHub()
return await github.chat('Notify me when new issues are created in idiotWu/npi-test')


if __name__ == '__main__':
test_init_github_cred()
asyncio.run(main())
42 changes: 33 additions & 9 deletions npi/core/app.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
"""The basic interface for NPi Apps"""
import asyncio
import json
import inspect
import functools
from typing import Dict, List, Optional, Union, Type, cast
from typing import Dict, List, Optional, Union, Type, cast, Callable, Any, Awaitable

from pydantic import Field
from openai import AsyncClient
Expand Down Expand Up @@ -302,16 +303,23 @@ async def _call_llm(self, thread: Thread, message: ThreadMessage) -> str:
await thread.send_msg(callback.Callable(call_msg))
logger.info(call_msg)

if fn_reg.Params is not None:
res = await fn_reg.fn(
params=fn_reg.Params(
_thread=thread,
_message=message,
**args,
async def _exec_tool():
if fn_reg.Params is not None:
return await fn_reg.fn(
params=fn_reg.Params(
_thread=thread,
_message=message,
**args,
)
)
)
else:
return await fn_reg.fn()

if args.get('npi_watch', 0) > 0:
logger.debug(f'[{self.name}]: watching function `{fn_name}`, interval: {args["npi_watch"]}s')
res = await self._watch_tool(_exec_tool, args['npi_watch'])
else:
res = await fn_reg.fn()
res = await _exec_tool()

logger.debug(f'[{self.name}]: function `{fn_name}` returned: {res}')

Expand All @@ -325,3 +333,19 @@ async def _call_llm(self, thread: Thread, message: ThreadMessage) -> str:
)

return response_message.content

async def _watch_tool(self, fn: Callable[[], Awaitable[str]], interval: int) -> str:
init_val = await fn()
logger.debug(f'[{self.name}]: watching: initial value: {init_val}')

while True:
await asyncio.sleep(interval)
val = await fn()
if val != init_val:
logger.debug(f'[{self.name}]: watching: changes detected: {val}')
return json.dumps(
{
"previous": init_val,
"current": val,
}, ensure_ascii=False
)
2 changes: 1 addition & 1 deletion npi/types/function_registration.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import asyncio
from npi.types.parameters import Parameters

ToolFunction = Callable[[Parameters], str | None | Awaitable[str | None]]
ToolFunction = Callable[[Parameters], str | Awaitable[str]]


class FunctionRegistration:
Expand Down
15 changes: 14 additions & 1 deletion npi/types/parameters.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,23 @@
from pydantic import BaseModel
from pydantic import BaseModel, Field
from npi.core.thread import Thread, ThreadMessage


class Parameters(BaseModel):
"""Base parameter model for tool definitions"""

npi_watch: int = Field(
default=0,
description="""
Interval (in seconds) for detecting changes in the return value of this tool.
If set to 0, the tool will not detect changes and will immediately returns.
If set to a positive integer, the tool will not return anything until a change in the return value is detected.
This is useful when you need to listen to the changes. For example, if you want to check new
emails in the inbox, you may call `get_emails({ "query": "is:unread", "npi_watch": 3 })`
and it will call the tool every 3 seconds and if a new email arrives, it will return the following diff:
{ "previous": Email[], "current": Email[] }
"""
)

_thread: Thread
_message: ThreadMessage

Expand Down
91 changes: 45 additions & 46 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ playwright = "^1.43.0"
loguru = "^0.7.2"
markdownify = "^0.12.1"
npiai-proto = "0.0.4.dev1"
npiai = { path = "./sdk/python"}
npiai = { path = "./sdk/python", develop = true }
discord-py = "^2.3.2"
pyyaml = "^6.0.1"
slack-sdk = "^3.27.1"
Expand All @@ -40,3 +40,4 @@ build-backend = "poetry.core.masonry.api"
server = "npi.server.app:main"
negotiator = "examples.calendar_negotiator.main:main"
twitter = "examples.twitter_crawler.main:main"
github-notifier = "examples.github_notifier.main:main"
Loading