Skip to content

Commit 7ac18af

Browse files
committed
Statnett-255: OBO authentication against Cognite
1 parent d0c7519 commit 7ac18af

File tree

13 files changed

+586
-572
lines changed

13 files changed

+586
-572
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
Next release
22
============
33

4+
* [#255](https://github.com/statnett/Talk2PowerSystem_PM/issues/255): OBO auth flow for Cognite
5+
* [#255](https://github.com/statnett/Talk2PowerSystem_PM/issues/255): Update the version of `cognite-sdk` from `7.86.0` to `7.89.0`
46
* [#251](https://github.com/statnett/Talk2PowerSystem_PM/issues/251): Change N-Shot tool configuration to default to the base GraphDB
57
* [#251](https://github.com/statnett/Talk2PowerSystem_PM/issues/251): Change N-Shot tool configuration SPARQL query template
68
* [#254](https://github.com/statnett/Talk2PowerSystem_PM/issues/254): Update the version of `ttyg` from `1.9.3` to `1.10.0`, so that the chat bot can run without admin access to GraphDB
7-
* [#254](https://github.com/statnett/Talk2PowerSystem_PM/issues/254): Update the version of `cognite-sdk` from `7.86.0` to `7.88.0`
89

910
1.2.0-rc4
1011
============

docs/AgentConfig.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ One of `dev1`, `dev2`, `dev3`, `test`, `prod` according to [CDF access from RNDP
150150
Otherwise, `tools.cognite.token_file_path` must be provided for client credentials authentication.
151151
- `tools.cognite.tenant_id` - REQUIRED iff `tools.cognite.interactive_client_id` is present - Azure tenant ID. For example, `a8d61462-f252-44b2-bf6a-d7231960c041`.
152152
- `tools.cognite.token_file_path` - OPTIONAL - Full path on the disk to the cognite token file. For example, `/var/run/secrets/microsoft.com/entra/cognite`.
153+
* `tools.cognite.client_secret` - OPTIONAL - Client secret for the Cognite confidential application.
153154
154155
## `llm`
155156

poetry.lock

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

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ dependencies = [
1313
"langchain-openai==0.3.32",
1414
"langgraph-checkpoint-redis==0.1.2",
1515
"jsonlines==4.0.0",
16-
"cognite-sdk==7.88.0",
16+
"cognite-sdk==7.89.0",
1717
"pydantic-settings==2.10.1",
1818
"PyYAML==6.0.2",
1919
"uvicorn[standard] (==0.35.0)",
@@ -22,6 +22,7 @@ dependencies = [
2222
"toml==0.10.2",
2323
"markdown==3.8.2",
2424
"python-jose[cryptography] (==3.5.0)",
25+
"msal==1.34.0",
2526
"cachetools==6.2.0",
2627
"importlib_resources==6.5.2",
2728
]

src/jupyter_notebooks/Talk2PowerSystem.ipynb

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -79,12 +79,12 @@
7979
"\n",
8080
"from langgraph.checkpoint.memory import InMemorySaver\n",
8181
"\n",
82-
"from talk2powersystemllm.agent import Talk2PowerSystemAgent\n",
82+
"from talk2powersystemllm.agent import Talk2PowerSystemAgentFactory\n",
8383
"\n",
84-
"agent_executor = Talk2PowerSystemAgent(\n",
84+
"agent = Talk2PowerSystemAgentFactory(\n",
8585
" Path(\"../../config/dev+cognite.yaml\"),\n",
8686
" checkpointer=InMemorySaver()\n",
87-
").agent"
87+
").get_agent()"
8888
]
8989
},
9090
{
@@ -106,12 +106,13 @@
106106
},
107107
"outputs": [],
108108
"source": [
109+
"from langchain_core.runnables import RunnableConfig\n",
109110
"from ttyg.agents import run_agent\n",
110111
"\n",
111112
"\n",
112-
"conf = {\"configurable\": {\"thread_id\": \"thread-123\"}}\n",
113-
"messages = {\"messages\": [(\"user\", \"List all transformers within substation OSLO.\")]}\n",
114-
"last_message_id = run_agent(agent_executor, messages, conf)"
113+
"conf = RunnableConfig(configurable={\"thread_id\": \"thread-123\"})\n",
114+
"messages = {\"messages\": [(\"user\", \"List timeseries\")]}\n",
115+
"last_message_id = run_agent(agent, messages, conf)"
115116
]
116117
},
117118
{
@@ -122,7 +123,7 @@
122123
"outputs": [],
123124
"source": [
124125
"messages = {\"messages\": [(\"user\", \"Give me their descriptions\")]}\n",
125-
"last_message_id = run_agent(agent_executor, messages, conf, last_message_id=last_message_id)"
126+
"last_message_id = run_agent(agent, messages, conf, last_message_id=last_message_id)"
126127
]
127128
},
128129
{
@@ -144,6 +145,7 @@
144145
"source": [
145146
"from datetime import datetime\n",
146147
"\n",
148+
"from langchain_core.runnables import RunnableConfig\n",
147149
"from ttyg.agents import run_agent\n",
148150
"\n",
149151
"from talk2powersystemllm.tools.user_datetime_context import user_datetime_ctx\n",
@@ -180,10 +182,10 @@
180182
"]\n",
181183
"\n",
182184
"for i, question in enumerate(questions):\n",
183-
" conf = {\"configurable\": {\"thread_id\": f\"thread-{i}\"}}\n",
185+
" conf = RunnableConfig(configurable={\"thread_id\": f\"thread-{i}\"})\n",
184186
" messages = {\"messages\": [(\"user\", question)]}\n",
185187
" user_datetime_ctx.set(datetime.now().astimezone().strftime(\"%Y-%m-%dT%H:%M:%S%z\"))\n",
186-
" run_agent(agent_executor, messages, conf)"
188+
" run_agent(agent, messages, conf)"
187189
]
188190
}
189191
],

src/jupyter_notebooks/Talk2PowerSystem_RNDP.ipynb

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -80,12 +80,12 @@
8080
"\n",
8181
"from langgraph.checkpoint.memory import InMemorySaver\n",
8282
"\n",
83-
"from talk2powersystemllm.agent import Talk2PowerSystemAgent\n",
83+
"from talk2powersystemllm.agent import Talk2PowerSystemAgentFactory\n",
8484
"\n",
85-
"agent_executor = Talk2PowerSystemAgent(\n",
85+
"agent = Talk2PowerSystemAgentFactory(\n",
8686
" Path(\"../../config/rndp.yaml\"),\n",
8787
" checkpointer=InMemorySaver()\n",
88-
").agent"
88+
").get_agent()"
8989
]
9090
},
9191
{
@@ -107,12 +107,13 @@
107107
},
108108
"outputs": [],
109109
"source": [
110+
"from langchain_core.runnables import RunnableConfig\n",
110111
"from ttyg.agents import run_agent\n",
111112
"\n",
112113
"\n",
113-
"conf = {\"configurable\": {\"thread_id\": \"thread-123\"}}\n",
114+
"conf = RunnableConfig(configurable={\"thread_id\": \"thread-123\"})\n",
114115
"messages = {\"messages\": [(\"user\", \"List all transformers within substation OSLO.\")]}\n",
115-
"last_message_id = run_agent(agent_executor, messages, conf)"
116+
"last_message_id = run_agent(agent, messages, conf)"
116117
]
117118
},
118119
{
@@ -123,7 +124,7 @@
123124
"outputs": [],
124125
"source": [
125126
"messages = {\"messages\": [(\"user\", \"Give me their descriptions\")]}\n",
126-
"last_message_id = run_agent(agent_executor, messages, conf, last_message_id=last_message_id)"
127+
"last_message_id = run_agent(agent, messages, conf, last_message_id=last_message_id)"
127128
]
128129
},
129130
{
@@ -145,6 +146,7 @@
145146
"source": [
146147
"from datetime import datetime\n",
147148
"\n",
149+
"from langchain_core.runnables import RunnableConfig\n",
148150
"from ttyg.agents import run_agent\n",
149151
"\n",
150152
"from talk2powersystemllm.tools.user_datetime_context import user_datetime_ctx\n",
@@ -181,10 +183,10 @@
181183
"]\n",
182184
"\n",
183185
"for i, question in enumerate(questions):\n",
184-
" conf = {\"configurable\": {\"thread_id\": f\"thread-{i}\"}}\n",
186+
" conf = RunnableConfig(configurable={\"thread_id\": f\"thread-{i}\"})\n",
185187
" messages = {\"messages\": [(\"user\", question)]}\n",
186188
" user_datetime_ctx.set(datetime.now().astimezone().strftime(\"%Y-%m-%dT%H:%M:%S%z\"))\n",
187-
" run_agent(agent_executor, messages, conf)"
189+
" run_agent(agent, messages, conf)"
188190
]
189191
}
190192
],

