Skip to content

Commit 9978a5d

Browse files
authored
Merge pull request #40 from meta-d/develop
Develop
2 parents ad053f0 + f740b69 commit 9978a5d

File tree

60 files changed

+523
-186
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+523
-186
lines changed

.deploy/api/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "ocap-server",
33
"author": "Metad",
4-
"version": "2.4.6",
4+
"version": "2.4.7",
55
"scripts": {
66
"start": "nx serve",
77
"build": "nx build",

.deploy/webapp/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "ocap",
3-
"version": "2.4.6",
3+
"version": "2.4.7",
44
"description": "",
55
"license": "AGPL-3.0",
66
"homepage": "https://mtda.cloud",

apps/cloud/src/app/features/project/copilot/architect/command.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@ import { inject } from '@angular/core'
22
import { CopilotAgentType } from '@metad/copilot'
33
import { injectCopilotCommand } from '@metad/copilot-angular'
44
import { TranslateService } from '@ngx-translate/core'
5+
import { injectAgentFewShotTemplate } from 'apps/cloud/src/app/@core/copilot'
56
import { NGXLogger } from 'ngx-logger'
67
import { injectCreateIndicatorArchitect } from './graph'
7-
import { INDICATOR_AGENT_NAME, PLANNER_NAME, REPLANNER_NAME } from './types'
8+
import { INDICATOR_AGENT_NAME, IndicatorArchitectCommandName, PLANNER_NAME, REPLANNER_NAME } from './types'
89

910
export function injectIndicatorArchitectCommand() {
1011
const logger = inject(NGXLogger)
1112
const translate = inject(TranslateService)
13+
14+
const fewShotPrompt = injectAgentFewShotTemplate(IndicatorArchitectCommandName, { k: 1, vectorStore: null })
1215
const createIndicatorGraph = injectCreateIndicatorArchitect()
1316

1417
// const indicators = computed(() => projectService.indicators() ?? [])
@@ -39,8 +42,9 @@ export function injectIndicatorArchitectCommand() {
3942
agent: {
4043
type: CopilotAgentType.Graph,
4144
conversation: true,
42-
interruptAfter: [PLANNER_NAME, REPLANNER_NAME, INDICATOR_AGENT_NAME]
45+
interruptAfter: [ INDICATOR_AGENT_NAME ]
4346
},
47+
fewShotPrompt,
4448
createGraph: createIndicatorGraph
4549
})
4650
}
Lines changed: 86 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,115 @@
1-
import { computed, inject } from '@angular/core'
2-
import { END, START, StateGraph, StateGraphArgs } from '@langchain/langgraph/web'
3-
import { CreateGraphOptions } from '@metad/copilot'
4-
import { Plan, injectAgentFewShotTemplate } from '../../../../@core/copilot/'
1+
import { computed, inject, Signal } from '@angular/core'
2+
import { ToolMessage } from '@langchain/core/messages'
3+
import { RunnableLambda } from '@langchain/core/runnables'
4+
import { DynamicStructuredTool } from '@langchain/core/tools'
5+
import { ToolNode } from '@langchain/langgraph/prebuilt'
6+
import { START, StateGraph, StateGraphArgs } from '@langchain/langgraph/web'
7+
import { ChatOpenAI } from '@langchain/openai'
8+
import { Indicator } from '@metad/cloud/state'
9+
import { CreateGraphOptions, Team } from '@metad/copilot'
10+
import { injectDimensionMemberTool } from '@metad/core'
511
import { ProjectService } from '../../project.service'
612
import { injectRunIndicatorAgent } from '../indicator/graph'
7-
import { createPlannerAgent } from './agent-planner'
8-
import { createReplannerAgent } from './agent-replanner'
9-
import {
10-
INDICATOR_AGENT_NAME,
11-
IndicatorArchitectCommandName,
12-
IndicatorArchitectState,
13-
PLANNER_NAME,
14-
REPLANNER_NAME
15-
} from './types'
16-
17-
const superState: StateGraphArgs<IndicatorArchitectState>['channels'] = Plan.createState()
13+
import { promptIndicatorCode } from '../prompt'
14+
import { INDICATOR_AGENT_NAME, IndicatorArchitectState, markdownIndicators } from './types'
15+
16+
const superState: StateGraphArgs<IndicatorArchitectState>['channels'] = Team.createState()
1817

