alita-sdk 0.3.115__py3-none-any.whl → 0.3.117__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -90,10 +90,8 @@ class AnalyseJira(BaseToolkit):
90
90
  jira_username = kwargs.get('jira_username')
91
91
  jira_token = kwargs.get('jira_token')
92
92
  jira_api_key = kwargs.get('jira_api_key')
93
- try:
94
- jira_custom_fields = json.loads(kwargs.get('jira_custom_fields', '{}'))
95
- except:
96
- jira_custom_fields = {}
93
+
94
+ jira_custom_fields = kwargs.get('jira_custom_fields', {})
97
95
  jira_custom_fields['team'] = kwargs.get('team_field', '')
98
96
  jira_custom_fields['environment'] = kwargs.get('environment_field', '')
99
97
  closed_status = kwargs.get('closed_status', '')
@@ -99,8 +99,6 @@ class JiraAnalyseWrapper(BaseToolApiWrapper):
99
99
  ):
100
100
  """
101
101
  Extract Jira issues for the specified projects.
102
- projects: str
103
- one or more projects keys separated with comma
104
102
  closed_issues_based_on: int
105
103
  define whether issues can be thought as closed based on their status (1) or not empty resolved date (2)
106
104
  resolved_after: str
@@ -111,8 +109,9 @@ class JiraAnalyseWrapper(BaseToolApiWrapper):
111
109
  created after date (i.e. 2023-01-01)
112
110
  add_filter: str
113
111
  additional filter for Jira issues in JQL format like "customfield_10000 = 'value' AND customfield_10001 = 'value'"
112
+ project_keys: str
113
+ one or more projects keys separated with comma
114
114
  """
