diff --git a/.github/workflows/generate-application-server-client.yml b/.github/workflows/generate-application-server-client.yml
index 1c0ad012..0e22003e 100644
--- a/.github/workflows/generate-application-server-client.yml
+++ b/.github/workflows/generate-application-server-client.yml
@@ -83,4 +83,4 @@ jobs:
run: |
echo "Removing the autocommit-openapi label..."
curl --silent --fail-with-body -X DELETE -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
- https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/labels/autocommit-openapi
+ https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/labels/autocommit-openapi
\ No newline at end of file
diff --git a/.github/workflows/generate-intelligence-service-client.yml b/.github/workflows/generate-intelligence-service-client.yml
index 54842180..88b05152 100644
--- a/.github/workflows/generate-intelligence-service-client.yml
+++ b/.github/workflows/generate-intelligence-service-client.yml
@@ -57,7 +57,7 @@ jobs:
- name: Install Python dependencies
working-directory: server/intelligence-service
- run: poetry install --no-interaction --no-root
+ run: poetry lock --no-update && poetry install --no-interaction --no-root
- name: Generate API client for the application server
working-directory: server/intelligence-service
@@ -97,4 +97,4 @@ jobs:
run: |
echo "Removing the autocommit-openapi label..."
curl --silent --fail-with-body -X DELETE -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
- https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/labels/autocommit-openapi
+ https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/labels/autocommit-openapi
\ No newline at end of file
diff --git a/.github/workflows/black.yml b/.github/workflows/intelligence-service-qa.yml
similarity index 80%
rename from .github/workflows/black.yml
rename to .github/workflows/intelligence-service-qa.yml
index fba24f95..a73cabfd 100644
--- a/.github/workflows/black.yml
+++ b/.github/workflows/intelligence-service-qa.yml
@@ -1,9 +1,17 @@
-name: Lint
+name: Intelligence Service QA
-on: [pull_request]
+on:
+ pull_request:
+ paths:
+ - "server/intelligence-service/**"
+ push:
+ paths:
+ - "server/intelligence-service/**"
+ branches: [develop]
jobs:
lint:
+ name: Code Quality Checks
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
diff --git a/package.json b/package.json
index 59110344..e32cd3d8 100644
--- a/package.json
+++ b/package.json
@@ -16,8 +16,10 @@
"generate:api:application-server": "npm run generate:api:application-server-specs && npm run generate:api:application-server:clean && npm run generate:api:application-server-client",
"generate:api:intelligence-service": "npm run generate:api:intelligence-service:clean && npm run generate:api:intelligence-service-specs && npm run generate:api:intelligence-service-client",
"generate:api": "npm run generate:api:intelligence-service && npm run generate:api:application-server",
- "prettier:java:check": "prettier --check server/application-server/src/**/*.java",
- "prettier:java:write": "prettier --write server/application-server/src/**/*.java",
+ "format:java:check": "prettier --check server/application-server/src/**/*.java",
+ "format:java:write": "prettier --write server/application-server/src/**/*.java",
+ "format:python:check": "cd server/intelligence-service/ && poetry run black --check .",
+ "format:python:write": "cd server/intelligence-service/ && poetry run black .",
"db:changelog:diff": "cd server/application-server && docker compose up -d postgres && mvn liquibase:diff && docker compose down postgres"
},
"devDependencies": {
diff --git a/server/application-server/src/main/java/de/tum/in/www1/hephaestus/intelligenceservice/api/DefaultApi.java b/server/application-server/src/main/java/de/tum/in/www1/hephaestus/intelligenceservice/api/DefaultApi.java
index 0158bbd6..946825f3 100644
--- a/server/application-server/src/main/java/de/tum/in/www1/hephaestus/intelligenceservice/api/DefaultApi.java
+++ b/server/application-server/src/main/java/de/tum/in/www1/hephaestus/intelligenceservice/api/DefaultApi.java
@@ -42,7 +42,7 @@ public DefaultApi(ApiClient apiClient) {
}
/**
- * Get a response from an LLM to a chat message.
+ * Start and continue a chat session with an LLM.
*
*
200 - Successful Response
*
422 - Validation Error
@@ -55,7 +55,7 @@ public ChatResponse chatChatPost(ChatRequest chatRequest) throws RestClientExcep
}
/**
- * Get a response from an LLM to a chat message.
+ * Start and continue a chat session with an LLM.
*
*
200 - Successful Response
*
422 - Validation Error
diff --git a/server/application-server/src/main/java/de/tum/in/www1/hephaestus/intelligenceservice/model/ChatRequest.java b/server/application-server/src/main/java/de/tum/in/www1/hephaestus/intelligenceservice/model/ChatRequest.java
index 70234d22..2a35c7bd 100644
--- a/server/application-server/src/main/java/de/tum/in/www1/hephaestus/intelligenceservice/model/ChatRequest.java
+++ b/server/application-server/src/main/java/de/tum/in/www1/hephaestus/intelligenceservice/model/ChatRequest.java
@@ -20,6 +20,10 @@
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.fasterxml.jackson.annotation.JsonValue;
+import org.openapitools.jackson.nullable.JsonNullable;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import org.openapitools.jackson.nullable.JsonNullable;
+import java.util.NoSuchElementException;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.annotation.JsonTypeName;
import org.hibernate.validator.constraints.*;
@@ -28,13 +32,17 @@
* ChatRequest
*/
@JsonPropertyOrder({
- ChatRequest.JSON_PROPERTY_MESSAGE
+ ChatRequest.JSON_PROPERTY_MESSAGE,
+ ChatRequest.JSON_PROPERTY_THREAD_ID
})
@jakarta.annotation.Generated(value = "org.openapitools.codegen.languages.JavaClientCodegen", comments = "Generator version: 7.7.0")
public class ChatRequest {
public static final String JSON_PROPERTY_MESSAGE = "message";
private String message;
+ public static final String JSON_PROPERTY_THREAD_ID = "thread_id";
+ private JsonNullable threadId = JsonNullable.undefined();
+
public ChatRequest() {
}
@@ -63,6 +71,39 @@ public void setMessage(String message) {
this.message = message;
}
+ public ChatRequest threadId(String threadId) {
+ this.threadId = JsonNullable.of(threadId);
+
+ return this;
+ }
+
+ /**
+ * Get threadId
+ * @return threadId
+ */
+ @jakarta.annotation.Nullable
+ @JsonIgnore
+
+ public String getThreadId() {
+ return threadId.orElse(null);
+ }
+
+ @JsonProperty(JSON_PROPERTY_THREAD_ID)
+ @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS)
+
+ public JsonNullable getThreadId_JsonNullable() {
+ return threadId;
+ }
+
+ @JsonProperty(JSON_PROPERTY_THREAD_ID)
+ public void setThreadId_JsonNullable(JsonNullable threadId) {
+ this.threadId = threadId;
+ }
+
+ public void setThreadId(String threadId) {
+ this.threadId = JsonNullable.of(threadId);
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) {
@@ -72,12 +113,24 @@ public boolean equals(Object o) {
return false;
}
ChatRequest chatRequest = (ChatRequest) o;
- return Objects.equals(this.message, chatRequest.message);
+ return Objects.equals(this.message, chatRequest.message) &&
+ equalsNullable(this.threadId, chatRequest.threadId);
+ }
+
+ private static boolean equalsNullable(JsonNullable a, JsonNullable b) {
+ return a == b || (a != null && b != null && a.isPresent() && b.isPresent() && Objects.deepEquals(a.get(), b.get()));
}
@Override
public int hashCode() {
- return Objects.hash(message);
+ return Objects.hash(message, hashCodeNullable(threadId));
+ }
+
+ private static int hashCodeNullable(JsonNullable a) {
+ if (a == null) {
+ return 1;
+ }
+ return a.isPresent() ? Arrays.deepHashCode(new Object[]{a.get()}) : 31;
}
@Override
@@ -85,6 +138,7 @@ public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("class ChatRequest {\n");
sb.append(" message: ").append(toIndentedString(message)).append("\n");
+ sb.append(" threadId: ").append(toIndentedString(threadId)).append("\n");
sb.append("}");
return sb.toString();
}
diff --git a/server/application-server/src/main/java/de/tum/in/www1/hephaestus/intelligenceservice/model/ChatResponse.java b/server/application-server/src/main/java/de/tum/in/www1/hephaestus/intelligenceservice/model/ChatResponse.java
index a601104d..5f615747 100644
--- a/server/application-server/src/main/java/de/tum/in/www1/hephaestus/intelligenceservice/model/ChatResponse.java
+++ b/server/application-server/src/main/java/de/tum/in/www1/hephaestus/intelligenceservice/model/ChatResponse.java
@@ -20,6 +20,10 @@
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.fasterxml.jackson.annotation.JsonValue;
+import org.openapitools.jackson.nullable.JsonNullable;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import org.openapitools.jackson.nullable.JsonNullable;
+import java.util.NoSuchElementException;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.annotation.JsonTypeName;
import org.hibernate.validator.constraints.*;
@@ -28,13 +32,17 @@
* ChatResponse
*/
@JsonPropertyOrder({
- ChatResponse.JSON_PROPERTY_RESPONSE
+ ChatResponse.JSON_PROPERTY_RESPONSE,
+ ChatResponse.JSON_PROPERTY_THREAD_ID
})
@jakarta.annotation.Generated(value = "org.openapitools.codegen.languages.JavaClientCodegen", comments = "Generator version: 7.7.0")
public class ChatResponse {
public static final String JSON_PROPERTY_RESPONSE = "response";
private String response;
+ public static final String JSON_PROPERTY_THREAD_ID = "thread_id";
+ private JsonNullable threadId = JsonNullable.undefined();
+
public ChatResponse() {
}
@@ -63,6 +71,39 @@ public void setResponse(String response) {
this.response = response;
}
+ public ChatResponse threadId(String threadId) {
+ this.threadId = JsonNullable.of(threadId);
+
+ return this;
+ }
+
+ /**
+ * Get threadId
+ * @return threadId
+ */
+ @jakarta.annotation.Nullable
+ @JsonIgnore
+
+ public String getThreadId() {
+ return threadId.orElse(null);
+ }
+
+ @JsonProperty(JSON_PROPERTY_THREAD_ID)
+ @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS)
+
+ public JsonNullable getThreadId_JsonNullable() {
+ return threadId;
+ }
+
+ @JsonProperty(JSON_PROPERTY_THREAD_ID)
+ public void setThreadId_JsonNullable(JsonNullable threadId) {
+ this.threadId = threadId;
+ }
+
+ public void setThreadId(String threadId) {
+ this.threadId = JsonNullable.of(threadId);
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) {
@@ -72,12 +113,24 @@ public boolean equals(Object o) {
return false;
}
ChatResponse chatResponse = (ChatResponse) o;
- return Objects.equals(this.response, chatResponse.response);
+ return Objects.equals(this.response, chatResponse.response) &&
+ equalsNullable(this.threadId, chatResponse.threadId);
+ }
+
+ private static boolean equalsNullable(JsonNullable a, JsonNullable b) {
+ return a == b || (a != null && b != null && a.isPresent() && b.isPresent() && Objects.deepEquals(a.get(), b.get()));
}
@Override
public int hashCode() {
- return Objects.hash(response);
+ return Objects.hash(response, hashCodeNullable(threadId));
+ }
+
+ private static int hashCodeNullable(JsonNullable a) {
+ if (a == null) {
+ return 1;
+ }
+ return a.isPresent() ? Arrays.deepHashCode(new Object[]{a.get()}) : 31;
}
@Override
@@ -85,6 +138,7 @@ public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("class ChatResponse {\n");
sb.append(" response: ").append(toIndentedString(response)).append("\n");
+ sb.append(" threadId: ").append(toIndentedString(threadId)).append("\n");
sb.append("}");
return sb.toString();
}
diff --git a/server/intelligence-service/app/config.py b/server/intelligence-service/app/config.py
index 5383e680..006ea3a5 100644
--- a/server/intelligence-service/app/config.py
+++ b/server/intelligence-service/app/config.py
@@ -19,8 +19,11 @@ def is_openai_available(self):
@property
def is_azure_openai_available(self):
- return bool(self.AZURE_OPENAI_API_KEY) and bool(self.AZURE_OPENAI_ENDPOINT) and bool(
- self.AZURE_OPENAI_API_VERSION)
+ return (
+ bool(self.AZURE_OPENAI_API_KEY)
+ and bool(self.AZURE_OPENAI_ENDPOINT)
+ and bool(self.AZURE_OPENAI_API_VERSION)
+ )
settings = Settings()
diff --git a/server/intelligence-service/app/main.py b/server/intelligence-service/app/main.py
index dd46f0f8..986a4cdb 100644
--- a/server/intelligence-service/app/main.py
+++ b/server/intelligence-service/app/main.py
@@ -1,7 +1,7 @@
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
-from .model import model
-
+from .model import start_chat as start_chat_function, chat as chat_function
+from typing import Dict, Optional
app = FastAPI(
title="Hephaestus Intelligence Service API",
@@ -10,19 +10,43 @@
contact={"name": "Felix T.J. Dietrich", "email": "felixtj.dietrich@tum.de"},
)
+# Global dictionary to store conversation states
+conversations: Dict[str, dict] = {}
+
class ChatRequest(BaseModel):
message: str
+ thread_id: Optional[str] = None
class ChatResponse(BaseModel):
response: str
+ thread_id: Optional[str] = None
-@app.post("/chat", response_model=ChatResponse, summary="Get a response from an LLM to a chat message.")
+@app.post(
+ "/chat",
+ response_model=ChatResponse,
+ summary="Start and continue a chat session with an LLM.",
+)
async def chat(request: ChatRequest):
- try:
- response = model.invoke(request.message)
- return {"response": response.content}
- except Exception as e:
- raise HTTPException(status_code=500, detail=str(e))
+ if request.thread_id is None:
+ # Start a new chat session
+ result = start_chat_function(request.message)
+ thread_id = result["thread_id"]
+ state = result["state"]
+ response_message = result["response"]["messages"][-1].content
+ conversations[thread_id] = state
+ return ChatResponse(thread_id=thread_id, response=response_message)
+ else:
+ thread_id = request.thread_id
+ # Check if the thread_id exists
+ if thread_id not in conversations:
+ raise HTTPException(status_code=404, detail="Thread ID not found")
+ state = conversations[thread_id]
+ user_input = request.message
+ result = chat_function(thread_id, user_input, state)
+ state = result["state"]
+ response_message = result["response"]["messages"][-1].content
+ conversations[thread_id] = state
+ return ChatResponse(response=response_message)
diff --git a/server/intelligence-service/app/model.py b/server/intelligence-service/app/model.py
index ecacc241..59fb8638 100644
--- a/server/intelligence-service/app/model.py
+++ b/server/intelligence-service/app/model.py
@@ -1,15 +1,27 @@
import os
+from .config import settings
+from random import randint
+from typing import Sequence
+from typing_extensions import Annotated, TypedDict
+
+from langchain_core.messages import trim_messages, BaseMessage, HumanMessage
from langchain.chat_models.base import BaseChatModel
from langchain_openai import ChatOpenAI, AzureChatOpenAI
+from langchain_core.prompts import ChatPromptTemplate
+from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
+from langgraph.checkpoint.memory import MemorySaver
+from langgraph.graph import START, StateGraph
+from langgraph.graph.message import add_messages
-from .config import settings
model: BaseChatModel
if os.getenv("GITHUB_ACTIONS") == "true":
- class MockChatModel():
+
+ class MockChatModel:
def invoke(self, message: str):
return "Mock response"
+
model = MockChatModel()
elif settings.is_openai_available:
@@ -18,3 +30,79 @@ def invoke(self, message: str):
model = AzureChatOpenAI()
else:
raise EnvironmentError("No LLM available")
+
+
+class State(TypedDict):
+ messages: Annotated[Sequence[BaseMessage], add_messages]
+ step: int
+
+
+mentor_prompt = ChatPromptTemplate.from_messages(
+ [
+ (
+ "system",
+ "You are an AI mentor helping a students working on the software engineering projects."
+ + "You need to guide the student through the set of three questions regarding their work on the project during the last week (sprint)."
+ + "Steps are the indicator of your current task in the conversation with the student. Your current step is {step}. Just follow the instructions and focus on the current step."
+ + "If your step is 0: greet the student and ask about the overall progress on the project."
+ + "If your step is 1: ask the student about the challenges faced during the sprint reffering to what he saied about progress."
+ + "If your step is 2: ask about the plan for the next sprint."
+ + "If your step is >2: continue the conversation trying to assist the student.",
+ ),
+ MessagesPlaceholder(variable_name="messages"),
+ ]
+)
+
+workflow = StateGraph(state_schema=State)
+trimmer = trim_messages(
+ max_tokens=400,
+ strategy="last",
+ token_counter=model,
+ include_system=True,
+ allow_partial=False,
+ start_on="human",
+)
+
+
+def call_model(state: State):
+ chain = mentor_prompt | model
+ trimmed_messages = trimmer.invoke(state["messages"])
+ response = chain.invoke({"messages": trimmed_messages, "step": state["step"]})
+ return {"messages": [response]}
+
+
+workflow.add_edge(START, "model")
+workflow.add_node("model", call_model)
+
+memory = MemorySaver()
+app = workflow.compile(checkpointer=memory)
+
+
+def start_chat(input_message: str):
+ messages = [HumanMessage(input_message)]
+ state = State(messages=messages, step=0)
+ thread_id = "chat_" + str(randint(0, 9999))
+ config = {"configurable": {"thread_id": thread_id}}
+
+ output = app.invoke(
+ {"messages": state["messages"], "step": state["step"]},
+ config,
+ )
+
+ state["messages"] += output.get("messages", [])
+ return {"thread_id": thread_id, "state": state, "response": output}
+
+
+def chat(thread_id: str, input_message: str, state: State):
+ config = {"configurable": {"thread_id": thread_id}}
+ # append the new human message to the conversation
+ state["messages"] += [HumanMessage(input_message)]
+ state["step"] += 1
+
+ output = app.invoke(
+ {"messages": state["messages"], "step": state["step"]},
+ config,
+ )
+
+ state["messages"] += output.get("messages", [])
+ return {"state": state, "response": output}
diff --git a/server/intelligence-service/openapi.yaml b/server/intelligence-service/openapi.yaml
index d33d3e8d..0f802b7e 100644
--- a/server/intelligence-service/openapi.yaml
+++ b/server/intelligence-service/openapi.yaml
@@ -5,6 +5,11 @@ components:
message:
title: Message
type: string
+ thread_id:
+ anyOf:
+ - type: string
+ - type: 'null'
+ title: Thread Id
required:
- message
title: ChatRequest
@@ -14,6 +19,11 @@ components:
response:
title: Response
type: string
+ thread_id:
+ anyOf:
+ - type: string
+ - type: 'null'
+ title: Thread Id
required:
- response
title: ChatResponse
@@ -79,4 +89,4 @@ paths:
schema:
$ref: '#/components/schemas/HTTPValidationError'
description: Validation Error
- summary: Get a response from an LLM to a chat message.
+ summary: Start and continue a chat session with an LLM.
diff --git a/server/intelligence-service/poetry.lock b/server/intelligence-service/poetry.lock
index 3506f850..9d3e654f 100644
--- a/server/intelligence-service/poetry.lock
+++ b/server/intelligence-service/poetry.lock
@@ -1,4 +1,4 @@
-# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand.
+# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand.
[[package]]
name = "aiohappyeyeballs"
@@ -769,6 +769,17 @@ http2 = ["h2 (>=3,<5)"]
socks = ["socksio (==1.*)"]
zstd = ["zstandard (>=0.18.0)"]
+[[package]]
+name = "httpx-sse"
+version = "0.4.0"
+description = "Consume Server-Sent Event (SSE) messages with HTTPX."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721"},
+ {file = "httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f"},
+]
+
[[package]]
name = "idna"
version = "3.10"
@@ -983,6 +994,53 @@ files = [
[package.dependencies]
langchain-core = ">=0.2.38,<0.3.0"
+[[package]]
+name = "langgraph"
+version = "0.2.44"
+description = "Building stateful, multi-actor applications with LLMs"
+optional = false
+python-versions = "<4.0,>=3.9.0"
+files = [
+ {file = "langgraph-0.2.44-py3-none-any.whl", hash = "sha256:ac03af8950efcb2ebb898bce0800c8c9bad9180f7490a73d2eaecacab7a7920a"},
+ {file = "langgraph-0.2.44.tar.gz", hash = "sha256:8ae179c3a77666b0a78b273d89d8f32a012282ad49298234426a4443d559aa62"},
+]
+
+[package.dependencies]
+langchain-core = ">=0.2.43,<0.3.0 || >0.3.0,<0.3.1 || >0.3.1,<0.3.2 || >0.3.2,<0.3.3 || >0.3.3,<0.3.4 || >0.3.4,<0.3.5 || >0.3.5,<0.3.6 || >0.3.6,<0.3.7 || >0.3.7,<0.3.8 || >0.3.8,<0.3.9 || >0.3.9,<0.3.10 || >0.3.10,<0.3.11 || >0.3.11,<0.3.12 || >0.3.12,<0.3.13 || >0.3.13,<0.3.14 || >0.3.14,<0.4.0"
+langgraph-checkpoint = ">=2.0.0,<3.0.0"
+langgraph-sdk = ">=0.1.32,<0.2.0"
+
+[[package]]
+name = "langgraph-checkpoint"
+version = "2.0.2"
+description = "Library with base interfaces for LangGraph checkpoint savers."
+optional = false
+python-versions = "<4.0.0,>=3.9.0"
+files = [
+ {file = "langgraph_checkpoint-2.0.2-py3-none-any.whl", hash = "sha256:6e5dfd90e1fc71b91ccff75939ada1114e5d7f824df5f24c62d39bed69039ee2"},
+ {file = "langgraph_checkpoint-2.0.2.tar.gz", hash = "sha256:c1d033e4e4855f580fa56830327eb86513b64ab5be527245363498e76b19a0b9"},
+]
+
+[package.dependencies]
+langchain-core = ">=0.2.38,<0.4"
+msgpack = ">=1.1.0,<2.0.0"
+
+[[package]]
+name = "langgraph-sdk"
+version = "0.1.35"
+description = "SDK for interacting with LangGraph API"
+optional = false
+python-versions = "<4.0.0,>=3.9.0"
+files = [
+ {file = "langgraph_sdk-0.1.35-py3-none-any.whl", hash = "sha256:b137c324fbce96afe39cc6a189c61fc042164068f0f6f02ac8de864d8ece6e05"},
+ {file = "langgraph_sdk-0.1.35.tar.gz", hash = "sha256:414cfbc172b883446197763f3645d86bbc6a5b8ce9693c1df3fb6ce7d854a994"},
+]
+
+[package.dependencies]
+httpx = ">=0.25.2"
+httpx-sse = ">=0.4.0"
+orjson = ">=3.10.1"
+
[[package]]
name = "langsmith"
version = "0.1.139"
@@ -1109,6 +1167,79 @@ files = [
{file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"},
]
+[[package]]
+name = "msgpack"
+version = "1.1.0"
+description = "MessagePack serializer"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "msgpack-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7ad442d527a7e358a469faf43fda45aaf4ac3249c8310a82f0ccff9164e5dccd"},
+ {file = "msgpack-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:74bed8f63f8f14d75eec75cf3d04ad581da6b914001b474a5d3cd3372c8cc27d"},
+ {file = "msgpack-1.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:914571a2a5b4e7606997e169f64ce53a8b1e06f2cf2c3a7273aa106236d43dd5"},
+ {file = "msgpack-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c921af52214dcbb75e6bdf6a661b23c3e6417f00c603dd2070bccb5c3ef499f5"},
+ {file = "msgpack-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8ce0b22b890be5d252de90d0e0d119f363012027cf256185fc3d474c44b1b9e"},
+ {file = "msgpack-1.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:73322a6cc57fcee3c0c57c4463d828e9428275fb85a27aa2aa1a92fdc42afd7b"},
+ {file = "msgpack-1.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e1f3c3d21f7cf67bcf2da8e494d30a75e4cf60041d98b3f79875afb5b96f3a3f"},
+ {file = "msgpack-1.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:64fc9068d701233effd61b19efb1485587560b66fe57b3e50d29c5d78e7fef68"},
+ {file = "msgpack-1.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:42f754515e0f683f9c79210a5d1cad631ec3d06cea5172214d2176a42e67e19b"},
+ {file = "msgpack-1.1.0-cp310-cp310-win32.whl", hash = "sha256:3df7e6b05571b3814361e8464f9304c42d2196808e0119f55d0d3e62cd5ea044"},
+ {file = "msgpack-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:685ec345eefc757a7c8af44a3032734a739f8c45d1b0ac45efc5d8977aa4720f"},
+ {file = "msgpack-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3d364a55082fb2a7416f6c63ae383fbd903adb5a6cf78c5b96cc6316dc1cedc7"},
+ {file = "msgpack-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:79ec007767b9b56860e0372085f8504db5d06bd6a327a335449508bbee9648fa"},
+ {file = "msgpack-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6ad622bf7756d5a497d5b6836e7fc3752e2dd6f4c648e24b1803f6048596f701"},
+ {file = "msgpack-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e59bca908d9ca0de3dc8684f21ebf9a690fe47b6be93236eb40b99af28b6ea6"},
+ {file = "msgpack-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e1da8f11a3dd397f0a32c76165cf0c4eb95b31013a94f6ecc0b280c05c91b59"},
+ {file = "msgpack-1.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:452aff037287acb1d70a804ffd022b21fa2bb7c46bee884dbc864cc9024128a0"},
+ {file = "msgpack-1.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8da4bf6d54ceed70e8861f833f83ce0814a2b72102e890cbdfe4b34764cdd66e"},
+ {file = "msgpack-1.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:41c991beebf175faf352fb940bf2af9ad1fb77fd25f38d9142053914947cdbf6"},
+ {file = "msgpack-1.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a52a1f3a5af7ba1c9ace055b659189f6c669cf3657095b50f9602af3a3ba0fe5"},
+ {file = "msgpack-1.1.0-cp311-cp311-win32.whl", hash = "sha256:58638690ebd0a06427c5fe1a227bb6b8b9fdc2bd07701bec13c2335c82131a88"},
+ {file = "msgpack-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fd2906780f25c8ed5d7b323379f6138524ba793428db5d0e9d226d3fa6aa1788"},
+ {file = "msgpack-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d46cf9e3705ea9485687aa4001a76e44748b609d260af21c4ceea7f2212a501d"},
+ {file = "msgpack-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5dbad74103df937e1325cc4bfeaf57713be0b4f15e1c2da43ccdd836393e2ea2"},
+ {file = "msgpack-1.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:58dfc47f8b102da61e8949708b3eafc3504509a5728f8b4ddef84bd9e16ad420"},
+ {file = "msgpack-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4676e5be1b472909b2ee6356ff425ebedf5142427842aa06b4dfd5117d1ca8a2"},
+ {file = "msgpack-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17fb65dd0bec285907f68b15734a993ad3fc94332b5bb21b0435846228de1f39"},
+ {file = "msgpack-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a51abd48c6d8ac89e0cfd4fe177c61481aca2d5e7ba42044fd218cfd8ea9899f"},
+ {file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2137773500afa5494a61b1208619e3871f75f27b03bcfca7b3a7023284140247"},
+ {file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:398b713459fea610861c8a7b62a6fec1882759f308ae0795b5413ff6a160cf3c"},
+ {file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:06f5fd2f6bb2a7914922d935d3b8bb4a7fff3a9a91cfce6d06c13bc42bec975b"},
+ {file = "msgpack-1.1.0-cp312-cp312-win32.whl", hash = "sha256:ad33e8400e4ec17ba782f7b9cf868977d867ed784a1f5f2ab46e7ba53b6e1e1b"},
+ {file = "msgpack-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:115a7af8ee9e8cddc10f87636767857e7e3717b7a2e97379dc2054712693e90f"},
+ {file = "msgpack-1.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:071603e2f0771c45ad9bc65719291c568d4edf120b44eb36324dcb02a13bfddf"},
+ {file = "msgpack-1.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0f92a83b84e7c0749e3f12821949d79485971f087604178026085f60ce109330"},
+ {file = "msgpack-1.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a1964df7b81285d00a84da4e70cb1383f2e665e0f1f2a7027e683956d04b734"},
+ {file = "msgpack-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59caf6a4ed0d164055ccff8fe31eddc0ebc07cf7326a2aaa0dbf7a4001cd823e"},
+ {file = "msgpack-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0907e1a7119b337971a689153665764adc34e89175f9a34793307d9def08e6ca"},
+ {file = "msgpack-1.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:65553c9b6da8166e819a6aa90ad15288599b340f91d18f60b2061f402b9a4915"},
+ {file = "msgpack-1.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7a946a8992941fea80ed4beae6bff74ffd7ee129a90b4dd5cf9c476a30e9708d"},
+ {file = "msgpack-1.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4b51405e36e075193bc051315dbf29168d6141ae2500ba8cd80a522964e31434"},
+ {file = "msgpack-1.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4c01941fd2ff87c2a934ee6055bda4ed353a7846b8d4f341c428109e9fcde8c"},
+ {file = "msgpack-1.1.0-cp313-cp313-win32.whl", hash = "sha256:7c9a35ce2c2573bada929e0b7b3576de647b0defbd25f5139dcdaba0ae35a4cc"},
+ {file = "msgpack-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:bce7d9e614a04d0883af0b3d4d501171fbfca038f12c77fa838d9f198147a23f"},
+ {file = "msgpack-1.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c40ffa9a15d74e05ba1fe2681ea33b9caffd886675412612d93ab17b58ea2fec"},
+ {file = "msgpack-1.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1ba6136e650898082d9d5a5217d5906d1e138024f836ff48691784bbe1adf96"},
+ {file = "msgpack-1.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0856a2b7e8dcb874be44fea031d22e5b3a19121be92a1e098f46068a11b0870"},
+ {file = "msgpack-1.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:471e27a5787a2e3f974ba023f9e265a8c7cfd373632247deb225617e3100a3c7"},
+ {file = "msgpack-1.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:646afc8102935a388ffc3914b336d22d1c2d6209c773f3eb5dd4d6d3b6f8c1cb"},
+ {file = "msgpack-1.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:13599f8829cfbe0158f6456374e9eea9f44eee08076291771d8ae93eda56607f"},
+ {file = "msgpack-1.1.0-cp38-cp38-win32.whl", hash = "sha256:8a84efb768fb968381e525eeeb3d92857e4985aacc39f3c47ffd00eb4509315b"},
+ {file = "msgpack-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:879a7b7b0ad82481c52d3c7eb99bf6f0645dbdec5134a4bddbd16f3506947feb"},
+ {file = "msgpack-1.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:53258eeb7a80fc46f62fd59c876957a2d0e15e6449a9e71842b6d24419d88ca1"},
+ {file = "msgpack-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7e7b853bbc44fb03fbdba34feb4bd414322180135e2cb5164f20ce1c9795ee48"},
+ {file = "msgpack-1.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3e9b4936df53b970513eac1758f3882c88658a220b58dcc1e39606dccaaf01c"},
+ {file = "msgpack-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46c34e99110762a76e3911fc923222472c9d681f1094096ac4102c18319e6468"},
+ {file = "msgpack-1.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a706d1e74dd3dea05cb54580d9bd8b2880e9264856ce5068027eed09680aa74"},
+ {file = "msgpack-1.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:534480ee5690ab3cbed89d4c8971a5c631b69a8c0883ecfea96c19118510c846"},
+ {file = "msgpack-1.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8cf9e8c3a2153934a23ac160cc4cba0ec035f6867c8013cc6077a79823370346"},
+ {file = "msgpack-1.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3180065ec2abbe13a4ad37688b61b99d7f9e012a535b930e0e683ad6bc30155b"},
+ {file = "msgpack-1.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c5a91481a3cc573ac8c0d9aace09345d989dc4a0202b7fcb312c88c26d4e71a8"},
+ {file = "msgpack-1.1.0-cp39-cp39-win32.whl", hash = "sha256:f80bc7d47f76089633763f952e67f8214cb7b3ee6bfa489b3cb6a84cfac114cd"},
+ {file = "msgpack-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:4d1b7ff2d6146e16e8bd665ac726a89c74163ef8cd39fa8c1087d4e52d3a2325"},
+ {file = "msgpack-1.1.0.tar.gz", hash = "sha256:dd432ccc2c72b914e4cb77afce64aab761c1137cc698be3984eee260bcb2896e"},
+]
+
[[package]]
name = "multidict"
version = "6.1.0"
@@ -2529,4 +2660,4 @@ propcache = ">=0.2.0"
[metadata]
lock-version = "2.0"
python-versions = "^3.12"
-content-hash = "4d8bfd77fe7d93844ac01046e3b8699f59ce1184ad9e424ea60662b6b62a4115"
+content-hash = "7f7ca24dc8c94926e9d096d40d4fd244fe8adba33fae95d87b625ecdd40ec6dd"
diff --git a/server/intelligence-service/pyproject.toml b/server/intelligence-service/pyproject.toml
index 6bf2fbaf..e343c4dd 100644
--- a/server/intelligence-service/pyproject.toml
+++ b/server/intelligence-service/pyproject.toml
@@ -13,6 +13,7 @@ fastapi = {extras = ["standard"], version = "0.112.1"}
pydantic-settings = "2.4.0"
langchain = "0.2.15"
langchain-openai = "0.1.23"
+langgraph = "0.2.44"
[tool.poetry.group.dev.dependencies]
black = "24.10.0"