1918
export function injectCreateIndicatorArchitect() {
20-
const fewShotTemplate = injectAgentFewShotTemplate(IndicatorArchitectCommandName, { k: 1, vectorStore: null })
2119
const projectService = inject(ProjectService)
20+
const memberRetrieverTool = injectDimensionMemberTool()
2221
const createIndicatorGraph = injectRunIndicatorAgent()
2322

2423
const indicators = computed(() => projectService.indicators() ?? [])
2524

26-
return async ({ llm, checkpointer, interruptBefore, interruptAfter}: CreateGraphOptions) => {
25+
return async ({ llm, checkpointer, interruptBefore, interruptAfter }: CreateGraphOptions) => {
2726
const indicatorWorker = await createIndicatorGraph({ llm })
2827

29-
const planner = await createPlannerAgent({ llm, fewShotTemplate, indicators })
30-
const replanner = await createReplannerAgent({ llm })
28+
// const planner = await createPlannerAgent({ llm, fewShotTemplate, indicators })
29+
// const replanner = await createReplannerAgent({ llm })
30+
const tools = [memberRetrieverTool]
31+
const supervisorAgent = await createSupervisorAgent({ llm, indicators, tools: [] })
3132

3233
async function executeStep(state: IndicatorArchitectState): Promise<any> {
33-
const task = state.plan[0]
34-
3534
const { messages } = await indicatorWorker.invoke({
3635
messages: [],
3736
role: state.role,
3837
context: state.context,
39-
input: task
38+
input: state.instructions,
4039
})
4140

4241
return {
43-
pastSteps: [[task, messages[messages.length - 1].content.toString()]],
44-
plan: state.plan.slice(1),
45-
messages: [messages[messages.length - 1]]
42+
tool_call_id: null,
43+
messages: [
44+
new ToolMessage({
45+
tool_call_id: state.tool_call_id,
46+
content: messages[messages.length - 1].content
47+
})
48+
]
4649
}
4750
}
4851

49-
function shouldEnd(state: IndicatorArchitectState) {
50-
return state.response ? 'true' : 'false'
51-
}
52-
5352
const superGraph = new StateGraph({ channels: superState })
5453
// Add steps nodes
55-
.addNode(PLANNER_NAME, planner)
54+
.addNode(Team.SUPERVISOR_NAME, supervisorAgent.withConfig({ runName: Team.SUPERVISOR_NAME }))
55+
.addNode(Team.TOOLS_NAME, new ToolNode<IndicatorArchitectState>(tools))
5656
.addNode(INDICATOR_AGENT_NAME, executeStep)
57-
.addNode(REPLANNER_NAME, replanner)
58-
.addEdge(START, PLANNER_NAME)
59-
.addEdge(PLANNER_NAME, INDICATOR_AGENT_NAME)
60-
.addEdge(INDICATOR_AGENT_NAME, REPLANNER_NAME)
61-
.addConditionalEdges(REPLANNER_NAME, shouldEnd, {
62-
true: END,
63-
false: INDICATOR_AGENT_NAME
64-
})
57+
.addEdge(START, Team.SUPERVISOR_NAME)
58+
.addConditionalEdges(Team.SUPERVISOR_NAME, Team.supervisorRouter)
59+
.addEdge(INDICATOR_AGENT_NAME, Team.SUPERVISOR_NAME)
60+
.addEdge(Team.TOOLS_NAME, Team.SUPERVISOR_NAME)
6561

66-
return superGraph.compile({ checkpointer, interruptBefore, interruptAfter})
62+
return superGraph.compile({ checkpointer, interruptBefore, interruptAfter })
6763
}
6864
}
65+
66+
export async function createSupervisorAgent({
67+
llm,
68+
indicators,
69+
tools
70+
}: {
71+
llm: ChatOpenAI
72+
indicators: Signal<Indicator[]>
73+
tools: DynamicStructuredTool[]
74+
}) {
75+
const agent = await Team.createSupervisorAgent(
76+
llm,
77+
[
78+
{
79+
name: INDICATOR_AGENT_NAME,
80+
description: 'Create an indicator, only one at a time'
81+
}
82+
],
83+
tools,
84+
`As a indicator system architect specializing in data analysis, your task is to develop a set of indicators specifically for business data analysis based on multidimensional cube information and user prompts, and align with your business role.
85+
Each indicator gives a concise business requirement and name, and the indicators are sorted in the order of creation dependencies.
86+
87+
{role}
88+
89+
{language}
90+
91+
{context}
92+
93+
Methods for indicator design:
94+
- Directly use the basic measures defined in the model, which are measurement data extracted directly from the data source, such as sales amount, inventory quantity, etc.
95+
- Design indicators with calculation formulas, which are indicators calculated based on basic measures, such as year-on-year growth rate, average inventory turnover rate, etc.
96+
- Use Restricted filters to limit measurement data according to specific conditions or dimensions, such as sales in a specific time period, inventory in a certain area, etc.
97+
98+
1. Do not create duplicate indicators that already exist:
99+
{indicators}
100+
101+
2. ${promptIndicatorCode(`{indicatorCodes}`)}
102+
3. Please plan the indicator system first, and then decide to call route to create it one by one.
103+
`
104+
)
105+
106+
return RunnableLambda.from(async (state: IndicatorArchitectState) => {
107+
return {
108+
...state,
109+
indicators: markdownIndicators(indicators()),
110+
indicatorCodes: indicators()
111+
.map((indicator) => indicator.code)
112+
.join(', '),
113+
}
114+
}).pipe(agent)
115+
}
Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Indicator } from '@metad/cloud/state'
2-
import { Plan } from 'apps/cloud/src/app/@core/copilot'
2+
import { Team } from '@metad/copilot'
33