115
-
116
115
  if not (
117
116
  (
118
117
  closed_issues_based_on == 1
@@ -123,15 +122,21 @@ class JiraAnalyseWrapper(BaseToolApiWrapper):
123
122
  return (
124
123
  "ERROR: Check input parameters closed_issues_based_on and closed_status"
125
124
  )
125
+
126
+ project_keys = project_keys or self.project_keys
126
127
 
127
128
  dispatch_custom_event(
128
- name="jira_issues_extraction_start",
129
+ name="thinking_step",
129
130
  data={
130
- "closed_issues_based_on": closed_issues_based_on,
131
- "closed_status": self.closed_status,
132
- }
131
+ "message": f"I am extracting Jira issues with initial parameters:\
132
+ project keys: {project_keys}, closed status: {self.closed_status},\
133
+ defects name: {self.defects_name}, custom fields: {self.custom_fields}, \
134
+ closed status based on: {closed_issues_based_on}, resolved after: {resolved_after}, \
135
+ updated after: {updated_after}, created after: {created_after}, additional filter:{add_filter}",
136
+ "tool_name": "jira_issues_extraction_start",
137
+ "toolkit": "analyse_jira",
138
+ },
133
139
  )
134
- project_keys = project_keys or self.project_keys
135
140
 
136
141
  jira_issues = JiraIssues(
137
142
  self.jira,
@@ -144,12 +149,15 @@ class JiraAnalyseWrapper(BaseToolApiWrapper):
144
149
  df_issues, df_map = jira_issues.extract_issues_from_jira_and_transform(
145
150
  self.custom_fields, (resolved_after, updated_after, created_after)
146
151
  )
152
+
147
153
  dispatch_custom_event(
148
- name="jira_issues_extracted",
154
+ name="thinking_step",
149
155
  data={
150
- "project_keys": jira_issues.projects,
151
- "issue_count": len(df_issues),
152
- "map_rows": len(df_map),
156
+ "message": f"I am saving the extracted Jira issues to the artifact repository. \
157
+ issues count: {len(df_issues)}, mapping rows: {len(df_map)}, \
158
+ output file: {OUTPUT_MAPPING_FILE}{jira_issues.projects}.csv",
159
+ "tool_name": "get_jira_issues",
160
+ "toolkit": "analyse_jira",
153
161
  },
154
162
  )
155
163
  self.save_dataframe(
@@ -157,13 +165,6 @@ class JiraAnalyseWrapper(BaseToolApiWrapper):
157
165
  f"{OUTPUT_MAPPING_FILE}{jira_issues.projects}.csv",
158
166
  csv_options={"index_label": "id"},
159
167
  )
160
- dispatch_custom_event(
161
- name="jira_map_statuces_saved",
162
- data={
163
- "output_file": f"{OUTPUT_MAPPING_FILE}{jira_issues.projects}.csv",
164
- "row_count": len(df_map),
165
- }
166
- )
167
168
 
168
169
  if not df_issues.empty:
169
170
  self.save_dataframe(
@@ -172,11 +173,14 @@ class JiraAnalyseWrapper(BaseToolApiWrapper):
172
173
  csv_options={"index_label": "id"},
173
174
  )
174
175
  dispatch_custom_event(
175
- name="jira_issues_saved",
176
+ name="thinking_step",
176
177
  data={
177
- "output_file": f"{OUTPUT_WORK_ITEMS_FILE}{jira_issues.projects}.csv",
178
- "row_count": len(df_issues),
179
- }
178
+ "message": f"Saving Jira issues to the file . \
179
+ output file: {OUTPUT_WORK_ITEMS_FILE}{jira_issues.projects}.csv,\
180
+ row count: {len(df_issues)}",
181
+ "tool_name": "get_jira_issues",
182
+ "toolkit": "analyse_jira",
183
+ },
180
184
  )
181
185
 
182
186
  return f"{jira_issues.projects} Data has been extracted successfully."
@@ -26,6 +26,7 @@ from ..tools.loop_output import LoopToolNode
26
26
  from ..tools.tool import ToolNode
27
27
  from ..utils.evaluate import EvaluateTemplate
28
28
  from ..utils.utils import clean_string, TOOLKIT_SPLITTER
29
+ from ..tools.router import RouterNode
29
30
 
30
31
  logger = logging.getLogger(__name__)
31
32
 
@@ -124,6 +125,58 @@ class TransitionalEdge(Runnable):
124
125
  return self.next_step if self.next_step != 'END' else END
125
126
 
126
127
 
128
+ class StateModifierNode(Runnable):
129
+ name = "StateModifierNode"
130
+
131
+ def __init__(self, template: str, variables_to_clean: Optional[list[str]] = None,
132
+ input_variables: Optional[list[str]] = None,
133
+ output_variables: Optional[list[str]] = None):
134
+ self.template = template
135
+ self.variables_to_clean = variables_to_clean or []
136
+ self.input_variables = input_variables or ["messages"]
137
+ self.output_variables = output_variables or []
138
+
139
+ def invoke(self, state: Annotated[BaseStore, InjectedStore()], config: Optional[RunnableConfig] = None) -> dict:
140
+ logger.info(f"Modifying state with template: {self.template}")
141
+
142
+ # Collect input variables from state
143
+ input_data = {}
144
+ for var in self.input_variables:
145
+ if var in state:
146
+ input_data[var] = state.get(var)
147
+
148
+ # Render the template using Jinja
149
+ from jinja2 import Template
150
+ rendered_message = Template(self.template).render(**input_data)
151
+ result = {}
152
+ # Store the rendered message in the state or messages
153
+ if len(self.output_variables) > 0:
154
+ # Use the first output variable to store the rendered content
155
+ output_var = self.output_variables[0]
156
+ result[output_var] = rendered_message
157
+
158
+ # Clean up specified variables (make them empty, not delete)
159
+
160
+ for var in self.variables_to_clean:
161
+ if var in state:
162
+ # Empty the variable based on its type
163
+ if isinstance(state[var], list):
164
+ result[var] = []
165
+ elif isinstance(state[var], dict):
166
+ result[var] = {}
167
+ elif isinstance(state[var], str):
168
+ result[var] = ""
169
+ elif isinstance(state[var], (int, float)):
170
+ result[var] = 0
171
+ elif state[var] is None:
172
+ pass
173
+ else:
174
+ # For other types, set to None
175
+ result[var] = None
176
+ logger.info(f"State modifier result: {result}")
177
+ return result
178
+
179
+
127
180
  def prepare_output_schema(lg_builder, memory, store, debug=False, interrupt_before=[], interrupt_after=[]):
128
181
  # prepare output channels
129
182
  output_channels = (
@@ -283,6 +336,32 @@ def create_graph(
283
336
  output_variables=node.get('output', []),
284
337
  input_variables=node.get('input', ['messages']),
285
338
  structured_output=node.get('structured_output', False)))
339
+ elif node_type == 'router':
340
+ # Add a RouterNode as an independent node
341
+ lg_builder.add_node(node_id, RouterNode(
342
+ name=node['id'],
343
+ condition=node.get('condition', ''),
344
+ routes=node.get('routes', []),
345
+ default_output=node.get('default_output', 'END'),
346
+ input_variables=node.get('input', ['messages'])
347
+ ))
348
+ # Add a single conditional edge for all routes
349
+ lg_builder.add_conditional_edges(
350
+ node_id,
351
+ ConditionalEdge(
352
+ condition="{{router_output}}", # router node returns the route key in 'router_output'
353
+ condition_inputs=["router_output"],
354
+ conditional_outputs=node.get('routes', []),
355
+ default_output=node.get('default_output', 'END')
356
+ )
357
+ )
358
+ elif node_type == 'state_modifier':
359
+ lg_builder.add_node(node_id, StateModifierNode(
360
+ template=node.get('template', ''),
361
+ variables_to_clean=node.get('variables_to_clean', []),
362
+ input_variables=node.get('input', ['messages']),
363
+ output_variables=node.get('output', [])
364
+ ))
286
365
  if node.get('transition'):
287
366
  next_step = clean_string(node['transition'])
288
367
  logger.info(f'Adding transition: {next_step}')
@@ -294,7 +373,7 @@ def create_graph(
294
373
  node['decision'].get('description', ""),
295
374
  decisional_inputs=node['decision'].get('decisional_inputs', ['messages']),
296
375
  default_output=node['decision'].get('default_output', 'END')))