src/talk2powersystemllm/agent.py

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ class CogniteSettings(BaseModel):
7777
token_file_path: Path | None = None
7878
interactive_client_id: str | None = None
7979
tenant_id: str | None = None
80+
client_secret: str | None = None
8081

8182
@model_validator(mode="after")
8283
def check_credentials(self) -> "CogniteSettings":
@@ -179,14 +180,15 @@ def init_graphdb(graphdb_settings: GraphDBSettings) -> GraphDB:
179180
return GraphDB(**kwargs)
180181

181182

182-
def init_cognite(cognite_settings: CogniteSettings) -> CogniteSession:
183+
def init_cognite(cognite_settings: CogniteSettings, obo_token: str | None = None) -> CogniteSession:
183184
return CogniteSession(
184185
base_url=cognite_settings.base_url,
185186
client_name=cognite_settings.client_name,
186187
project=cognite_settings.project,
187188
token_file_path=cognite_settings.token_file_path,
188189
interactive_client_id=cognite_settings.interactive_client_id,
189190
tenant_id=cognite_settings.tenant_id,
191+
obo_token=obo_token,
190192
)
191193

192194

@@ -211,11 +213,13 @@ def init_llm(llm_settings: LLMSettings) -> BaseChatModel:
211213
)
212214

213215

