Skip to content

Commit 30b3775

Browse files
committed
first stab
1 parent 2c33e80 commit 30b3775

File tree

10 files changed

+2846
-20
lines changed

10 files changed

+2846
-20
lines changed

poetry.lock

Lines changed: 2389 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ python = "^3.9"
1111
click = "*"
1212
importlib-metadata = ">=8.2.0"
1313
langchain = "^0.2.12"
14+
langchain-openai = "^0.1.22"
15+
langchain-community = "^0.2.11"
16+
kgcl-schema = "^0.6.8"
17+
langchain-ollama = "^0.1.1"
18+
langchain-anthropic = "^0.1.22"
19+
langchain-chroma = "^0.1.3"
1420

1521
[tool.poetry.group.dev.dependencies]
1622
pytest = {version = ">=8.3.2"}

src/llm_change_agent/cli.py

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,17 @@
55
import click
66

77
from llm_change_agent import __version__
8-
from llm_change_agent.main import demo
8+
from llm_change_agent.constants import OPEN_AI_MODEL
9+
from llm_change_agent.llm_agent import LLMChangeAgent
10+
from llm_change_agent.utils.llm_utils import (
11+
get_anthropic_models,
12+
get_lbl_cborg_models,
13+
get_ollama_models,
14+
get_openai_models,
15+
)
16+
17+
ALL_AVAILABLE_PROVIDERS = ["openai", "ollama", "anthropic", "cborg"]
18+
ALL_AVAILABLE_MODELS = get_openai_models() + get_ollama_models() + get_anthropic_models() + get_lbl_cborg_models()
919

