split out js and py
@@ -1,38 +1,4 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
.yarn/install-state.gz
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
.env
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
|
||||
__pycache__
|
||||
.ruff_cache
|
||||
.mypy_cache
|
||||
@@ -0,0 +1,38 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
.yarn/install-state.gz
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
.env
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
|
||||
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 629 B After Width: | Height: | Size: 629 B |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
@@ -1,5 +1,3 @@
|
||||
"use server";
|
||||
|
||||
import { MessagePrimitive } from "@assistant-ui/react";
|
||||
import { ActionBarPrimitive } from "@assistant-ui/react";
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
"use client"
|
||||
|
||||
import React from 'react';
|
||||
import { Composer, ThreadPrimitive } from "@assistant-ui/react";
|
||||
import {
|
||||
Composer,
|
||||
ThreadPrimitive,
|
||||
AssistantRuntimeProvider,
|
||||
useLocalRuntime,
|
||||
type ChatModelAdapter,
|
||||
} from "@assistant-ui/react";
|
||||
import { AssistantMessage, UserMessage } from './Message';
|
||||
|
||||
export function TweetComposer(): React.ReactElement {
|
||||
@@ -0,0 +1 @@
|
||||
OPENAI_API_KEY=
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 LangChain, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,54 @@
|
||||
.PHONY: all format lint test tests integration_tests docker_tests help extended_tests
|
||||
|
||||
# Default target executed when no arguments are given to make.
|
||||
all: help
|
||||
|
||||
# Define a variable for the test file path.
|
||||
TEST_FILE ?= tests/unit_tests/
|
||||
integration_test integration_tests: TEST_FILE=tests/integration_tests/
|
||||
|
||||
test tests integration_test integration_tests:
|
||||
poetry run pytest $(TEST_FILE)
|
||||
|
||||
|
||||
######################
|
||||
# LINTING AND FORMATTING
|
||||
######################
|
||||
|
||||
# Define a variable for Python and notebook files.
|
||||
PYTHON_FILES=.
|
||||
MYPY_CACHE=.mypy_cache
|
||||
lint format: PYTHON_FILES=.
|
||||
lint_diff format_diff: PYTHON_FILES=$(shell git diff --relative=libs/partners/mongo-rag-cli --name-only --diff-filter=d master | grep -E '\.py$$|\.ipynb$$')
|
||||
lint_package: PYTHON_FILES=gen_ui_backend
|
||||
lint_tests: PYTHON_FILES=tests
|
||||
lint_tests: MYPY_CACHE=.mypy_cache_test
|
||||
|
||||
lint lint_diff lint_package lint_tests:
|
||||
poetry run ruff .
|
||||
poetry run ruff format $(PYTHON_FILES) --diff
|
||||
poetry run ruff --select I $(PYTHON_FILES)
|
||||
mkdir $(MYPY_CACHE); poetry run mypy $(PYTHON_FILES) --cache-dir $(MYPY_CACHE)
|
||||
|
||||
format format_diff:
|
||||
poetry run ruff format $(PYTHON_FILES)
|
||||
poetry run ruff --select I --fix $(PYTHON_FILES)
|
||||
|
||||
spell_check:
|
||||
poetry run codespell --toml pyproject.toml
|
||||
|
||||
spell_fix:
|
||||
poetry run codespell --toml pyproject.toml -w
|
||||
|
||||
######################
|
||||
# HELP
|
||||
######################
|
||||
|
||||
help:
|
||||
@echo '----'
|
||||
@echo 'check_imports - check imports'
|
||||
@echo 'format - run code formatters'
|
||||
@echo 'lint - run linters'
|
||||
@echo 'test - run unit tests'
|
||||
@echo 'tests - run unit tests'
|
||||
@echo 'test TEST_FILE=<test_file> - run all tests in file'
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"dependencies": ["."],
|
||||
"graphs": {
|
||||
"agent": "./tweet_composer/__init__.py:build_graph"
|
||||
},
|
||||
"env": ".env"
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
[tool.poetry]
|
||||
name = "tweet-composer"
|
||||
version = "0.0.0"
|
||||
description = ""
|
||||
authors = []
|
||||
readme = "README.md"
|
||||
license = "MIT"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "<3.12,>=3.9.0"
|
||||
langchain-core = "^0.3.0"
|
||||
langchain-openai = "^0.2.0"
|
||||
python-dotenv = "^1.0.1"
|
||||
langgraph = "^0.2.21"
|
||||
pydantic = ">=2.5.2,<3.0.0"
|
||||
|
||||
[tool.poetry.group.test]
|
||||
optional = true
|
||||
|
||||
[tool.poetry.group.test.dependencies]
|
||||
pytest = "^7.3.0"
|
||||
freezegun = "^1.2.2"
|
||||
pytest-mock = "^3.10.0"
|
||||
syrupy = "^4.0.2"
|
||||
pytest-watcher = "^0.3.4"
|
||||
pytest-asyncio = "^0.21.1"
|
||||
|
||||
[tool.poetry.group.codespell]
|
||||
optional = true
|
||||
|
||||
[tool.poetry.group.codespell.dependencies]
|
||||
codespell = "^2.2.0"
|
||||
|
||||
[tool.poetry.group.lint]
|
||||
optional = true
|
||||
|
||||
[tool.poetry.group.lint.dependencies]
|
||||
ruff = "^0.1.5"
|
||||
|
||||
[tool.poetry.group.typing.dependencies]
|
||||
mypy = "^1"
|
||||
|
||||
[tool.poetry.group.test_integration]
|
||||
optional = true
|
||||
|
||||
[tool.poetry.group.test_integration.dependencies]
|
||||
|
||||
[tool.ruff]
|
||||
select = [
|
||||
"E", # pycodestyle
|
||||
"F", # pyflakes
|
||||
"I", # isort
|
||||
]
|
||||
ignore = ["E501"]
|
||||
|
||||
[tool.mypy]
|
||||
disallow_untyped_defs = "True"
|
||||
|
||||
[tool.coverage.run]
|
||||
omit = ["tests/*"]
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
# --strict-markers will raise errors on unknown marks.
|
||||
# https://docs.pytest.org/en/7.1.x/how-to/mark.html#raising-errors-on-unknown-marks
|
||||
#
|
||||
# https://docs.pytest.org/en/7.1.x/reference/reference.html
|
||||
# --strict-config any warnings encountered while parsing the `pytest`
|
||||
# section of the configuration file raise errors.
|
||||
#
|
||||
# https://github.com/tophat/syrupy
|
||||
# --snapshot-warn-unused Prints a warning on unused snapshots rather than fail the test suite.
|
||||
addopts = "--snapshot-warn-unused --strict-markers --strict-config --durations=5"
|
||||
# Registering custom markers.
|
||||
# https://docs.pytest.org/en/7.1.x/example/markers.html#registering-markers
|
||||
markers = [
|
||||
"requires: mark tests as requiring a specific library",
|
||||
"asyncio: mark tests as requiring asyncio",
|
||||
"compile: mark placeholder test used to compile integration tests without running them",
|
||||
]
|
||||
asyncio_mode = "auto"
|
||||
@@ -0,0 +1,85 @@
|
||||
from typing import Any, Dict, List
|
||||
|
||||
from langchain_openai import ChatOpenAI
|
||||
from langgraph.graph import END, START, MessagesState, StateGraph
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class UserRules(BaseModel):
|
||||
rules: List[str] = Field(
|
||||
description="The rules which you have inferred from the conversation."
|
||||
)
|
||||
|
||||
|
||||
class GraphState(MessagesState):
|
||||
userRules: UserRules
|
||||
userAcceptedText: bool
|
||||
|
||||
|
||||
DEFAULT_RULES_STRING = "*no rules have been set yet*"
|
||||
|
||||
SYSTEM_PROMPT = """You are a helpful assistant tasked with thoughtfully fulfilling the requests of the user.
|
||||
User defined rules:
|
||||
{userRules}"""
|
||||
|
||||
|
||||
def call_model(state: GraphState) -> Dict[str, List[Dict[str, str]]]:
|
||||
model = ChatOpenAI(model="gpt-4o-mini", temperature=0)
|
||||
|
||||
rules = DEFAULT_RULES_STRING
|
||||
if state.get("userRules") and state.get("userRules").rules:
|
||||
rules = "- " + "\n - ".join(state.get("userRules").rules)
|
||||
|
||||
system_prompt = SYSTEM_PROMPT.format(userRules=rules)
|
||||
|
||||
response = model.invoke(
|
||||
[{"role": "system", "content": system_prompt}, *state["messages"]]
|
||||
)
|
||||
|
||||
return {"messages": [{"role": "assistant", "content": response.content}]}
|
||||
|
||||
|
||||
def generate_insights(state: GraphState) -> Dict[str, Any]:
|
||||
system_prompt = """You are a helpful assistant, tasked with generating rules based on insights you've gathered from the following conversation.
|
||||
This conversation contains back and fourth between an AI assistant, and a user who is using the assistant to generate text for things such as writing tweets.
|
||||
User messages which are prefixed with "REVISION" contain the entire revised text the user made to the assistant message directly before in the conversation.
|
||||
There also may be additional back and fourth between the user and the assistant which you should consider when generating rules.
|
||||
|
||||
In your response, include every single rule, including those which already existed. You should only ever exclude rule(s) if the user has explicitly stated something which contradicts a previous rule.
|
||||
The user and assistant may have had conversations before which you do not have access to, so be careful when removing rules.
|
||||
|
||||
The user has defined the following rules:
|
||||
{userRules}"""
|
||||
|
||||
rules = DEFAULT_RULES_STRING
|
||||
if state.get("userRules") and state.get("userRules").rules:
|
||||
rules = "- " + "\n - ".join(state.get("userRules").rules)
|
||||
|
||||
system_prompt = system_prompt.format(userRules=rules)
|
||||
|
||||
model = ChatOpenAI(model="gpt-4o", temperature=0).with_structured_output(UserRules)
|
||||
|
||||
result = model.invoke(
|
||||
[{"role": "system", "content": system_prompt}, *state["messages"]]
|
||||
)
|
||||
|
||||
return {"userRules": result.rules, "userAcceptedText": False}
|
||||
|
||||
|
||||
def should_generate_insights(state: GraphState) -> str:
|
||||
print(state)
|
||||
if state.get("userAcceptedText", False):
|
||||
return "generateInsights"
|
||||
return "callModel"
|
||||
|
||||
|
||||
def build_graph():
|
||||
workflow = StateGraph(GraphState)
|
||||
|
||||
workflow.add_node("callModel", call_model)
|
||||
workflow.add_node("generateInsights", generate_insights)
|
||||
workflow.add_conditional_edges(START, should_generate_insights)
|
||||
workflow.add_edge("callModel", END)
|
||||
workflow.add_edge("generateInsights", END)
|
||||
|
||||
return workflow.compile()
|
||||