214-
class Talk2PowerSystemAgent:
215-
agent: CompiledStateGraph
216+
class Talk2PowerSystemAgentFactory:
217+
settings: Talk2PowerSystemAgentSettings
216218
graphdb_client: GraphDB
217-
cognite_session: CogniteSession | None = None
219+
checkpointer: Checkpointer | None = None
218220
model: BaseChatModel
221+
instructions: str
222+
tools: list[BaseTool]
219223

220224
def __init__(
221225
self,
@@ -224,6 +228,7 @@ def __init__(
224228
):
225229
self.settings = read_config(path_to_yaml_config)
226230
self.graphdb_client = init_graphdb(self.settings.graphdb)
231+
self.checkpointer = checkpointer
227232

228233
tools_settings = self.settings.tools
229234
self.tools: list[BaseTool] = []
@@ -233,11 +238,6 @@ def __init__(
233238
)
234239
self.tools.append(sparql_query_tool)
235240

236-
ontology_schema_and_vocabulary_tool = OntologySchemaAndVocabularyTool(
237-
graph=self.graphdb_client,
238-
ontology_schema_file_path=tools_settings.ontology_schema.file_path,
239-
)
240-
241241
autocomplete_search_settings = tools_settings.autocomplete_search
242242
autocomplete_search_kwargs = {
243243
"property_path": autocomplete_search_settings.property_path,
@@ -263,22 +263,28 @@ def __init__(
263263
)
264264
self.tools.append(retrieval_query_tool)
265265

266-
if tools_settings.cognite:
267-
cognite_settings = tools_settings.cognite
268-
self.cognite_session = init_cognite(cognite_settings)
269-
self.tools.append(RetrieveTimeSeriesTool(cognite_session=self.cognite_session))
270-
self.tools.append(RetrieveDataPointsTool(cognite_session=self.cognite_session))
271-
272266
self.tools.append(NowTool())
273267

274-
instructions = f"""{self.settings.prompts.assistant_instructions}""".replace(
268+
ontology_schema_and_vocabulary_tool = OntologySchemaAndVocabularyTool(
269+
graph=self.graphdb_client,
270+
ontology_schema_file_path=tools_settings.ontology_schema.file_path,
271+
)
272+
self.instructions = f"""{self.settings.prompts.assistant_instructions}""".replace(
275273
"{ontology_schema}", ontology_schema_and_vocabulary_tool.schema_graph.serialize(format="turtle")
276274
)
277275

278276
self.model = init_llm(self.settings.llm)
279-
self.agent = create_react_agent(
277+
278+
def get_agent(self, cognite_obo_token: str | None = None) -> CompiledStateGraph:
279+
tools = self.tools.copy()
280+
if self.settings.tools.cognite:
281+
cognite_settings = self.settings.tools.cognite
282+
cognite_session = init_cognite(cognite_settings, cognite_obo_token)
283+
tools.append(RetrieveTimeSeriesTool(cognite_session=cognite_session))
284+
tools.append(RetrieveDataPointsTool(cognite_session=cognite_session))
285+
return create_react_agent(
280286
model=self.model,
281-
tools=self.tools,
282-
prompt=instructions,
283-
checkpointer=checkpointer,
287+
tools=tools,
288+
prompt=self.instructions,
289+
checkpointer=self.checkpointer,
284290
)

src/talk2powersystemllm/app/server/config.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ def check_required_fields_and_set_default_oidc_discovery_url(self) -> "SecurityS
5858
"client_id, authority, logout, login_redirect, logout_redirect, audience!")
5959
if self.enabled and not self.oidc_discovery_url:
6060
self.oidc_discovery_url = (
61-
self.authority.rstrip("/") + "/v2.0/.well-known/openid-configuration"
61+
self.authority.rstrip("/") + "/v2.0/.well-known/openid-configuration"
6262
)
6363
return self
6464

@@ -96,7 +96,7 @@ class AppSettings(BaseSettings):
9696
gtg_refresh_interval: int | None = Field(default=30, ge=1,
9797
description="The __gtg endpoint refresh interval in seconds")
9898
about_refresh_interval: int | None = Field(default=30, ge=1,
99-
description="The __about endpoint refresh interval in seconds")
99+
description="The __about endpoint refresh interval in seconds")
100100
trouble_md_path: Path | None = "/code/trouble.md"
101101
docs_url: str | None = "/docs"
102102
root_path: str | None = "/"
Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
from .cognite_healthcheck import CogniteHealthchecker
21
from .graphdb_healthcheck import GraphDBHealthchecker
32
from .healthchecks import HealthChecks
43

54
__all__ = [
65
"HealthChecks",
76
"GraphDBHealthchecker",
8-
"CogniteHealthchecker",
97
]

src/talk2powersystemllm/app/server/healthchecks/cognite_healthcheck.py

Lines changed: 0 additions & 35 deletions
This file was deleted.

0 commit comments

Comments
 (0)