Skip to content

Commit 35eeb7a

Browse files
authored
Merge pull request #34 from pattern-tech/feat/llms-support
Feat/llms support
2 parents 3d33379 + bbd4aaf commit 35eeb7a

6 files changed

Lines changed: 202 additions & 33 deletions

File tree

api/.env.sample

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
OPENAI_API_KEY=
2-
31
POSTGRES_HOST=postgres
42
POSTGRES_PORT=5432
53
POSTGRES_DB=pattern-core
@@ -11,7 +9,7 @@ JWT_SECRET_KEY=mysecretkey
119
QDRANT_URL=http://qdrant:6333
1210
QDRANT_COLLECTION=pattern-core
1311

14-
LANGCHAIN_API_KEY=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
12+
LANGCHAIN_API_KEY=
1513

1614
#functions
1715
GOOGLE_SEARCH_URL=https://google.serper.dev/search
@@ -24,4 +22,24 @@ EXA_URL=https://api.exa.ai
2422
PERPLEXITY_URL=https://api.perplexity.ai
2523
TAVILY_URL=https://api.tavily.com
2624

27-
SECRET_KEY=
25+
SECRET_KEY=
26+
27+
#llm
28+
# +-------------+------------------------------------------------+
29+
# | Provider | Model |
30+
# +-------------+------------------------------------------------+
31+
# | openai | gpt4o-mini |
32+
# | google | gemini-2.0-flash |
33+
# | together | deepseek-ai/DeepSeek-R1-Distill-Llama-70B-free |
34+
# | ollama | llama3.3 |
35+
# | groq | llama-3.3-70b-versatile |
36+
# | firework | accounts/fireworks/models/firefunction-v2 |
37+
# | huggingface | meta-llama/Llama-3.3-70B-Instruct |
38+
# +-------------+------------------------------------------------+
39+
40+
LLM_SERVICE=openai
41+
LLM_MODEL=gpt-4o-mini
42+
LLM_API_KEY=
43+
44+
OLLAMA_HOST=
45+
OLLAMA_MODELS=

api/.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,4 +213,6 @@ cython_debug/
213213
**/.DS_Store
214214