44
export const IndicatorArchitectCommandName = 'indicator-architect'
55
export const PLANNER_NAME = 'Planner'
@@ -8,11 +8,10 @@ export const REPLANNER_NAME = 'Replanner'
88
export const INDICATOR_AGENT_NAME = 'IndicatorAgent'
99

1010
// Define the top-level State interface
11-
export interface IndicatorArchitectState extends Plan.State {
12-
}
11+
export interface IndicatorArchitectState extends Team.State {}
1312

1413
export function markdownIndicators(indicators: Indicator[]) {
1514
return indicators
1615
.map((indicator) => ` -name: ${indicator.name}\n code: ${indicator.code}\n business: ${indicator.business || ''}`)
17-
.join('\n')
16+
.join('\n') || 'Empty'
1817
}

apps/cloud/src/app/features/project/copilot/context.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { NgmCopilotContextToken } from '@metad/copilot-angular'
33
import { markdownEntityType } from '@metad/core'
44
import { firstValueFrom, map, shareReplay } from 'rxjs'
55
import { ProjectService } from '../project.service'
6+
import { isIndicatorMeasureProperty } from '@metad/ocap-core'
67

78
export function provideCopilotCubes() {
89
const projectService = inject(ProjectService)
@@ -19,7 +20,19 @@ export function provideCopilotCubes() {
1920
dataSourceId: model.id,
2021
serizalize: async () => {
2122
const entityType = await firstValueFrom(projectService.selectEntityType(model.key, cube.name))
22-
return `The model id: '${model.id}'\n` + markdownEntityType(entityType)
23+
if (entityType) {
24+
25+
}
26+
return `The model id: '${model.id}'\n` + markdownEntityType({
27+
...entityType,
28+
// Filter excludes indicators
29+
properties: Object.keys(entityType.properties)
30+
.filter((key) => !isIndicatorMeasureProperty(entityType.properties[key]))
31+
.reduce((properties, key) => {
32+
properties[key] = entityType.properties[key]
33+
return properties
34+
}, {})
35+
})
2336
}
2437
},
2538
key: cube.name,

apps/cloud/src/app/features/project/copilot/indicator/indicator-agent.ts

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { DynamicStructuredTool } from '@langchain/core/tools'
22
import { makeCubeRulesPrompt, MEMBER_RETRIEVER_TOOL_NAME } from '@metad/core'
33
import { Route } from '../../../../@core/copilot'
44
import { markdownBusinessAreas, markdownTags } from '../schema'
5+
import { promptIndicatorCode } from '../prompt'
56

67
export async function createIndicatorWorker(
78
{ llm, indicatorCodes, businessAreas, tags },
@@ -11,13 +12,14 @@ export async function createIndicatorWorker(
1112
`You are a business expert in BI indicator system management. Please convert the specified Cube information and requirement description into corresponding parameters and call the createIndicator tool to create a new indicator.` +
1213
`\n{{role}}\n` +
1314
`\n${makeCubeRulesPrompt()}` +
14-
`\n1. Code cannot be the same as the following existing ones: [${indicatorCodes().join(', ')}]` +
15-
`\n2. Specify a hierarchy name (not the level name) of calendar dimension for this indicator to be used for future calculations of the indicator's trends at different time granularity. If no calendar semantic dimension is found in cube, this question needs to be answered.` +
16-
`\n3. If the requirement specifies the member condition of the dimension to be limited, then determine which dimension and member description needs to be limited based on the cube dimension information. Call the '${MEMBER_RETRIEVER_TOOL_NAME}' tool to obtain the accurate information of the dimension member and add it to the filters.` +
17-
`\n4. First, select a suitable measure from the Measures of the Cube as the measure field for defining the basic type of indicator. If the measure field of the basic indicator cannot meet the requirements, consider creating an MDX formula of calculated measure as the formula for the derived indicator. You don't need to multiply by 100 when defining a percentage formula` +
18-
`\n5. Set all dimensions (not hierarchy) not used in filters or formula or calendar to the 'dimensions' field.` +
19-
`\n6. If the indicator value is a ratio or percentage, you need to set unit to '%'.` +
20-
`\n7. If the cube has variables then all variables with defaultValueKey are added to the indicator's variables property, where each variable has the format:
15+
`
16+
1. ${promptIndicatorCode(indicatorCodes().join(', '))}
17+
2. Specifies the hierarchy name (not the level name) of the calendar dimension for this metric, which is used to calculate the metric trend at different time granularities in the future, if you can find a hierarchy of calendar dimension with multiple time granularities levels.
18+
3. If the requirement specifies the member condition of the dimension to be limited, then determine which dimension and member description needs to be limited based on the cube dimension information. Call the '${MEMBER_RETRIEVER_TOOL_NAME}' tool to obtain the accurate information of the dimension member and add it to the filters.
19+
4. First, select a suitable measure from the Measures of the Cube as the measure field for defining the basic type of indicator. If the measure field of the basic indicator cannot meet the requirements, consider creating an MDX formula of calculated measure as the formula for the derived indicator. You don't need to multiply by 100 when defining a percentage formula
20+
5. Set all dimensions (not hierarchy) not used in filters or formula or calendar to the 'dimensions' field.
21+
6. If the indicator value is a ratio or percentage, you need to set unit to '%'.
22+
7. If the cube has variables then all variables with defaultValueKey are added to the indicator's variables property, where each variable has the format:
2123
{
2224
dimension: {
2325
dimension: variable.referenceDimension,
@@ -30,12 +32,14 @@ export async function createIndicatorWorker(
3032
caption: variable.defaultValueCaption
3133
}
3234
]
33-
}.` +
34-
`\n8. Select the appropriate Business Areas from the following to fill in the businessAreaId field:` +
35-
markdownBusinessAreas(businessAreas()) +
36-
`\n9. Select the relevant tags from the following and fill in the tags field:` +
37-
markdownTags(tags()) +
38-
`\n If no Cube information is provided or you need to reselect a Cube, please call the 'pickCube' tool to get the Cube information.` +
35+
}.
36+
8. Select the appropriate Business Areas from the following to fill in the businessAreaId field:
37+
${markdownBusinessAreas(businessAreas())}
38+
9. Select the relevant tags from the following and fill in the tags field:
39+
${markdownTags(tags())}
40+
If no Cube information is provided or you need to reselect a Cube, please call the 'pickCube' tool to get the Cube information.
41+
` +
42+
3943
`\n{{context}}` +
4044
`\nCurrent indicator info is:` +
4145
`\n{{indicator}}`
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export function promptIndicatorCode(indicatorCodes: string) {
2+
return `Indicator code rules:
3+
a. Cannot be the same as the following existing ones: [${indicatorCodes}]
4+
b. Cannot be the same as the name of measures in the cube
5+
c. The code uses a unified coding rule, for example, indicators belonging to the same business module use the same code prefix.`
6+
}

apps/cloud/src/app/features/project/indicators/approvals/approvals.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<ngm-table class="flex-1 overflow-hidden rounded-lg border mt-2"
1+
<ngm-table class="flex-1 overflow-hidden rounded-lg mt-2"
22
paging
33
[data]="approvals$ | async"
44
[columns]="[

apps/cloud/src/app/features/project/indicators/approvals/approvals.component.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
:host {
2-
@apply flex-1 w-full max-w-full overflow-hidden flex flex-col p-4;
2+
@apply flex-1 w-full max-w-full overflow-hidden flex flex-col;
33
}
44

55
::ng-deep {

0 commit comments

Comments
 (0)