1020
__all__ = [
1121
"main",
@@ -36,9 +46,31 @@ def main(verbose: int, quiet: bool):
3646

3747

3848
@main.command()
39-
def run():
40-
"""Run the llm-change-agent's demo command."""
41-
demo()
49+
def list_models():
50+
"""List the available language models."""
51+
openai_models = "\n ".join(get_openai_models())
52+
anthropic_models = "\n ".join(get_anthropic_models())
53+
ollama_models = "\n ".join(get_ollama_models())
54+
lbl_cborg_models = "\n ".join(get_lbl_cborg_models())
55+
56+
click.echo(f"OpenAI models:\n {openai_models}")
57+
click.echo(f"Anthropic models:\n {anthropic_models}")
58+
click.echo(f"Ollama models:\n {ollama_models}")
59+
click.echo(f"LBL-CBORG models:\n {lbl_cborg_models}")
60+
61+
62+
@main.command()
63+
@click.option(
64+
"--model", type=click.Choice(ALL_AVAILABLE_MODELS), default=OPEN_AI_MODEL, help="Model to use for generation."
65+
)
66+
@click.option(
67+
"--provider", type=click.Choice(ALL_AVAILABLE_PROVIDERS), default="openai", help="Provider to use for generation."
68+
)
69+
@click.option("--prompt", type=str, default="Hello, world!", help="Prompt to use for generation.")
70+
def generate(model: str, provider: str, prompt: str):
71+
"""Generate text using the specified model."""
72+
llm_agent = LLMChangeAgent(model=model, prompt=prompt, provider=provider)
73+
llm_agent.run()
4274

4375

4476
if __name__ == "__main__":
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
"""Configuration classes for the LLM models."""
2+
3+
from pydantic import BaseModel
4+
5+
6+
class LLMConfig(BaseModel):
7+
"""Configuration for the LLM model."""
8+
9+
model: str
10+
provider: str
11+
temperature: float = 0.0
12+
13+
14+
class OpenAIConfig(LLMConfig):
15+
"""Configuration for OpenAI LLM model."""
16+
17+
pass
18+
19+
20+
class OllamaConfig(LLMConfig):
21+
"""Configuration for Ollama LLM model."""
22+
23+
format: str = None
24+
pass
25+
26+
27+
class AnthropicConfig(LLMConfig):
28+
"""Configuration for Anthropic LLM model."""
29+
30+
pass
31+
32+
33+
class CBORGConfig(LLMConfig):
34+
"""Configuration for CBORG LLM model."""
35+
36+
base_url: str = "https://api.cborg.lbl.gov" # Local clients can also use https://api-local.cborg.lbl.gov

src/llm_change_agent/constants.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
"""Constants for the LLM Change Agent."""
2+
3+
from os import getenv
4+
5+
from importlib_metadata import files
6+
7+
OPENAI_KEY = str(getenv("OPENAI_API_KEY"))
8+
ANTHROPIC_KEY = str(getenv("ANTHROPIC_API_KEY"))
9+
CBORG_KEY = str(getenv("CBORG_API_KEY"))
10+
11+
OPEN_AI_MODEL = "gpt-4o-mini"
12+
ANTHROPIC_MODEL = "claude-3-5-sonnet-20240620"
13+
OLLAMA_MODEL = "llama3.1" #! not all models support tools (tool calling)
14+
CBORG_MODEL = "lbl/llama-3"
15+
16+
KGCL_SCHEMA = [file for file in files("kgcl-schema") if file.stem == "kgcl" and file.suffix == ".yaml"][0]
17+
KGCL_GRAMMAR = [file for file in files("kgcl-schema") if file.stem == "kgcl" and file.suffix == ".lark"][0]

src/llm_change_agent/llm_agent.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
"""Main python file."""
2+
3+
from pprint import pprint
4+
5+
from llm_change_agent.constants import OPEN_AI_MODEL
6+
from llm_change_agent.utils.llm_utils import (
7+
execute_agent,
8+
get_anthropic_models,
9+
get_lbl_cborg_models,
10+
get_ollama_models,
11+
get_openai_models,
12+
get_provider_for_model,
13+
get_provider_model_map,
14+
llm_factory,
15+
)
16+
17+
18+
class LLMChangeAgent:
19+
"""Define LLMChangeAgent class."""
20+
21+
def __init__(self, model: str, prompt: str, provider: str):
22+
"""Initialize LLMChangeAgent class."""
23+
self.model = model
24+
self.prompt = prompt
25+
self.provider = provider
26+
self.llm = None
27+
28+
def _get_llm_config(self):
29+
"""Get the LLM configuration based on the selected LLM."""
30+
31+
def _validate_and_get_model(llm_model, get_models_func, default_model=OPEN_AI_MODEL):
32+
"""Validate the model and get the model."""
33+
if llm_model is None:
34+
return OpenAIConfig(model=default_model, provider=get_provider_for_model(llm_model))
35+
list_of_models = get_models_func()
36+
if llm_model not in list_of_models:
37+
raise ValueError(f"Model {llm_model} not supported. Please choose from {list_of_models}")
38+
return llm_model
39+
40+
if self.provider == "openai":
41+
from .config.llm_config import OpenAIConfig
42+
43+
llm_model = _validate_and_get_model(self.model, get_openai_models)
44+
return OpenAIConfig(model=llm_model, provider=get_provider_for_model(llm_model))
45+
46+
elif self.provider == "ollama":
47+
from .config.llm_config import OllamaConfig
48+
49+
llm_model = _validate_and_get_model(self.model, get_ollama_models)
50+
return OllamaConfig(model=llm_model)
51+
52+
elif self.provider == "anthropic":
53+
from .config.llm_config import AnthropicConfig
54+
55+
llm_model = _validate_and_get_model(self.model, get_anthropic_models)
56+
return AnthropicConfig(model=llm_model, provider=get_provider_for_model(llm_model))
57+
58+
elif self.provider == "cborg":
59+
from .config.llm_config import CBORGConfig
60+
61+
llm_model = _validate_and_get_model(self.model, get_lbl_cborg_models)
62+
return CBORGConfig(model=llm_model, provider=get_provider_for_model(llm_model))
63+
64+
else:
65+
all_models = get_provider_model_map()
66+
if llm_model is None:
67+
llm_model = OPEN_AI_MODEL
68+
from .config.llm_config import OpenAIConfig
69+
70+
return OpenAIConfig(model=llm_model, provider=get_provider_for_model(llm_model))
71+
72+
for provider, models in all_models.items():
73+
if llm_model in models:
74+
return self._get_llm_config(provider, llm_model)
75+
76+
raise ValueError(f"Model {llm_model} not supported.")
77+
78+
def run(self):
79+
"""Run the LLM Change Agent."""
80+
llm_config = self._get_llm_config()
81+
self.prompt = """
82+
Add a new exact synonym gastric cancer for MONDO_0001056.
83+
"""
84+
new_prompt = "Give me all relevant KGCL commands based on this request: " + self.prompt
85+
self.llm = llm_factory(llm_config)
86+
response = execute_agent(llm=self.llm, prompt=new_prompt)
87+
pprint(response["output"])
88+
return response["output"]

src/llm_change_agent/main.py

Lines changed: 0 additions & 10 deletions
This file was deleted.
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
"""Templates for the LLM Change Agent."""
2+
3+
from langchain_core.prompts.prompt import PromptTemplate
4+
5+
6+
def get_issue_analyzer_template():
7+
"""Issue analyzer template."""
8+
template = """
9+
{input}
10+
You are an semantic engineer. Based on the text you are given,
11+
you will analyze it and categorize the text into categories.
12+
The different categories along with their description are provided by the
13+
following LinkML schema:
14+
{schema}
15+
All that is expected of you is to categorize the text into one of the above categories and
16+
form relevant KGCL commands in a list.
17+
You have the following tools at your disposal to help you with this task:
18+
{tools}
19+
You also have the grammar in lark format: {grammar} along with an explanation of the grammar: {explanation}
20+
I want NO verbosity in your response. The final answer should be a dictionary with the category
21+
as key an command or list of commands as value.
22+
23+
Use the following format:
24+
25+
Question: the input question you must answer
26+
Thought: you should always think about what to do
27+
Action: the action to take, should be one of [{tool_names}]
28+
Action Input: the input to the action
29+
Observation: the result of the action
30+
... (this Thought/Action/Action Input/Observation can repeat N times)
31+
Thought: I now know the final answer
32+
Final Answer: the final answer to the original input question
33+
34+
Begin!
35+
36+
Question: {input}
37+
Thought:{agent_scratchpad}
38+
"""
39+
return PromptTemplate(
40+
input_variables=[
41+
"input",
42+
"agent_scratchpad",
43+
"tools",
44+
"tool_names",
45+
"intermediate_steps",
46+
"schema",
47+
"grammar",
48+
"explanation",
49+
],
50+
template=template,
51+
)
52+
53+
54+
def grammar_explanation():
55+
"""Grammar explanation template."""
56+
return """
57+
The grammar defines commands for various operations such as renaming, creating, deleting, and modifying entities. It includes the following components:
58+
59+
- `expression`: The entry point of the grammar, which can be any of the defined commands like `rename`, `create`, `delete`, etc.
60+
61+
- `rename`: This command follows the pattern `"rename" _WS [id _WS "from" _WS] old_label ["@" old_language] _WS ("to"|"as"|"->") _WS new_label ["@" new_language]`.
62+
63+
- `create`: This command follows the pattern `"create node" _WS id _WS label ["@" language]`.
64+
65+
- `create_class`: This command follows the pattern `"create" _WS id`.
66+
67+
- `create_synonym`: This command follows the pattern `"create" _WS [synonym_qualifier _WS] "synonym" _WS synonym ["@" language] _WS "for" _WS entity`.
68+
69+
- `delete`: This command follows the pattern `"delete" _WS entity`.
70+
71+
- `obsolete`: This command follows the pattern `"obsolete" _WS entity` or `"obsolete" _WS entity _WS "with replacement" _WS replacement`.
72+
73+
- `unobsolete`: This command follows the pattern `"unobsolete" _WS entity`.
74+
75+
- `deepen`: This command follows the pattern `"deepen" _WS entity _WS "from" _WS old_entity _WS ("to"|"->") _WS new_entity`.
76+
77+
- `shallow`: This command follows the pattern `"shallow" _WS entity _WS "from" _WS old_entity _WS ("to"|"->") _WS new_entity`.
78+
79+
- `move`: This command follows the pattern `"move" _WS entity_subject _WS entity_predicate _WS entity_object _WS "from" _WS old_entity _WS ("to"|"as"|"->") _WS new_entity`.
80+
81+
- `create_edge`: This command follows the pattern `"create edge" _WS entity_subject _WS entity_predicate _WS entity_object_id`.
82+
83+
- `delete_edge`: This command follows the pattern `"delete edge" _WS entity_subject _WS entity_predicate _WS entity_object_id`.
84+
85+
- `change_relationship`: This command follows the pattern `"change relationship between" _WS entity_subject _WS "and" _WS entity_object _WS "from" _WS old_entity _WS "to" _WS new_entity`.
86+
87+
- `change_annotation`: This command follows the pattern `"change annotation of" _WS entity_subject _WS "with" _WS entity_predicate _WS "from" _WS old_entity_object _WS "to" _WS new_entity_object`.
88+
89+
- `change_definition`: This command follows the pattern `"change definition of" _WS entity _WS "to" _WS new_definition` or `"change definition of" _WS entity _WS "from" _WS old_definition _WS "to" _WS new_definition`.
90+
91+
- `add_definition`: This command follows the pattern `"add definition" _WS new_definition _WS "to" _WS entity`.
92+
93+
- `remove_definition`: This command follows the pattern `"remove definition for" _WS entity`.
94+
95+
- `remove_from_subset`: This command follows the pattern `"remove" _WS id _WS "from subset" _WS subset`.
96+
97+
- `add_to_subset`: This command follows the pattern `"add" _WS id _WS "to subset" _WS subset`.
98+
99+
The `%import` statements import common definitions for `ID`, `LABEL`, `CURIE`, `SINGLE_QUOTE_LITERAL`, `TRIPLE_SINGLE_QUOTE_LITERAL`, `DOUBLE_QUOTE_LITERAL`, `TRIPLE_DOUBLE_QUOTE_LITERAL`, `LANGUAGE_TAG`, and whitespace (`_WS`). The `%ignore` statement tells the parser to ignore whitespace.
100+
101+
"""

0 commit comments

Comments
 (0)