215215
data_processing/*
216-
!data_processing/*.py
216+
!data_processing/*.py
217+
218+
/.vscode

api/requirements.txt

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,18 @@ fastapi==0.115.5
33
uvicorn==0.32.0
44
python-dotenv==1.0.1
55
bcrypt==4.2.0
6-
pydantic==2.9.2
7-
pydantic[email]==2.9.2
6+
pydantic==2.9.0
7+
pydantic[email]==2.9.0
88
sqlalchemy==2.0.35
99
python-jose==3.3.0
1010
passlib==1.7.4
1111
langgraph==0.2.53
1212
langchain-community==0.3.7
13-
langchain-openai==0.2.9
1413
langchain-qdrant==0.2.0
1514
langchain-postgres==0.0.12
1615
psycopg==3.2.3
1716
psycopg-pool==3.2.4
18-
psycopg2-binary==2.9.10
17+
psycopg2-binary==2.9.10
1918
newsapi-python==0.2.7
2019
cryptography==44.0.0
2120
beautifulsoup4==4.12.3
@@ -24,3 +23,11 @@ web3==7.6.1
2423
moralis==0.1.49
2524
scalar_fastapi==1.0.3
2625
python-multipart==0.0.19
26+
langchain-openai==0.3.0
27+
langchain-ollama==0.2.3
28+
langchain-together==0.3.0
29+
langchain-fireworks==0.2.7
30+
langchain-google-genai==2.0.7
31+
langchain-google-vertexai==2.0.9
32+
langchain-groq==0.2.4
33+
langchain-huggingface==0.1.2

api/src/agent/services/agent_service.py

Lines changed: 53 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,22 @@
1+
import os
12
import json
23
import asyncio
34

45
from typing import List
56
from langchain import hub
67
from pydantic import BaseModel, Field
8+
from langchain_ollama import ChatOllama
79
from langchain_openai import ChatOpenAI
8-
from langgraph.prebuilt import create_react_agent
10+
from langchain.agents import create_react_agent
911
from langchain_core.prompts import ChatPromptTemplate
1012
from langchain.callbacks.base import BaseCallbackHandler
1113
from langchain_core.callbacks import StdOutCallbackHandler
1214
from langchain_core.runnables.history import RunnableWithMessageHistory
13-
from langchain.agents import AgentExecutor, create_openai_functions_agent
15+
from langchain.agents import (AgentExecutor,
16+
create_openai_functions_agent,
17+
create_tool_calling_agent)
18+
19+
from src.agent.tools.shared_tools import init_llm
1420

1521

1622
class PlanStep(BaseModel):
@@ -127,20 +133,28 @@ def __init__(self, tools, memory=None, streaming: bool = True):
127133
# Set up the streaming callback if streaming is enabled.
128134
if streaming:
129135
self.streaming_handler = StreamingCallbackHandler()
130-
self.llm = ChatOpenAI(
131-
model="gpt-4o-mini",
132-
streaming=True,
133-
callbacks=[self.streaming_handler]
134-
)
136+
137+
self.llm = init_llm(service=os.environ["LLM_SERVICE"],
138+
model_name=os.environ["LLM_MODEL"],
139+
api_key=os.environ["LLM_API_KEY"],
140+
stream=streaming,
141+
callbacks=[self.streaming_handler])
142+
143+
if isinstance(self.llm, ChatOpenAI):
144+
self.prompt = hub.pull("pattern-agent/pattern-agent")
145+
146+
self.agent = create_openai_functions_agent(
147+
self.llm, self.tools, self.prompt)
148+
elif isinstance(self.llm, ChatOllama):
149+
self.prompt = hub.pull("hwchase17/react")
150+
151+
self.agent = create_react_agent(
152+
llm=self.llm, tools=self.tools, prompt=self.prompt)
135153
else:
136-
self.llm = ChatOpenAI(model="gpt-4o-mini")
154+
self.prompt = hub.pull("pattern-agent/pattern-agent")
137155

138-
self.prompt = hub.pull("pattern-agent/pattern-agent")
139-
self.agent = create_openai_functions_agent(
140-
self.llm,
141-
self.tools,
142-
self.prompt
143-
)
156+
self.agent = create_tool_calling_agent(
157+
llm=self.llm, tools=self.tools, prompt=self.prompt)
144158

145159
if streaming:
146160
self.agent_executor = AgentExecutor(
@@ -168,7 +182,19 @@ def __init__(self, tools, memory=None, streaming: bool = True):
168182

169183
async def stream(self, message: str):
170184
"""
171-
Asynchronously stream the agent’s response token-by-token.
185+
Args:
186+
message (str): The input message to be processed by the agent.
187+
188+
Yields:
189+
str: Tokens of the agent's response as they become available.
190+
191+
Raises:
192+
asyncio.TimeoutError: If waiting for a token from the queue times out.
193+
194+
Notes:
195+
- If memory is enabled, the agent's response is invoked synchronously using `run_in_executor`.
196+
- If memory is not enabled, the agent's response is invoked asynchronously using `arun`.
197+
- The method clears any leftover tokens in the queue before starting to stream the response.
172198
"""
173199
# Clear any leftover tokens.
174200
while not self.streaming_handler.queue.empty():
@@ -200,6 +226,18 @@ async def stream(self, message: str):
200226
result = await task
201227

202228
def ask(self, message: str):
229+
"""
230+
Sends a message to the agent and returns the response.
231+
232+
Args:
233+
message (str): The message to send to the agent.
234+
235+
Returns:
236+
The response from the agent.
237+
238+
If the agent has memory, it uses the agent with chat history to invoke the response.
239+
Otherwise, it uses the agent executor to invoke the response.
240+
"""
203241
if self.memory:
204242
return self.agent_with_chat_history.invoke(
205243
input={"input": message},

api/src/agent/tools/agentic/eth_blockchain_tool.py

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
1-
from langchain.tools import tool
1+
import os
22

33
from langchain import hub
4+
from langchain.tools import tool
5+
from langchain_ollama import ChatOllama
46
from langchain_openai import ChatOpenAI
5-
from langchain.agents import AgentExecutor, create_openai_functions_agent
7+
from langchain.agents import (
8+
AgentExecutor,
9+
create_openai_functions_agent,
10+
create_tool_calling_agent)
611

7-
from src.agent.tools.shared_tools import handle_exceptions, timeout
12+
from src.agent.tools.shared_tools import init_llm
813
from src.agent.tools.tools_index import get_all_tools
14+
from src.agent.tools.shared_tools import handle_exceptions, timeout
915

1016

1117
@tool
@@ -35,14 +41,29 @@ def ethereum_blockchain_tool(query: str):
3541
ValueError: If the query cannot be parsed or contract address is invalid
3642
"""
3743

38-
llm = ChatOpenAI(model="gpt-4o-mini")
39-
prompt = hub.pull("pattern-agent/eth-agent")
44+
llm = init_llm(service=os.environ["LLM_SERVICE"],
45+
model_name=os.environ["LLM_MODEL"],
46+
api_key=os.environ["LLM_API_KEY"],
47+
stream=False)
48+
4049
tools = get_all_tools(tools_path="eth_blockchain_function")
4150

42-
agent = create_openai_functions_agent(
43-
llm,
44-
tools,
45-
prompt)
51+
if isinstance(llm, ChatOpenAI):
52+
prompt = hub.pull("pattern-agent/eth-agent")
53+
54+
agent = create_openai_functions_agent(
55+
llm, tools, prompt)
56+
elif isinstance(llm, ChatOllama):
57+
prompt = hub.pull("hwchase17/react")
58+
59+
agent = create_react_agent(
60+
llm=llm, tools=tools, prompt=prompt)
61+
else:
62+
prompt = hub.pull("pattern-agent/eth-agent")
63+
64+
agent = create_tool_calling_agent(
65+
llm=llm, tools=tools, prompt=prompt)
66+
4667

4768
agent_executor = AgentExecutor(
4869
agent=agent,

api/src/agent/tools/shared_tools.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,14 @@
22

33
from typing import TypeVar
44
from functools import wraps
5+
from langchain_groq import ChatGroq
6+
from langchain_openai import ChatOpenAI
7+
from langchain_ollama import ChatOllama
58
from multiprocessing import Process, Queue
9+
from langchain_together import ChatTogether
10+
from langchain_fireworks import ChatFireworks
11+
from langchain_google_genai import ChatGoogleGenerativeAI
12+
from langchain_huggingface import ChatHuggingFace, HuggingFacePipeline
613

714
T = TypeVar('T')
815

@@ -116,3 +123,79 @@ def wrapper(*args, **kwargs):
116123
except Exception as e:
117124
return f"Error: {str(e)}, Class: {e.__class__.__name__}"
118125
return wrapper
126+
127+
128+
def init_llm(service: str, model_name: str, api_key: str, stream: bool = False, callbacks=None):
129+
"""
130+
Returns an instance of a language model based on the specified service.
131+
132+
Args:
133+
service (str): The name of the service to use (e.g., "openai", "groq", "fireworks",
134+
"together", "huggingface", "ollama").
135+
model_name (str): The name of the model to use.
136+
api_key (str): The API key for the specified service.
137+
stream (bool, optional): Whether to enable streaming for the model. Defaults to False.
138+
callbacks (StreamingCallbackHandler, optional): callback functions for the model. Defaults to None.
139+
140+
Returns:
141+
An instance of the specified language model.
142+
143+
Raises:
144+
NotImplementedError: If the specified service is not supported.
145+
"""
146+
if service == "openai":
147+
return ChatOpenAI(
148+
model=model_name,
149+
streaming=False,
150+
api_key=api_key,
151+
callbacks=callbacks
152+
)
153+
elif service == "google":
154+
return ChatGoogleGenerativeAI(
155+
model=model_name,
156+
api_key=api_key,
157+
streaming=stream,
158+
callbacks=callbacks
159+
)
160+
elif service == "groq":
161+
return ChatGroq(
162+
model=model_name,
163+
api_key=api_key,
164+
streaming=stream,
165+
callbacks=callbacks
166+
)
167+
elif service == "fireworks":
168+
return ChatFireworks(
169+
model=model_name,
170+
api_key=api_key,
171+
streaming=stream,
172+
callbacks=callbacks
173+
)
174+
elif service == "together":
175+
return ChatTogether(
176+
model=model_name,
177+
together_api_key=api_key,
178+
streaming=stream,
179+
callbacks=callbacks
180+
)
181+
elif service == "huggingface":
182+
pipeline_kwargs = {
183+
"max_new_tokens": 512,
184+
"do_sample": False,
185+
"repetition_penalty": 1.03,
186+
}
187+
model = HuggingFacePipeline.from_model_id(
188+
model_id=model_name,
189+
task="text-generation",
190+
pipeline_kwargs=pipeline_kwargs,
191+
device_map="cpu"
192+
)
193+
return ChatHuggingFace(llm=model, callbacks=callbacks)
194+
elif service == "ollama":
195+
return ChatOllama(
196+
model=model_name,
197+
streaming=stream,
198+
callbacks=callbacks
199+
)
200+
else:
201+
raise NotImplementedError(f"Service {service} is not supported.")

0 commit comments

Comments
 (0)