297
- elif node.get('condition'):
376
+ elif node.get('condition') and node_type != 'router':
298
377
  logger.info(f'Adding condition: {node["condition"]}')
299
378
  condition_input = node['condition'].get('condition_input', ['messages'])
300
379
  condition_definition = node['condition'].get('condition_definition', '')
@@ -128,7 +128,7 @@ def parse_type(type_str):
128
128
 
129
129
 
130
130
  def create_state(data: Optional[dict] = None):
131
- state_dict = {'input': str,}
131
+ state_dict = {'input': str, 'router_output': str} # Always include router_output
132
132
  if not data:
133
133
  data = {'messages': 'list[str]'}
134
134
  for key, value in data.items():
@@ -0,0 +1,35 @@
1
+ import logging
2
+ from typing import Any, Optional, Union, List
3
+ from langchain_core.runnables import RunnableConfig
4
+ from langchain_core.tools import BaseTool
5
+ from ..utils.evaluate import EvaluateTemplate
6
+ from ..utils.utils import clean_string
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+ class RouterNode(BaseTool):
11
+ name: str = 'RouterNode'
12
+ description: str = 'A router node that evaluates a condition and routes accordingly.'
13
+ condition: str = ''
14
+ routes: List[str] = [] # List of possible output node keys
15
+ default_output: str = 'END'
16
+ input_variables: Optional[list[str]] = None
17
+
18
+ def invoke(self, state: Union[str, dict], config: Optional[RunnableConfig] = None, **kwargs: Any) -> dict:
19
+ input_data = {}
20
+ for field in self.input_variables or []:
21
+ input_data[field] = state.get(field, "")
22
+ template = EvaluateTemplate(self.condition, input_data)
23
+ result = template.evaluate()
24
+ logger.info(f"RouterNode evaluated condition '{self.condition}' with input {input_data} => {result}")
25
+ result = clean_string(str(result))
26
+ if result in self.routes:
27
+ # If the result is one of the routes, return it
28
+ return {"router_output": result}
29
+ elif result == self.default_output:
30
+ # If the result is the default output, return it
31
+ return {"router_output": clean_string(self.default_output)}
32
+ return {"router_output": 'END'}
33
+
34
+ def _run(self, *args, **kwargs):
35
+ return self.invoke(**kwargs)
@@ -1,6 +1,7 @@
1
1
  import logging
2
2
  import json
3
3
  import traceback
4
+ from datetime import datetime, timezone
4
5
  from uuid import UUID, uuid4
5
6
  from typing import Any, Dict, List, Optional
6
7
  from collections import defaultdict
@@ -51,6 +52,35 @@ class AlitaStreamlitCallback(BaseCallbackHandler):
51
52
  # Tool
52
53
  #
53
54
 
55
+ def on_custom_event(
56
+ self,
57
+ name: str,
58
+ data: Any,
59
+ *,
60
+ run_id: UUID,
61
+ tags: Optional[List[str]] = None,
62
+ metadata: Optional[Dict[str, Any]] = None,
63
+ **kwargs: Any,
64
+ ) -> None:
65
+ """Callback containing a group of custom events"""
66
+
67
+ payload = {
68
+ "name": name,
69
+ "run_id": str(run_id),
70
+ "tool_run_id": str(run_id), # compatibility
71
+ "metadata": metadata,
72
+ "datetime": str(datetime.now(tz=timezone.utc)),
73
+ **data,
74
+ }
75
+ payload = json.loads(
76
+ json.dumps(payload, ensure_ascii=False, default=lambda o: str(o))
77
+ )
78
+
79
+ self.callback_state[str(run_id)] = self.st.status(
80
+ f"Running {payload.get("tool_name")}...", expanded=True
81
+ )
82
+ self.callback_state[str(run_id)].write(f"Tool inputs: {payload}")
83
+
54
84
  def on_tool_start(self, *args, run_id: UUID, **kwargs):
55
85
  """ Callback """
56
86
  if self.debug:
@@ -125,7 +155,7 @@ class AlitaStreamlitCallback(BaseCallbackHandler):
125
155
 
126
156
  self.current_model_name = metadata.get('ls_model_name', self.current_model_name)
127
157
  llm_run_id = str(run_id)
128
-
158
+
129
159
  self.callback_state[llm_run_id] = self.st.status(f"Running LLM ...", expanded=True)
130
160
  self.callback_state[llm_run_id].write(f"LLM inputs: {messages}")
131
161
 
@@ -176,4 +206,4 @@ class AlitaStreamlitCallback(BaseCallbackHandler):
176
206
  log.debug("on_llm_end(%s, %s)", response, kwargs)
177
207
  llm_run_id = str(run_id)
178
208
  self.callback_state[llm_run_id].update(label=f"Completed LLM call", state="complete", expanded=False)
179
- self.callback_state.pop(llm_run_id, None)
209
+ self.callback_state.pop(llm_run_id, None)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: alita_sdk
3
- Version: 0.3.115
3
+ Version: 0.3.117
4
4
  Summary: SDK for building langchain agents using resouces from Alita
5
5
  Author-email: Artem Rozumenko <artyom.rozumenko@gmail.com>, Mikalai Biazruchka <mikalai_biazruchka@epam.com>, Roman Mitusov <roman_mitusov@epam.com>, Ivan Krakhmaliuk <lifedjik@gmail.com>
6
6
  Project-URL: Homepage, https://projectalita.ai
@@ -10,8 +10,8 @@ alita_sdk/clients/prompt.py,sha256=li1RG9eBwgNK_Qf0qUaZ8QNTmsncFrAL2pv3kbxZRZg,1
10
10
  alita_sdk/community/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
11
  alita_sdk/community/utils.py,sha256=lvuCJaNqVPHOORJV6kIPcXJcdprVW_TJvERtYAEgpjM,249
12
12
  alita_sdk/community/analysis/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
- alita_sdk/community/analysis/jira_analyse/__init__.py,sha256=bimXRFxQ4mUO3X-_9IPulLJe8k8G2JTZPf8e2zmB1dg,6020
14
- alita_sdk/community/analysis/jira_analyse/api_wrapper.py,sha256=ISLmKcawStahxgsf0I2Nav6yIq19K_L-gkBM_XJ8NTY,8691
13
+ alita_sdk/community/analysis/jira_analyse/__init__.py,sha256=Rm-HKEi_HIxrgHdq9mZ-XzxMKLXm8-81eJwJT2lar-c,5945
14
+ alita_sdk/community/analysis/jira_analyse/api_wrapper.py,sha256=FPYAOh2SV5ggWg0ywDdkyt6udyR3juQvZM7lOIwvcKU,9327
15
15
  alita_sdk/community/browseruse/__init__.py,sha256=uAxPZEX7ihpt8HtcGDFrzTNv9WcklT1wG1ItTwUO8y4,3601
16
16
  alita_sdk/community/browseruse/api_wrapper.py,sha256=Y05NKWfTROPmBxe8ZFIELSGBX5v3RTNP30OTO2Tj8uI,10838
17
17
  alita_sdk/langchain/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -19,10 +19,10 @@ alita_sdk/langchain/assistant.py,sha256=V0MpZG9IQRlKMr1SfAabVbUsIp-gNPO9A1SWxuFi
19
19
  alita_sdk/langchain/chat_message_template.py,sha256=kPz8W2BG6IMyITFDA5oeb5BxVRkHEVZhuiGl4MBZKdc,2176
20
20
  alita_sdk/langchain/constants.py,sha256=eHVJ_beJNTf1WJo4yq7KMK64fxsRvs3lKc34QCXSbpk,3319
21
21
  alita_sdk/langchain/indexer.py,sha256=0ENHy5EOhThnAiYFc7QAsaTNp9rr8hDV_hTK8ahbatk,37592
22
- alita_sdk/langchain/langraph_agent.py,sha256=DJyItdnscqceupHbdNnEENnfbinAMi7KuKvO7saix2M,17044
22
+ alita_sdk/langchain/langraph_agent.py,sha256=vxsbjOE0UWZIuyXNGEMR1QJ8t5yHS7Kzjt8a8nLb-jw,20626
23
23
  alita_sdk/langchain/mixedAgentParser.py,sha256=M256lvtsL3YtYflBCEp-rWKrKtcY1dJIyRGVv7KW9ME,2611
24
24
  alita_sdk/langchain/mixedAgentRenderes.py,sha256=asBtKqm88QhZRILditjYICwFVKF5KfO38hu2O-WrSWE,5964
25
- alita_sdk/langchain/utils.py,sha256=JPfl96bUwp8asXzP11Fl8QLvcU3TUtGgybXTSxZGWho,6578
25
+ alita_sdk/langchain/utils.py,sha256=Npferkn10dvdksnKzLJLBI5bNGQyVWTBwqp3vQtUqmY,6631
26
26
  alita_sdk/langchain/agents/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
27
27
  alita_sdk/langchain/agents/xml_chat.py,sha256=Mx7PK5T97_GrFCwHHZ3JZP42S7MwtUzV0W-_8j6Amt8,6212
28
28
  alita_sdk/langchain/document_loaders/AlitaBDDScenariosLoader.py,sha256=4kFU1ijrM1Jw7cywQv8mUiBHlE6w-uqfzSZP4hUV5P4,3771
@@ -82,17 +82,18 @@ alita_sdk/tools/loop.py,sha256=uds0WhZvwMxDVFI6MZHrcmMle637cQfBNg682iLxoJA,8335
82
82
  alita_sdk/tools/loop_output.py,sha256=NoGIGYc42wY3NNcWRijYzRnUVXcCn5cRVd8QmuIpoHU,8068
83
83
  alita_sdk/tools/pgvector_search.py,sha256=NN2BGAnq4SsDHIhUcFZ8d_dbEOM8QwB0UwpsWCYruXU,11692
84
84
  alita_sdk/tools/prompt.py,sha256=nJafb_e5aOM1Rr3qGFCR-SKziU9uCsiP2okIMs9PppM,741
85
+ alita_sdk/tools/router.py,sha256=wCvZjVkdXK9dMMeEerrgKf5M790RudH68pDortnHSz0,1517
85
86
  alita_sdk/tools/tool.py,sha256=jFRq8BeC55NwpgdpsqGk_Y3tZL4YKN0rE7sVS5OE3yg,5092
86
87
  alita_sdk/tools/vectorstore.py,sha256=F-DoHxPa4UVsKB-FEd-wWa59QGQifKMwcSNcZ5WZOKc,23496
87
- alita_sdk/utils/AlitaCallback.py,sha256=nRdLEY0icMFQJcQ0Daiz99UqFKhqYNBFPp2B0tuKjOE,6429
88
+ alita_sdk/utils/AlitaCallback.py,sha256=cvpDhR4QLVCNQci6CO6TEUrUVDZU9_CRSwzcHGm3SGw,7356
88
89
  alita_sdk/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
89
90
  alita_sdk/utils/evaluate.py,sha256=iM1P8gzBLHTuSCe85_Ng_h30m52hFuGuhNXJ7kB1tgI,1872
90
91
  alita_sdk/utils/streamlit.py,sha256=zp8owZwHI3HZplhcExJf6R3-APtWx-z6s5jznT2hY_k,29124
91
92
  alita_sdk/utils/utils.py,sha256=dM8whOJAuFJFe19qJ69-FLzrUp6d2G-G6L7d4ss2XqM,346
92
- alita_sdk-0.3.115.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
93
+ alita_sdk-0.3.117.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
93
94
  tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
94
95
  tests/test_jira_analysis.py,sha256=I0cErH5R_dHVyutpXrM1QEo7jfBuKWTmDQvJBPjx18I,3281
95
- alita_sdk-0.3.115.dist-info/METADATA,sha256=qJne58yRgLKhXd6WbxVWqMoorDE2ptNA7PY_CH9kuXA,7075
96
- alita_sdk-0.3.115.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
97
- alita_sdk-0.3.115.dist-info/top_level.txt,sha256=SWRhxB7Et3cOy3RkE5hR7OIRnHoo3K8EXzoiNlkfOmc,25
98
- alita_sdk-0.3.115.dist-info/RECORD,,
96
+ alita_sdk-0.3.117.dist-info/METADATA,sha256=UKs2mCU1AvQTt5jMaeRngvrGk8z6WwY4DX6aNBmaEzo,7075
97
+ alita_sdk-0.3.117.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
98
+ alita_sdk-0.3.117.dist-info/top_level.txt,sha256=SWRhxB7Et3cOy3RkE5hR7OIRnHoo3K8EXzoiNlkfOmc,25
99
+ alita_sdk-0.3.117.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.7.1)
2
+ Generator: setuptools (80.8.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5